[
  {
    "path": "AUTHORS",
    "content": "Damian Pietras <daper@daper.net>\n"
  },
  {
    "path": "COPYING",
    "content": "\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\t\t\t    Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n\t\t    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n\t\t\t    NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n\t\t     END OF TERMS AND CONDITIONS\n\n\t    How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "ChangeLog",
    "content": "See the NEWS file.\n"
  },
  {
    "path": "Doxyfile",
    "content": "PROJECT_NAME           = MOC\nPROJECT_NUMBER         = 2.6-alpha3\nOUTPUT_DIRECTORY       = technical_docs\nCREATE_SUBDIRS         = NO\nOUTPUT_LANGUAGE        = English\nUSE_WINDOWS_ENCODING   = NO\nBRIEF_MEMBER_DESC      = YES\nREPEAT_BRIEF           = YES\nABBREVIATE_BRIEF       =\nALWAYS_DETAILED_SEC    = NO\nINLINE_INHERITED_MEMB  = NO\nFULL_PATH_NAMES        = YES\nSTRIP_FROM_PATH        =\nSTRIP_FROM_INC_PATH    =\nSHORT_NAMES            = NO\nJAVADOC_AUTOBRIEF      = YES\nMULTILINE_CPP_IS_BRIEF = NO\nDETAILS_AT_TOP         = NO\nINHERIT_DOCS           = YES\nDISTRIBUTE_GROUP_DOC   = YES\nTAB_SIZE               = 8\nALIASES                =\nOPTIMIZE_OUTPUT_FOR_C  = YES\nOPTIMIZE_OUTPUT_JAVA   = NO\nSUBGROUPING            = YES\nEXTRACT_ALL            = YES\nEXTRACT_PRIVATE        = YES\nEXTRACT_STATIC         = NO\nEXTRACT_LOCAL_CLASSES  = YES\nEXTRACT_LOCAL_METHODS  = NO\nHIDE_UNDOC_MEMBERS     = YES\nHIDE_UNDOC_CLASSES     = YES\nHIDE_FRIEND_COMPOUNDS  = NO\nHIDE_IN_BODY_DOCS      = NO\nINTERNAL_DOCS          = NO\nCASE_SENSE_NAMES       = YES\nHIDE_SCOPE_NAMES       = NO\nSHOW_INCLUDE_FILES     = NO\nINLINE_INFO            = YES\nSORT_MEMBER_DOCS       = YES\nSORT_BRIEF_DOCS        = NO\nSORT_BY_SCOPE_NAME     = NO\nGENERATE_TODOLIST      = YES\nGENERATE_TESTLIST      = YES\nGENERATE_BUGLIST       = YES\nGENERATE_DEPRECATEDLIST= YES\nENABLED_SECTIONS       =\nMAX_INITIALIZER_LINES  = 30\nSHOW_USED_FILES        = YES\nSHOW_DIRECTORIES       = NO\nFILE_VERSION_FILTER    =\nQUIET                  = NO\nWARNINGS               = YES\nWARN_IF_UNDOCUMENTED   = YES\nWARN_IF_DOC_ERROR      = YES\nWARN_NO_PARAMDOC       = NO\nWARN_FORMAT            = \"$file:$line: $text\"\nWARN_LOGFILE           =\nINPUT                  =\nFILE_PATTERNS          = *.c *.h *.doxy\nRECURSIVE              = YES\nEXCLUDE                =\nEXCLUDE_SYMLINKS       = NO\nEXCLUDE_PATTERNS       =\nEXAMPLE_PATH           =\nEXAMPLE_PATTERNS       =\nEXAMPLE_RECURSIVE      = NO\nIMAGE_PATH             =\nINPUT_FILTER           =\nFILTER_PATTERNS        =\nFILTER_SOURCE_FILES    = NO\nSOURCE_BROWSER         = NO\nINLINE_SOURCES         = NO\nSTRIP_CODE_COMMENTS    = YES\nREFERENCED_BY_RELATION = YES\nREFERENCES_RELATION    = YES\nVERBATIM_HEADERS       = YES\nALPHABETICAL_INDEX     = NO\nCOLS_IN_ALPHA_INDEX    = 5\nIGNORE_PREFIX          =\nGENERATE_HTML          = YES\nHTML_OUTPUT            = html\nHTML_FILE_EXTENSION    = .html\nHTML_HEADER            =\nHTML_FOOTER            =\nHTML_STYLESHEET        =\nHTML_ALIGN_MEMBERS     = YES\nGENERATE_HTMLHELP      = NO\nCHM_FILE               =\nHHC_LOCATION           =\nGENERATE_CHI           = NO\nBINARY_TOC             = NO\nTOC_EXPAND             = NO\nDISABLE_INDEX          = NO\nENUM_VALUES_PER_LINE   = 4\nGENERATE_TREEVIEW      = NO\nTREEVIEW_WIDTH         = 250\nGENERATE_LATEX         = YES\nLATEX_OUTPUT           = latex\nLATEX_CMD_NAME         = latex\nMAKEINDEX_CMD_NAME     = makeindex\nCOMPACT_LATEX          = NO\nPAPER_TYPE             = a4wide\nEXTRA_PACKAGES         =\nLATEX_HEADER           =\nPDF_HYPERLINKS         = NO\nUSE_PDFLATEX           = NO\nLATEX_BATCHMODE        = NO\nLATEX_HIDE_INDICES     = NO\nGENERATE_RTF           = NO\nRTF_OUTPUT             = rtf\nCOMPACT_RTF            = NO\nRTF_HYPERLINKS         = NO\nRTF_STYLESHEET_FILE    =\nRTF_EXTENSIONS_FILE    =\nGENERATE_MAN           = NO\nMAN_OUTPUT             = man\nMAN_EXTENSION          = .3\nMAN_LINKS              = NO\nGENERATE_XML           = NO\nXML_OUTPUT             = xml\nXML_SCHEMA             =\nXML_DTD                =\nXML_PROGRAMLISTING     = YES\nGENERATE_AUTOGEN_DEF   = NO\nGENERATE_PERLMOD       = NO\nPERLMOD_LATEX          = NO\nPERLMOD_PRETTY         = YES\nPERLMOD_MAKEVAR_PREFIX =\nENABLE_PREPROCESSING   = YES\nMACRO_EXPANSION        = NO\nEXPAND_ONLY_PREDEF     = NO\nSEARCH_INCLUDES        = YES\nINCLUDE_PATH           =\nINCLUDE_FILE_PATTERNS  =\nPREDEFINED             =\nEXPAND_AS_DEFINED      =\nSKIP_FUNCTION_MACROS   = YES\nTAGFILES               =\nGENERATE_TAGFILE       =\nALLEXTERNALS           = NO\nEXTERNAL_GROUPS        = YES\nPERL_PATH              = /usr/bin/perl\nCLASS_DIAGRAMS         = YES\nHIDE_UNDOC_RELATIONS   = YES\nHAVE_DOT               = NO\nCLASS_GRAPH            = YES\nCOLLABORATION_GRAPH    = YES\nGROUP_GRAPHS           = YES\nUML_LOOK               = NO\nTEMPLATE_RELATIONS     = NO\nINCLUDE_GRAPH          = YES\nINCLUDED_BY_GRAPH      = YES\nCALL_GRAPH             = NO\nGRAPHICAL_HIERARCHY    = YES\nDIRECTORY_GRAPH        = YES\nDOT_IMAGE_FORMAT       = png\nDOT_PATH               =\nDOTFILE_DIRS           =\nMAX_DOT_GRAPH_WIDTH    = 1024\nMAX_DOT_GRAPH_HEIGHT   = 1024\nMAX_DOT_GRAPH_DEPTH    = 0\nDOT_TRANSPARENT        = NO\nDOT_MULTI_TARGETS      = NO\nGENERATE_LEGEND        = YES\nDOT_CLEANUP            = YES\nSEARCHENGINE           = NO\n"
  },
  {
    "path": "INSTALL",
    "content": "Installation Instructions\n*************************\n\nCopyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,\n2006, 2007, 2008, 2009 Free Software Foundation, Inc.\n\n   Copying and distribution of this file, with or without modification,\nare permitted in any medium without royalty provided the copyright\nnotice and this notice are preserved.  This file is offered as-is,\nwithout warranty of any kind.\n\nBasic Installation\n==================\n\n   Briefly, the shell commands `./configure; make; make install' should\nconfigure, build, and install this package.  The following\nmore-detailed instructions are generic; see the `README' file for\ninstructions specific to this package.  Some packages provide this\n`INSTALL' file but do not implement all of the features documented\nbelow.  The lack of an optional feature in a given package is not\nnecessarily a bug.  More recommendations for GNU packages can be found\nin *note Makefile Conventions: (standards)Makefile Conventions.\n\n   The `configure' shell script attempts to guess correct values for\nvarious system-dependent variables used during compilation.  It uses\nthose values to create a `Makefile' in each directory of the package.\nIt may also create one or more `.h' files containing system-dependent\ndefinitions.  Finally, it creates a shell script `config.status' that\nyou can run in the future to recreate the current configuration, and a\nfile `config.log' containing compiler output (useful mainly for\ndebugging `configure').\n\n   It can also use an optional file (typically called `config.cache'\nand enabled with `--cache-file=config.cache' or simply `-C') that saves\nthe results of its tests to speed up reconfiguring.  Caching is\ndisabled by default to prevent problems with accidental use of stale\ncache files.\n\n   If you need to do unusual things to compile the package, please try\nto figure out how `configure' could check whether to do them, and mail\ndiffs or instructions to the address given in the `README' so they can\nbe considered for the next release.  If you are using the cache, and at\nsome point `config.cache' contains results you don't want to keep, you\nmay remove or edit it.\n\n   The file `configure.ac' (or `configure.in') is used to create\n`configure' by a program called `autoconf'.  You need `configure.ac' if\nyou want to change it or regenerate `configure' using a newer version\nof `autoconf'.\n\n   The simplest way to compile this package is:\n\n  1. `cd' to the directory containing the package's source code and type\n     `./configure' to configure the package for your system.\n\n     Running `configure' might take a while.  While running, it prints\n     some messages telling which features it is checking for.\n\n  2. Type `make' to compile the package.\n\n  3. Optionally, type `make check' to run any self-tests that come with\n     the package, generally using the just-built uninstalled binaries.\n\n  4. Type `make install' to install the programs and any data files and\n     documentation.  When installing into a prefix owned by root, it is\n     recommended that the package be configured and built as a regular\n     user, and only the `make install' phase executed with root\n     privileges.\n\n  5. Optionally, type `make installcheck' to repeat any self-tests, but\n     this time using the binaries in their final installed location.\n     This target does not install anything.  Running this target as a\n     regular user, particularly if the prior `make install' required\n     root privileges, verifies that the installation completed\n     correctly.\n\n  6. You can remove the program binaries and object files from the\n     source code directory by typing `make clean'.  To also remove the\n     files that `configure' created (so you can compile the package for\n     a different kind of computer), type `make distclean'.  There is\n     also a `make maintainer-clean' target, but that is intended mainly\n     for the package's developers.  If you use it, you may have to get\n     all sorts of other programs in order to regenerate files that came\n     with the distribution.\n\n  7. Often, you can also type `make uninstall' to remove the installed\n     files again.  In practice, not all packages have tested that\n     uninstallation works correctly, even though it is required by the\n     GNU Coding Standards.\n\n  8. Some packages, particularly those that use Automake, provide `make\n     distcheck', which can by used by developers to test that all other\n     targets like `make install' and `make uninstall' work correctly.\n     This target is generally not run by end users.\n\nCompilers and Options\n=====================\n\n   Some systems require unusual options for compilation or linking that\nthe `configure' script does not know about.  Run `./configure --help'\nfor details on some of the pertinent environment variables.\n\n   You can give `configure' initial values for configuration parameters\nby setting variables in the command line or in the environment.  Here\nis an example:\n\n     ./configure CC=c99 CFLAGS=-g LIBS=-lposix\n\n   *Note Defining Variables::, for more details.\n\nCompiling For Multiple Architectures\n====================================\n\n   You can compile the package for more than one kind of computer at the\nsame time, by placing the object files for each architecture in their\nown directory.  To do this, you can use GNU `make'.  `cd' to the\ndirectory where you want the object files and executables to go and run\nthe `configure' script.  `configure' automatically checks for the\nsource code in the directory that `configure' is in and in `..'.  This\nis known as a \"VPATH\" build.\n\n   With a non-GNU `make', it is safer to compile the package for one\narchitecture at a time in the source code directory.  After you have\ninstalled the package for one architecture, use `make distclean' before\nreconfiguring for another architecture.\n\n   On MacOS X 10.5 and later systems, you can create libraries and\nexecutables that work on multiple system types--known as \"fat\" or\n\"universal\" binaries--by specifying multiple `-arch' options to the\ncompiler but only a single `-arch' option to the preprocessor.  Like\nthis:\n\n     ./configure CC=\"gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64\" \\\n                 CXX=\"g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64\" \\\n                 CPP=\"gcc -E\" CXXCPP=\"g++ -E\"\n\n   This is not guaranteed to produce working output in all cases, you\nmay have to build one architecture at a time and combine the results\nusing the `lipo' tool if you have problems.\n\nInstallation Names\n==================\n\n   By default, `make install' installs the package's commands under\n`/usr/local/bin', include files under `/usr/local/include', etc.  You\ncan specify an installation prefix other than `/usr/local' by giving\n`configure' the option `--prefix=PREFIX', where PREFIX must be an\nabsolute file name.\n\n   You can specify separate installation prefixes for\narchitecture-specific files and architecture-independent files.  If you\npass the option `--exec-prefix=PREFIX' to `configure', the package uses\nPREFIX as the prefix for installing programs and libraries.\nDocumentation and other data files still use the regular prefix.\n\n   In addition, if you use an unusual directory layout you can give\noptions like `--bindir=DIR' to specify different values for particular\nkinds of files.  Run `configure --help' for a list of the directories\nyou can set and what kinds of files go in them.  In general, the\ndefault for these options is expressed in terms of `${prefix}', so that\nspecifying just `--prefix' will affect all of the other directory\nspecifications that were not explicitly provided.\n\n   The most portable way to affect installation locations is to pass the\ncorrect locations to `configure'; however, many packages provide one or\nboth of the following shortcuts of passing variable assignments to the\n`make install' command line to change installation locations without\nhaving to reconfigure or recompile.\n\n   The first method involves providing an override variable for each\naffected directory.  For example, `make install\nprefix=/alternate/directory' will choose an alternate location for all\ndirectory configuration variables that were expressed in terms of\n`${prefix}'.  Any directories that were specified during `configure',\nbut not in terms of `${prefix}', must each be overridden at install\ntime for the entire installation to be relocated.  The approach of\nmakefile variable overrides for each directory variable is required by\nthe GNU Coding Standards, and ideally causes no recompilation.\nHowever, some platforms have known limitations with the semantics of\nshared libraries that end up requiring recompilation when using this\nmethod, particularly noticeable in packages that use GNU Libtool.\n\n   The second method involves providing the `DESTDIR' variable.  For\nexample, `make install DESTDIR=/alternate/directory' will prepend\n`/alternate/directory' before all installation names.  The approach of\n`DESTDIR' overrides is not required by the GNU Coding Standards, and\ndoes not work on platforms that have drive letters.  On the other hand,\nit does better at avoiding recompilation issues, and works well even\nwhen some directory options were not specified in terms of `${prefix}'\nat `configure' time.\n\nOptional Features\n=================\n\n   If the package supports it, you can cause programs to be installed\nwith an extra prefix or suffix on their names by giving `configure' the\noption `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.\n\n   Some packages pay attention to `--enable-FEATURE' options to\n`configure', where FEATURE indicates an optional part of the package.\nThey may also pay attention to `--with-PACKAGE' options, where PACKAGE\nis something like `gnu-as' or `x' (for the X Window System).  The\n`README' should mention any `--enable-' and `--with-' options that the\npackage recognizes.\n\n   For packages that use the X Window System, `configure' can usually\nfind the X include and library files automatically, but if it doesn't,\nyou can use the `configure' options `--x-includes=DIR' and\n`--x-libraries=DIR' to specify their locations.\n\n   Some packages offer the ability to configure how verbose the\nexecution of `make' will be.  For these packages, running `./configure\n--enable-silent-rules' sets the default to minimal output, which can be\noverridden with `make V=1'; while running `./configure\n--disable-silent-rules' sets the default to verbose, which can be\noverridden with `make V=0'.\n\nParticular systems\n==================\n\n   On HP-UX, the default C compiler is not ANSI C compatible.  If GNU\nCC is not installed, it is recommended to use the following options in\norder to use an ANSI C compiler:\n\n     ./configure CC=\"cc -Ae -D_XOPEN_SOURCE=500\"\n\nand if that doesn't work, install pre-built binaries of GCC for HP-UX.\n\n   On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot\nparse its `<wchar.h>' header file.  The option `-nodtk' can be used as\na workaround.  If GNU CC is not installed, it is therefore recommended\nto try\n\n     ./configure CC=\"cc\"\n\nand if that doesn't work, try\n\n     ./configure CC=\"cc -nodtk\"\n\n   On Solaris, don't put `/usr/ucb' early in your `PATH'.  This\ndirectory contains several dysfunctional programs; working variants of\nthese programs are available in `/usr/bin'.  So, if you need `/usr/ucb'\nin your `PATH', put it _after_ `/usr/bin'.\n\n   On Haiku, software installed for all users goes in `/boot/common',\nnot `/usr/local'.  It is recommended to use the following options:\n\n     ./configure --prefix=/boot/common\n\nSpecifying the System Type\n==========================\n\n   There may be some features `configure' cannot figure out\nautomatically, but needs to determine by the type of machine the package\nwill run on.  Usually, assuming the package is built to be run on the\n_same_ architectures, `configure' can figure that out, but if it prints\na message saying it cannot guess the machine type, give it the\n`--build=TYPE' option.  TYPE can either be a short name for the system\ntype, such as `sun4', or a canonical name which has the form:\n\n     CPU-COMPANY-SYSTEM\n\nwhere SYSTEM can have one of these forms:\n\n     OS\n     KERNEL-OS\n\n   See the file `config.sub' for the possible values of each field.  If\n`config.sub' isn't included in this package, then this package doesn't\nneed to know the machine type.\n\n   If you are _building_ compiler tools for cross-compiling, you should\nuse the option `--target=TYPE' to select the type of system they will\nproduce code for.\n\n   If you want to _use_ a cross compiler, that generates code for a\nplatform different from the build platform, you should specify the\n\"host\" platform (i.e., that on which the generated programs will\neventually be run) with `--host=TYPE'.\n\nSharing Defaults\n================\n\n   If you want to set default values for `configure' scripts to share,\nyou can create a site shell script called `config.site' that gives\ndefault values for variables like `CC', `cache_file', and `prefix'.\n`configure' looks for `PREFIX/share/config.site' if it exists, then\n`PREFIX/etc/config.site' if it exists.  Or, you can set the\n`CONFIG_SITE' environment variable to the location of the site script.\nA warning: not all `configure' scripts look for a site script.\n\nDefining Variables\n==================\n\n   Variables not defined in a site shell script can be set in the\nenvironment passed to `configure'.  However, some packages may run\nconfigure again during the build, and the customized values of these\nvariables may be lost.  In order to avoid this problem, you should set\nthem in the `configure' command line, using `VAR=value'.  For example:\n\n     ./configure CC=/usr/local2/bin/gcc\n\ncauses the specified `gcc' to be used as the C compiler (unless it is\noverridden in the site shell script).\n\nUnfortunately, this technique does not work for `CONFIG_SHELL' due to\nan Autoconf bug.  Until the bug is fixed you can use this workaround:\n\n     CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash\n\n`configure' Invocation\n======================\n\n   `configure' recognizes the following options to control how it\noperates.\n\n`--help'\n`-h'\n     Print a summary of all of the options to `configure', and exit.\n\n`--help=short'\n`--help=recursive'\n     Print a summary of the options unique to this package's\n     `configure', and exit.  The `short' variant lists options used\n     only in the top level, while the `recursive' variant lists options\n     also present in any nested packages.\n\n`--version'\n`-V'\n     Print the version of Autoconf used to generate the `configure'\n     script, and exit.\n\n`--cache-file=FILE'\n     Enable the cache: use and save the results of the tests in FILE,\n     traditionally `config.cache'.  FILE defaults to `/dev/null' to\n     disable caching.\n\n`--config-cache'\n`-C'\n     Alias for `--cache-file=config.cache'.\n\n`--quiet'\n`--silent'\n`-q'\n     Do not print messages saying which checks are being made.  To\n     suppress all normal output, redirect it to `/dev/null' (any error\n     messages will still be shown).\n\n`--srcdir=DIR'\n     Look for the package's source code in directory DIR.  Usually\n     `configure' can determine that directory automatically.\n\n`--prefix=DIR'\n     Use DIR as the installation prefix.  *note Installation Names::\n     for more details, including other options available for fine-tuning\n     the installation locations.\n\n`--no-create'\n`-n'\n     Run the configure checks, but stop before creating any output\n     files.\n\n`configure' also accepts some other, not widely useful, options.  Run\n`configure --help' for more details.\n\n"
  },
  {
    "path": "Makefile.am",
    "content": "ACLOCAL_AMFLAGS = -I m4\nSUBDIRS = themes decoder_plugins\nAM_CPPFLAGS = -DSYSTEM_THEMES_DIR=\\\"$(pkgdatadir)/themes\\\" \\\n\t      -DPLUGIN_DIR=\\\"$(plugindir)/$(DECODER_PLUGIN_DIR)\\\"\n\nbin_PROGRAMS = mocp\nmocp_SOURCES = log.c \\\n\t       log.h \\\n\t       protocol.h \\\n\t       protocol.c \\\n\t       server.c \\\n\t       server.h \\\n\t       main.c \\\n\t       common.c \\\n\t       common.h \\\n\t       compiler.h \\\n\t       playlist.c \\\n\t       playlist.h \\\n\t       fifo_buf.c \\\n\t       fifo_buf.h \\\n\t       out_buf.c \\\n\t       out_buf.h \\\n\t       audio.c \\\n\t       audio.h \\\n\t       decoder.c \\\n\t       decoder.h \\\n\t       interface.c \\\n\t       interface.h \\\n\t       interface_elements.c \\\n\t       interface_elements.h \\\n\t       menu.c \\\n\t       menu.h \\\n\t       files.c \\\n\t       files.h \\\n\t       options.c \\\n\t       options.h \\\n\t       player.c \\\n\t       player.h \\\n\t       playlist_file.c \\\n\t       playlist_file.h \\\n\t       themes.c \\\n\t       themes.h \\\n\t       keys.c \\\n\t       keys.h \\\n\t       io.c \\\n\t       io.h \\\n\t       compat.c \\\n\t       compat.h \\\n\t       audio_conversion.c \\\n\t       audio_conversion.h \\\n\t       rbtree.c \\\n\t       rbtree.h \\\n\t       tags_cache.c \\\n\t       tags_cache.h \\\n\t       utf8.c \\\n\t       utf8.h \\\n\t       rcc.c \\\n\t       rcc.h \\\n\t       softmixer.c \\\n\t       softmixer.h \\\n\t       lyrics.h \\\n\t       lyrics.c \\\n\t       lists.h \\\n\t       lists.c \\\n\t       equalizer.h \\\n\t       equalizer.c\nEXTRA_mocp_SOURCES = \\\n\t\t     md5.c \\\n\t\t     md5.h \\\n\t\t     null_out.c \\\n\t\t     null_out.h \\\n\t\t     sndio_out.c \\\n\t\t     sndio_out.h \\\n\t\t     oss.c \\\n\t\t     oss.h \\\n\t\t     alsa.c \\\n\t\t     alsa.h \\\n\t\t     io_curl.c \\\n\t\t     io_curl.h \\\n\t\t     jack.c \\\n\t\t     jack.h\nman_MANS = mocp.1\nmocp_LDADD = @EXTRA_OBJS@ -lltdl -lm\nmocp_DEPENDENCIES = @EXTRA_OBJS@\nmocp_LDFLAGS = @EXTRA_LIBS@ $(RCC_LIBS) -export-dynamic\nEXTRA_DIST = README_equalizer mocp.1 THANKS keymap.example Doxyfile \\\n\t         doxy_pages/decoder_api.doxy doxy_pages/main_page.doxy \\\n\t         doxy_pages/sound_output_driver_api.doxy\nEXTRA_DIST += @EXTRA_DISTS@\nEXTRA_DIST += tools/README tools/md5check.sh tools/maketests.sh\nnoinst_DATA = tools/README\nnoinst_SCRIPTS = tools/md5check.sh tools/maketests.sh\n\ndoc_DATA = config.example THANKS README README_equalizer keymap.example\n"
  },
  {
    "path": "NEWS",
    "content": "   [Note that all relevant maintenance applied to the stable version has\n    also been applied to the development version.  You should review the\n    development version changes in conjunction with those of the current\n    stable version maintenance releases.]\n\n\n2.6 - \"Usability\"  [alpha3: 2016-11-16]\n\t* Autotools and packaging changes:\n\t  - Dropped version micro number for development versions\n\t  - Upgraded autoconf-archive macros to 2014-02-28 release\n\t  - Replaced custom shell code with Autoconf archive macros\n\t* Changed build behaviours:\n\t  - 'make dist': now defaults to XZ compression\n\t  - curl-config: replaced by pkg-config\n\t  - New GCC-5 and 6 warnings addressed\n\t  - Removed checks for POSIX.1 and C99 mandated headers and functions\n\t* Changed minimum support and release requirements:\n\t  - POSIX.1: introduced minimum requirement of IEEE 1003.1-2001\n\t  - C Compiler: introduced minimum requirement of ISO 9899:1999\n\t  - FFmpeg/LibAV: raised minimum requirement to release 1.0/10.0\n\t  - FLAC: raised minimum requirement to release 1.1.3\n\t  - Berkeley DB: raised minimum requirement to release 4.1\n\t  - libcurl: raised minimum requirement to version 7.15.1\n\t  - autoconf: raised minimum requirement to version 2.64\n\t  - ALSA: raised minimum requirement to version 1.0.11\n\t* New and changed library requirements:\n\t  - POPT library: MOC now requires libpopt\n\t* Documentation:\n\t  - Improved wording of some error messages\n\t  - Added environment variables section to the manpage\n\t* Removed functionality:\n\t  - Transitional code from the previous release\n\t  - autogen.sh in favour of autoreconf(1)\n\t  - Deprecated ffmpeg-config support\n\t  - Warning of changed executable name\n\t  - Removed \"Search Next\" interface command\n\t* Changed functionality:\n\t  - Decoupled mono-mixing from softmixer\n\t  - Made command line toggling arguments case insensitive\n\t* Added functionality:\n\t  - Introduced in-memory circular logging buffer\n\t  - Introduced MOCP_POPTRC environment variable\n\t  - Introduced MOCP_OPTS environment variable\n\t  - Add 24-bit support to the OSS sound driver (Vladimir Krylov)\n\t* New and changed command line options:\n\t  - echo-args: Show POPT-interpreted command line arguments\n\t* New and changed audio decoders:\n\t  - All sndfile-supported extensions are now available\n\t  - Added .mat[45] extensions as aliases for .mat in sndfile decoder\n\t  - Added .ircam extension as alias for .sf in sndfile decoder\n\t  - Added reading from Internet streams to FFmpeg decoder (Jenny Wong)\n\t* Changes to supported formats and codecs:\n\t  - All formats provided by Sndfile are now supported\n\t  - DSD: now supported via FFmpeg/LibAV (.dff and .dsf files)\n\t  - TTA: now supported via FFmpeg/LibAV\n\t  - VQF: now supported via FFmpeg/LibAV\n\t* Miscellaneous\n\t  - Improved logging from ALSA sound driver and library\n\t  - Fixed displayed ALSA volume percentage drift\n\n2.5.2  [2016-11-16]\n\t* New configuration file options:\n\t  - ALSAStutterDefeat: avoid the ALSA bug which causes stuttering\n\t* Significant bug fixes:\n\t  - Fixed error during configure on busybox systems\n\t  - Fixed calculation of average bitrate in FLAC decoder\n\t  - Fixed unintentional disabling of MMAP support\n\t  - Fixed build failure on split ncurses/tinfo systems\n\t  - Fixed unreaped children when running in foreground mode\n\t  - Fixed freeze on Nokia devices if audios play to completion\n\t  - Circumvented ALSA stutter bug\n\t  - Added missing 'Precache' option to example config file\n\t* Miscellaneous:\n\t  - Improved the accuracy of the average bitrate for FLAC\n\t  - Mitigated out-of-file seeking for several formats\n\t  - Warn of TagLib version requirement rising to 1.5\n\t  - Warn of Musepack library changing to libmpc (from libmpcdec)\n\n2.5.1  [2016-02-24]\n\t* Significant bug fixes:\n\t  - Corrected the setting of linked libraries for Berkeley DB\n\t  - Fixed ALSA volume setting problem (Tomasz Golinski)\n\t  - Fixed clearing of stream status message on error\n\t  - Resolved FFmpeg/LibAV's downmixing API issues (Andreas Cadhalpun)\n\t  - Removed duplicate logging of fatal error messages\n\t* Miscellaneous:\n\t  - Adapted to FFmpeg 3.0 API\n\t  - Warn of FFmpeg/LibAV version requirement rising to 1.0/10.0\n\t  - Warn of pending POSIX.1-2001 compliance requirement\n\t  - Fixed several warnings and errors on OpenBSD (Brent Cook)\n\t  - Fixed various (and potential) segfaults (Daniel T. Borelli,\n\t    Hendrik Iben, Rastislav Barlik)\n\t  - Fixed various resource leakages\n\t  - Silenced various compiler warnings\n\t  - Added instructions on building with autoreconf\n\n2.5.0 - \"Consolidation\"  [2014-08-13]\n\t* Autotools and packaging changes:\n\t  - Upgraded autoconf version requirement to 2.60\n\t  - Added '--with-alsa' to suppress ALSA sound driver inclusion\n\t  - Added '--without-oss' to suppress OSS sound driver inclusion\n\t  - Added '--disable-cache' to remove tags cache support\n\t  - Added specific GDB support to '--enable-debug'\n\t  - Refactor decoder plug-ins' autoconf scripts into source directories\n\t  - Ensure that all decoders get into the source distribution tarball\n\t  - Removed SID decoder's dependance on .la-file presence\n\t  - Removed distribution-specific .spec file\n\t  - Minor reformatting of the configure summary\n\t  - Added warnings for various deprecations and potential problems\n\t  - Added warnings for unmet future package requirements\n\t  - Updated GNU boilerplate text\n\t* Improved support for embedded systems:\n\t  - Refined FFmpeg decoder configuration for use with cross-compilation\n\t  - Provided use of Tremor with the Vorbis decoder\n\t  - Improve portability to non-GNU library platforms\n\t  - Added --with-libiconv-prefix configure option\n\t* Audio driver changes:\n\t  - Allow use of the OSSv4 per-application mixer API\n\t  - Provided SNDIO support for OpenBSD systems (Alexander Polakov)\n\t* New and changed audio decoders:\n\t  - New TiMidity decoder for MIDI (Hendrik Iben)\n\t  - Migrate AAC decoder to later FAAD2 API (Max Klinger)\n\t  - Added AAC+ (HE-AAC) support to AAC decoder\n\t  - New SidPlay2 decoder for SID (Hendrik Iben)\n\t  - New Modplug decoder (Hendrik Iben)\n\t  - New WavPack decoder (Alexandrov Sergey)\n\t  - Renamed SndFile plugin for consistancy\n\t  - Removed M4A format support from AAC decoder plugin\n\t  - Removed WAV format support from ModPlug decoder plugin\n\t  - Detect huge files in certain formats which SndFile cannot play\n\t  - Improved (drastically in some cases) the accuracy of AAC durations\n\t* Overhauled FFmpeg/LibAV decoder:\n\t  - Resolved FFmpeg API deprecations\n\t  - Provided LibAV compatibility\n\t  - Provided proper stereo downmixing\n\t  - Provided locking support for non-thread-safe library functions\n\t  - Provided better FFmpeg or LibAV discrimination\n\t  - Provided better audio duration reliability determination\n\t  - Increased number of decodable formats (including Xiph Opus)\n\t  - Added decoding of audio from video formats\n\t  - Added logging of FFmpeg/LibAV messages\n\t  - Added seeking in most (but not all) formats\n\t  - Added handling for \"planar\" codecs\n\t  - Excluded experimental codecs from decoding\n\t  - Fixed misreporting of tags, duration and bitrates\n\t  - Fixed memory and file descriptor leakages\n\t  - Fixed severe distortion on 8-bit samples\n\t  - Fixed loop playing FLAC files\n\t  - Fixed many FFmpeg/LibAV API breakages\n\t  - Fixed many miscellaneous bugs\n\t  - Detect over-length (and therefore broken) WAV files\n\t  - Fixed log formatting when FFmpeg messages contain newline characters\n\t* Audio reproduction changes:\n\t  - Improved support for 8-bit sample size\n\t  - Added software mixer (Hendrik Iben)\n\t  - Added parametric equalizer (Hendrik Iben)\n\t  - Fixed many bugs which produced distorted sound\n\t  - Fixed bugs in 24-bit sample handling (Tomasz Golinski)\n\t* General configuration file changes:\n\t  - Reconciled and regularised example config and keymap files\n\t  - Introduced lists and function-like syntax\n\t  - Introduced variable substitution\n\t  - Introduced symbol and boolean option types\n\t  - Improved security of the configuration file\n\t  - Automatic clearing of an overridden default key binding\n\t  - Made processing of keymap file consistant with that of config file\n\t* Changed configuration file options:\n\t  - Layout# options moved to a list and function-like syntax\n\t  - SoundDriver option moved to a list syntax\n\t  - Renamed OSSMixerChannel to OSSMixerChannel1\n\t  - Renamed ALSAMixer to ALSAMixer1\n\t  - QueueNextSongReturn moved to yes/no values\n\t  - TagsCacheSize set to zero now disables tag caching\n\t* New configuration file options:\n\t  - OnSongChange: run an external command (Jack Miller)\n\t  - RepeatSongChange: govern the running of the OnSongChange command\n\t  - OnStop: run an external command on stopping\n\t  - EnforceTagsEncoding: substitutes ID3v1TagsEncoding for ISO-8859-1\n\t    encoding in ID3v2 tags (Aleks Sherikov)\n\t  - FileNamesIconv: converts from local to UTF8 encoding for file names\n\t    (Aleks Sherikov)\n\t  - NonUTFXterm: converts UTF8 to local encoding for X-Term titles\n\t\t(Aleks Sherikov)\n\t  - AutoLoadLyrics: says whether MOC should look for lyrics files\n\t  - PreferredDecoders: allow finer control over decoder selection\n\t  - XTerms: externalises terminals regarded as X-Terms\n\t  - UseMIMEMagic: says whether to identify audio files by using MIME\n\t  - JackStartServer: autostart JACK the server (Max Klinger)\n\t  - ShowTimePercent: set the percent played state (Daniel T. Borelli)\n\t  - Various options for SidPlay2, Modplug and TiMidity support\n\t* New command line options:\n\t  - '-j' to jump to some position in the current track (Nuno Cardoso)\n\t  - '-O' to override configuration file settings\n\t  - '-Q' to display user formatted information (Juho Hämäläinen)\n\t  - '-q' to queue files from command line (Martin Milata)\n\t  - '-t' and '--on/off' to toggle or set playback options (Jack Miller)\n\t  - '-v' to set the volume (Jack Miller)\n\t* Screen handling changes:\n\t  - Changed minimum screen height to 7 lines (Tero Marttila)\n\t  - Added support for GNU screen title (Jonathan Derque)\n\t  - Restored screen to console mode after reporting fatal errors\n\t  - Populated playlist panel when loading default playlist file\n\t  - Removed default playlist autofocus at start\n\t  - Fixed overlength highlight bar\n\t  - Fixed screen upset when tags contain control characters\n\t  - Fixed some screen upsets when tags contain UTF-8 characters (firejox)\n\t  - Fixed screen upset caused by screen(1) mis-detection\n\t* New and updated client interaction features:\n\t  - 'a' command: also add a directory to the playlist (Filippo Giunchedi)\n\t  - 'L' command: display music lyrics (Géraud Le Falher)\n\t  - 'Y' command: prune unreadable files from the playlist (tyranix)\n\t  - Queued messages for display in the message area\n\t  - Added play queue (Martin Milata)\n\t  - Mark a fragment of a file which can be passed to external commands\n\t  - Clear status message after a stream open failure\n\t  - Minor help menu clarifications\n\t* Improve text entry history and editting:\n\t  - Recognise ^u (by default) as delete to start of line\n\t  - Recognise ^k (by default) as delete to end of line\n\t  - Save and restore entered text prior to history scrolling\n\t  - Save history entry modifications\n\t  - Do not save adjacent duplicate entries\n\t  - Do not save or modify with blank entries\n\t  - Position cursor at end of restored or history line\n\t* Theme changes:\n\t  - Enabled highlighted playlist numbers and file times (Marc Tschiesche)\n\t  - Fixed miscoloured frame when switching themes (Alexander Polakov)\n\t  - Fixed default colour settings\n\t  - Fixed cursor placement in themes menu (Alex Merenstein)\n\t  - Fixed ordering of theme files in themes menu\n\t  - Fixed new attributes application on theme switching (Alex Merenstein)\n\t* General code cleaning:\n\t  - Refactoring, optimisations and cosmetic improvements\n\t  - Silenced many build warnings and errors\n\t  - Replaced various deprecated, legacy and platform-specific functions\n\t  - Improved thread safety\n\t* Significant bug fixes:\n\t  - Fixed stale locks freeze in tags cache database following a crash\n\t  - Fixed CURL timeout for internet streaming errors (Daniel Stenberg)\n\t  - Fixed audio distortion on MP3 file having overly long tag values\n\t  - Fixed false positive stream detection in the MP3 decoder\n\t  - Fixed Ogg/Vorbis stream detection in the Vorbis decoder\n\t  - Fixed 'LRINTF error' raised when reconfiguring MOC\n\t  - Fixed backspace key mishandling\n\t  - Fixed client interface meta-key handling\n\t  - Fixed pthread stack overflow segfault on OpenBSD\n\t  - Fixed segfault when text entry history becomes full\n\t  - Fixed segfault processing playlists with relative paths\n\t  - Fixed memory corruptions when toggling tag reading\n\t  - Fixed assertion when a second client is started\n\t  - Fixed slow memory leak in client on long-playing streams\n\t  - Fixed severe distortion on 11025Hz 16-bit mono audios\n\t  - Fixed freeze at end of playing audio file\n\t  - Fixed server crash when attempting to play a deleted file\n\t  - Fixed MIME type detection on HTTP redirection\n\t  - Fixed crash when mixer value is above 100%\n\t  - Fixed handling of huge (greater than 2 GiB) files\n\t  - Fixed sub-second audio truncation on ALSA\n\t  - Fixed segfault when MIME-detected MP3 file has no \"extension\"\n\t  - Fixed segfault when using '--format' without an audio playing\n\t  - Workaround for streams that have the actual title as tags/comments\n\t  - Indentified cause of \"undefined symbol\" build errors\n\t  - Plugged all known memory and file descriptor leaks\n\t  - Fixed nonsense duration values returned on some corrupt FLAC files\n\t  - Fixed 'time >= 0' assertion at change of audio file\n\t  - Fixed client event notification failure in some circumstances\n\t  - Fixed client abort on duplicated playlist filenames\n\t  - Fixed delayed client exit when server invokes long-running scripts\n\t    (Alex Merenstein)\n\t  - Fixed occasional server freeze on logging when running scripts\n\t  - Fixed non-conforming 'User-Agent' HTTP request header\n\t* Miscellaneous:\n\t  - Updated and reformatted mocp manpage and equalizer README file\n\t  - Made many warning and error reports more informative and consistant\n\t  - Better logging of more problem determination information\n\t  - More informative version information display\n\t  - Introduced MD5-based decoder verification tools\n\t  - Improved compatibility with 64-bit systems\n\t  - Improved compatibility with big-endian systems\n\t  - Moved tags cache to a Berkeley DB\n\t  - Integration of down-stream distribution patches\n\t  - Fixed frames to duration calculations in ALSA\n\t  - Fixed some mutex management bugs\n\t  - Fixed many bugs which crashed MOC\n\n2.4.4  [2009-01-04]\n\t* Fix crash when saving a playlist with URLs.\n\t* Fix hang in case of symlink loop (like symlink to parent directory)\n\t  when adding files recursively to the playlist.\n\t* Fix bad memory access when using FILL parameter in layouts that caused\n\t  \"FATAL_ERROR: Layout1 is malformed\".\n\t* Fix compilation with newer ffmpeg. (Alexis Ballier)\n\t* Increase maximum file title (made from tags) length to 512. Helps on\n\t  wide terminals.\n\t* Fix displaying URLs on the playlist when it ends with a slash.\n\t* Fix compilation of flac plugin by detecting libflac using pkg-config.\n\t* Fix for multichannel playback. (Maarten van Es)\n\t* Fix handling of invalid track number in tags. A segfault occurred when\n\t  the track number was at least a value of 2^31.\n\t* Support for the new (SVN) libmpcdec API.\n\t* Remove old, irrelevant comment about iconv from the configuration\n\t  file.\n\t* Fix configure to display proper information about compiled RCC.\n\t* Run libtoolize in autogen.sh to prevent conflicts with installed\n\t  libtool/libltdl version.\n\n2.4.3  [2007-07-30]\n\t* Fix displaying tags from Internet streams, sometimes the name of the\n\t  station or other less useful text was displayed instead the title.\n\t* Fix a problem with opening Internet streams with curl 7.16.x. (Samid\n\t  Tennakoon)\n\t* Fix XTermTitle for urxvt.\n\t* Fix delete and backspace keys in entries.\n\t* Fix a race (crash) when issuing the next commands one after another\n\t  very fast.\n\t* Fix problems with vmix OSS virtual driver (no supported audio\n\t  formats).\n\t* Fix FollowPlayedFile after CLI restart.\n\t* Updated moc.spec (Klaus Ethgen)\n\n2.4.2  [2007-06-10]\n\t* Disable 24bit output by default due to reported problems with some\n\t  sound cards. It can be enabled by setting Allow24bitOutput option\n\t  to yes.\n\t* Fix escape key handling. (Jack Miller)\n\t* Fix CTRL-key combinations in entries. (Jack Miller)\n\t* Fix a crash when a file is precached and user requests playing a\n\t  different file.\n\t* Disabled using mmap() for reading files in the default configuration\n\t  due to reported incrased memory usage.\n\t* Fix ffmpeg build failure due to LOG_H defined by ffmpeg headers.\n\t* Fix reading PLS playlists with more than 9 entries.\n\t* Fix configure script: libiconv is required to build moc.\n\t* Fix the A command for '..' directory (Debian Bug#416102)\n\t* Fix displaying the playlist panel when the width of the terminal is\n\t  an odd number.\n\n2.4.1  [2006-02-12]\n\t* Added Command 'P' and a configuration option PlaylistFullPaths to turn\n\t  on/off displaying full paths for files in the playlist.\n\t* Fixed choosing endianess when playing float samples (musepack or\n\t  wave and other libsndfile formats).\n\t* Fixed a crash when going to '../' with the search entry.\n\t* Added MP4 to the list of supported by FFmpeg extensions.\n\t* Fixed clearing playlist from the command line (crash on mocp -c -a -p\n\t  file.mp3).\n\t* Fixed handling mixer errors (crash in some situations).\n\t* Fix searching with '/' (search began with the current item, not the\n\t  whole list).\n\t* Correct support librcc: tags ID3v1/v1.1 will be recoded in UTF-8\n\t  instead of the local encoding. (Alexey Gladkov)\n\t* Fixed handling invalid time in tags cache.\n\t* Fixed handling error when time information for a file could not be\n\t  read.\n\t* MusicDir and FastDirX are parsed before they are used, this handles\n\t  not \"clean\" directories in the config file (like /bin/ - with slash\n\t  at the end).\n\t* When interface is killed by SIGTERM or SIGHUP the playlist is saved\n\t  and the terminal mode is restored.\n\t* Setting volume in jack using an exponential function instead of\n\t  linear. (x37v.alex)\n\t* Draw bottom lines for side menus that don't touch the botom of the\n\t  main menu.\n\t* Reload directory content after running a custom command to see changes\n\t  made in the filesystem by this command.\n\t* Added missing exec_command# keys in the example keymap file.\n\n2.4.0  [2006-02-12]\n\t* Layout of the main window can be changed using Layout[123] options.\n\t  Switching between layouts is done using the 'l' key and now TAB is\n\t  used to switch between the playlist and the directory menu. For\n\t  example, you can configure the layout to see both the playlist and a\n\t  directory content.\n\t* Support for WMA, RealAudio, MP4 and AAC file using FFmpeg.\n\t* UTF-8 support.\n\t* Selecting themes at runtime - T command (this does not change the\n\t  config file).\n\t* Executing external commands, like 'cp %f /mnt/usb_drive' where %f is\n\t  substituted with the path to the currently selected file.\n\t* Tags are cached at the server side and read in a separate thread.\n\t  The interface is not locked until the tags are read. Size of the\n\t  cache can be adjusted using TagsCacheSize. The cache is saved at exit\n\t  and loaded at startup.\n\t* Moving items up and down: u and j commands.\n\t* Workaround for encoding of ID3v1 tags. New options: UseRCC - to use\n\t  librcc for ID3v1 reencoding (Initial patch by Alexey Gladkov),\n\t  ID3v1TagsEncoding - assumed encoding for ID3v1 tags.\n\t* Added UseCursorSelection option (default: no) to display cursor on\n\t  the selected file. This is useful with braille displays.\n\t* Added SetXtermTitle option (disable/enable setting xterm title).\n\t* Added m4a and aac to the list of extensions supported by ffmpeg.\n\t* Pressing n when nothing is played starts playing from the first item\n\t  on the playlist.\n\t* Added FollowPlayedFile option: menu follows the currently played file\n\t  so that it is scrolled if the file is outside the visible part\n\t  (default to yes).\n\t* Numbering items in the playlist. Can be turned off using\n\t  PlaylistNumbering.\n\t* New themes: moca_theme (Nicola Vitale), red_theme (yyz), and\n\t  darkdot_theme (David Lazar).\n\t* Added a command for adding a URL to the playlist using entry (CTRL-u).\n\t* A and --append can add files from playlists.\n\t* Interface show the playlist after startup if something from the\n\t  playlist is played (CanStartInPlaylist option).\n\t* Commands: --append, --clear, --play work now as expected even if there\n\t  is no client running.\n\t* Use full paths instead of just file names for displaying on the\n\t  playlist.\n\t* Internet streams can be paused.\n\t* Ogg plugin name was changed to vorbis.\n\t* Added RPM SPEC file. (Fredrik Rambris)\n\t* Redesign of the interface code. This is a big change in the code, but\n\t  not really visible to the user. It was necessary to maintain and\n\t  extend the interface in the future.\n\t* MOC can now be compiled under OpenBSD. Thanks to Tilo Stritzky for\n\t  pointing out the issues.\n\t* Striping leading and trailing white characters from URLs entered by\n\t  the user.\n\t* Count speex time without scaning the whole file.\n\t* -e is an alias for -a.\n\t* Display the current time for Internet streams.\n\t* MusicDir can be a playlist.\n\t* Silent seeking can be configured using SilentSeekTime option.\n\t* After adding a file to the playlist, the cursor is moved down.\n\t* The help screen's position is kept when it's not displayed.\n\t* Some operations like deleting items from the playlist should now be\n\t  faster.\n\t* Fixed a possible deadlock while seeking. This fixes a hang on\n\t  FreeBSD 6.\n\t* Fixed OSS support on some machones (like G3 iBook).\n\n2.3.3  [2006-01-04]\n\t* Workaround for backspace key on many terminals (like aterm).\n\t* Fixed a memory leak when playing Internet streams. (rixx)\n\t* Fixed a crash when an mpc file coudn't be read.\n\t* Fixed a memory leak occuring with every played mp3 file.\n\t* Fixed a memory leak when seeking in MPC file.\n\t* Fixed compilation on OpenBSD. (Tilo Stritzky)\n\t* Fixed parsing Icy metadata packages (possible segfault).\n\t* Fixed resource leak when reading tags from invalid OGG file.\n\t* Check if FileNamesIconv has valid format at startup like TagsIconv.\n\n2.3.2  [2005-09-25]\n\t* Optimized 24bit->16bit conversion. Helps playing mp3 on handheld\n\t  devices without FPU.\n\t* Fixed a crash when using the playlist with ReadTags turnedoff.\n\t* Fixed detecting taglib 1.4.\n\t* Fixed mutex initialization in io objects (crash on some systems -\n\t  FreeBSD).\n\t* Moved the man page to section 1.\n\t* Fixed bahaviour when one or both OSS mixer channels are not available.\n\t* Sort file names using the current locale settings. (breg)\n\t* Fixed -p description.\n\t* Fixed a memory leak when adding an item to a full entry's history.\n\t* Slightly less CPU usage in the client due to elimination of useless\n\t  bitrate updates.\n\t* Fixed a typo: owerwrite -> overwrite.\n\n2.3.1  [2005-08-02]\n\t* Fixed detecting MPEG stream by the content (Shoutcast fix).\n\t* Fixed handling ALSA mixer events (crash in some situations).\n\t* Fixed handling a mixer channel that has no playback volume.\n\t* Fixed crash when the alsa mixer couldn't be opened.\n\t* Fixed crash when nothing was decoded (appears sometimes when holding\n\t  the enter key on an mp3 file).\n\t* Fixed a crash which shows up on FreeBSD 5.4 after playing a file.\n\t  Thanks to Joseph Dunn for help.\n\t* Fixed includes for maximum and minimum values of intiger types (fix\n\t  compilation on some systems).\n\t* Fixed the syntax (not a proper C code). Mostly taken from the FreeBSD\n\t  port.\n\t* Show stream URL instead of nothing if the title is not available.\n\t* Fixed yellow_red_theme theme.\n\t* Fixed a hang after file open error.\n\t* Fixed counting time after file open error.\n\t* Fixed some gcc4 warnings.\n\n2.3.0  [2005-07-09]\n\t* Network streams (shoutcast, icecast, regular HTTP, FTP). You can load\n\t  an m3u file with a URL or use the 'o' command.\n\t* JACK output (by Alex Norman).\n\t* Added support for musepack (mpc).\n\t* The search command filters out not matching elements from the menu and\n\t  allows to walk through the items like in the regular menu.\n\t* Added support for speex format.\n\t* Plugins: to drop dependencies from many exotic libraries, support for\n\t  file formats was moved to shared libraries. This should help making\n\t  MOC packages in future when more formats will be added.\n\t* Sample rate conversion using libsamplerate and some sound conversions\n\t  like 16bit -> 24bit etc. With 24bit sound cards 24bit output is used\n\t  with mp3.\n\t* Added support for PLS version 2 playlists.\n\t* Added mono to stereo conversion.\n\t* MOC now compiles using libtool.\n\t* Error messages for precached files are not displayed while other file\n\t  is being played.\n\t* Input buffer in a separated thread is used for reading files (new\n\t  options: InputBuffer and Prebuffering).\n\t* New theme: Yellow/Red (by Morten Grunnet Buhl).\n\t* Added -A command line option and ASCIILines config option that\n\t  disables usage of graphic characters to draw lines.\n\t* Added commands to set volume from 10% to 90% in 10% steps, default\n\t  bindings are ALT-1 to ALT-9.\n\t* Added commands to quickly go to a selected directories\n\t  (by Alex Norman).\n\t* Added --next and --previous command line options (by Alex Norman).\n\t* Added --info command line option that prints all information about the\n\t  currently played file. (Based on the code by Michael Banks)\n\t* Two mixer channels can be set in the configuration file. They can be\n\t  switched at run time by pressing x.\n\t* Documentation of some parts of the code in Doxygen format.\n\t* Colors can be redefined in themes using 'colordef COLOR = R G B'\n\t  (works for terminal that can change the colors).\n\t* Support for 24bit flac files (not tested).\n\t* Added SeekTime option: how fast the seeking is. (Kamil Tarkowski)\n\t* A list of sound drivers can be used instead of only one driver in the\n\t  configuration file. The first working driver will be used.\n\t* MusicDir can be set to a playlist.\n\t* Alsa mixer has always 0-100 range despite the actual device range.\n\t* Added --toggle-pause command line option.\n\t* Don't block the audio device when paused. Based on a patch\n\t  by hondza <miscreant@tiscali.cz>\n\t* Treat an option that is set in the config file more than once as an\n\t  error.\n\t* Print descriptions for some errors in the config file.\n\t* Better error messages in the Ogg plugin.\n\t* Better detecting Ogg vorbis stream by content.\n\t* Decreased the time to wait for the pcm to become ready to get samples\n\t  in alsa. This helps with underruns.\n\t* Fixed protect attribute in themes.\n\t* Added black_theme (by Arn).\n\t* History for \"go to a directory\" and \"enter URL\" commands (using\n\t  arrows).\n\t* CTRL-l like CTRL-r refreshes the screen.\n\t* Separated iconv() conversion for file names and tags.\n\t* Improved performance a bit when operating on big playlists.\n\t* G command points to the currently played file - on the playlist if\n\t  it's there, and selects it in the menu.\n\t* When going to a directory using the i command, TAB completes to the\n\t  matching part of ambiguous directories.\n\t* Volume changes made by other programs are detected.\n\t* Added --recursively command line option (make a playlist from the\n\t  content of a directory given at command line).\n\t* Show bitrate in the right time - the value corresponds to what you\n\t  can hear, not the position that the decoder is at.\n\t* Added description about creating a decoder plugin.\n\t* Added introduction pages to Doxygen documentation.\n\t* Documented decoder plugins API.\n\t* Added --playit option (play files given on the command line without\n\t  modifing the playlist).\n\t* Added UseRealtimePriority (default no) option: set realtime priority\n\t  for the output buffer thread.\n\t* Titles in saved playlists are not converted by iconv().\n\t* Fixed crash on some systems when a file is precached.\n\t* Refuse to run if an ALSA mixer channel is wrong.\n\t* Fixed adding items to the playlist when the server's playlist already\n\t  has added files.\n\t* Don't try to use colors on black/white terminals.\n\t* Fixed a memory leak in iconv_str() when iconv() is not available.\n\t  (breg)\n\n2.2.2  [2005-05-16]\n\t* Added an option to ignore CRC errors in mp3 files (set to yes by\n\t  default, like most players do). This fixes playing mp3 files that\n\t  have bad CRC checksums but are actually good.\n\t* Fixed marking the currently played item when a new item on the\n\t  playlist appears.\n\t* Fixed a crash when going back at the first item with Repeat turned\n\t  on (Debian Bug#307651).\n\t* Fixed a crash when the time of a file can't be read.\n\t* Fixed handling FLAC files with 8-bit samples.\n\t* Added mp2, mp1, and mpga to the list of supported extensions by MAD:\n\t  this makes possible to play MPEG Layer I and II files.\n\t* Fixed a race condition that crashes MOC when switching songs quickly.\n\t* Fixed a small memory leak in FLAC.\n\t* Ogg Vorbis is not required for compilation (a bug in the configure\n\t  script).\n\t* Use a pipe to wake up another thread from select() instead of a\n\t  signal. Helps on NetBSD where pthread_kill() doesn't interrupt\n\t  select().\n\t* Fixes for compiler warnings on 64-bit systems.\n\t* Fixed a warning about wprintw() usage on some systems.\n\t* Fixed setting compiler flags for vorbis.\n\n2.2.1  [2005-04-16]\n\t* Fixed missing titles of files without tags when added to the playlist.\n\t* Fixed refreshing the screen after searching.\n\t* Fixed recognizing AIFF file extension.\n\t* Fixed compiler warnings on 64bit systems.\n\t* Fixed detecting ncurses, now also works with curses.\n\t* Fixed compilation errors on ioctl() use on some systems.\n\t* Support for libossaudio (NetBSD).\n\t* Fixed an compiler warning about EV_ERROR on NetBSD.\n\t* Better checking for vorbis.\n\t* Fixed --without-sndfile configure option.\n\t* Fixed a rare deadlock.\n\n2.2.0  [2005-02-26]\n\t* Added support for FLAC audio format.\n\t* Added support for various file formats like au, aiff, voc and more\n\t  using libsndfile. This also provides better support for wav files.\n\t* Custom keymaps can be used.\n\t* Synchronizing the playlist between clients (interface instances).\n\t* Going to a directory by typing the path with file-name completion\n\t  (i command).\n\t* Ability to use default (transparent) and grey color.\n\t  (Marcin Michałowski).\n\t* Added a theme with transparent background (Marcin Michałowski).\n\t* Added nightly_theme (Wim Speekenbrink).\n\t* Added green_theme (Jacek Lehmann).\n\t* Global theme directory.\n\t* MOC should now work on big-endian processors.\n\t* Added XTermTheme option to use a different theme when running on\n\t  XTerm.\n\t* Fixed the 'next' command when AutoNext option is turned off.\n\t* Displaying the total time of files on the playlist and in directories.\n\t* Faster reading and saving playlists.\n\t* Added --pause and --unpause command line parameters.\n\t* Added AlsaMixer and AlsaDevice options.\n\t* Added G command: go to a directory when the currently played file is.\n\t* Added U command: go to '..'.\n\t* Recognize if a file was modified and rereading tags and time if\n\t  necessary.\n\t* Fast, silent seeking ('[' and ']' keys).\n\t* The cursor is hidden when it's not needed.\n\t* Changed the erroneous name of the time_left_frames to\n\t  time_total_frames in themes.\n\t* Added '/' command working as an alias for g (Kamil Tarkowski).\n\t* Added CTRL-n working like CTRL-g (Kamil Tarkowski).\n\t* ? key working like h (Kamil Tarkowski).\n\t* Playlist time displayed in format 000:00:00 (Kamil Tarkowski).\n\t* True shuffle, not a random item from the playlist.\n\t* Changing the icon name when changing the xterm title (Jacek Lehmann).\n\t* Arrow keys and the delete key works in the entry field.\n\t* Detecting other terminals beside xterm (Jacek Lehmann).\n\t* Added b (back) command (Kamil Tarkowski).\n\t* A bit faster getting files time.\n\t* Added SavePlaylist option.\n\t* Added a bar showing time of the current file.\n\t* Repeat without AutoNext plays a song in loop.\n\t* Added (empty|filled)_mixer_bar, (empty|filled)_time_bar to themes.\n\t* Fixed setting sound parameters while playing OGG. This caused crashes\n\t  on big endian machines (Philippe De Muyter).\n\t* Fixed crash on some file tags.\n\t* Fixed crash when issuing some errors.\n\t* Fixed the --help message.\n\t* Fixed bad terminal state after exiting the client in rare cases.\n\t* Fixed adding directories recursively when ReadTags is turned off.\n\t* Fixed going to the root directory with the i command.\n\t* Fixed getting time when nothing is played.\n\t* iconv() (character set conversion) for file names.\n\t* END and HOME keys working in the i command entry (go to a directory).\n\t* Synchronizing adding/deleting items from the playlist with the\n\t  server's playlist.\n\t* Faster operations on big playlists.\n\t* Fixed saving playlist when more than one client wants to do this.\n\t* Fixed recursive adding file to the playlist if some directory\n\t  can't be read.\n\t* Fixed deleting items from the playlist.\n\t* Fixed handling a playlist file as command a line argument.\n\t* Fixed compilation with --disable-debug.\n\t* Fixed --version information.\n\t* Fixed sorting when using the A command.\n\t* Position of time and format in menu is fixed.\n\t* Searching for a theme in the user directory first.\n\t* Fixed a memory leak when reading OGG tags.\n\t* Few fixes when running MOC with file names or directories as\n\t  arguments.\n\t* Fixed reading track numbers for mp3 files.\n\t* Small fixes of ALSA behaviour when underrun occurs.\n\n2.1.4  [2004-12-31]\n\t* Fixed the 'next' command when AutoNext is turned off.\n\t* Sorting the files before adding them to the playlist when using the\n\t  A command.\n\t* Fixed a memory leak when reading tags of ogg files.\n\t* Fixed reading track numbers of mp3 files.\n\t* Detect other X terminals beside xterm.\n\t* Fixed error messages for mp3 files.\n\t* Fixed segfault when reading some tags.\n\n2.1.3  [2004-11-15]\n\t* Fixed compilation with header files not from Linux 2.4.\n\n2.1.2  [2004-11-14]\n\t* Fixed playing mp3 with UseMmap turned on.\n\t* Fixed handling ALSA device open errors.\n\t* Proper handling 8, 16, and 24 bit sound, raise error if using\n\t  anything else.\n\t* Fixed a (almost :) deadlock when pressing stop when seeking.\n\n2.1.1  [2004-11-13]\n\t* Added missing example_theme.\n\n2.1.0  [2004-11-11]\n\t* Multiple clients. You can have moc interface on many\n\t  consoles/terminals.\n\t* Searching the menu (CTRL-g) like M-s in Midnight Commander.\n\t* Playlist load/save support (m3u files).\n\t* The apperance can be customized using themes.\n\t* Displaying file time and format in the menu.\n\t* Precaching files and not closing the audio device when playing\n\t  the next file - no more gaps between two songs.\n\t* ALSA support.\n\t* Optional character set conversion using iconv().\n\t* MOC remembers the playlist between runs.\n\t* Option to hide file names extension.\n\t* Use of mmap() can be turned off (usefull on NFS).\n\t* Ability to use different config file/directory tan the default.\n\t* Pressing CTRL-C interrupts long operations that blocks the interface\n\t  like reding tags for hundreds of files.\n\t* Fixed seeking and counting time in VBR mp3 files.\n\t* Fixes for systems where threads are not processes.\n\t* Small fixes and cleanups.\n\n2.0.1  [2004-10-21]\n\t* Do not allow to add '..' to the playlist.\n\t* Fixed seeking when not playing.\n\t* Fixed segfault when using f after creating a playlist from the\n\t  command line.\n\t* Fixed invoking MOC with relative path arguments\n\t* Fixed displaying file title longer than the window.\n\t* Fixed broken menu after resizing terminal when help screen is\n\t  displayed.\n\n2.0.0  [2004-10-16]\n\t* Split into client-server, you can detach the interface and leave\n\t  the server playing songs in the background (keys: q - background,\n\t  Q - quit).\n\t* Output buffer with seperate thread.\n\t* Using OSS directly - better stop and pause.\n\t* Support xterm resizing.\n\t* Using xterm title.\n\t* Autonext option (X key).\n\t* Faster reading directory contents.\n\t* Displaying time left to the end of the songs.\n\t* Fixed moving through symlinked directories.\n\t* Renamed executable file to mocp due to conflict with QT meta object\n\t  compiler.\n\t* Configurable mixer channel, OSS output device, and mixer device.\n\t* ^r - refresh the screen, r - reread the directory.\n\t* H command and ShowHiddenFiles - as the option says.\n\t* Do not allow displaying bitrate too fast to read it.\n\t* Fixed compilation with pthreads, better configure.\n\t* Fixed problems when compiling with gcc 3.3\n\t* Fixed displaying window title longer than the window.\n\t* Space key working as pause.\n\t* Fixed possible segfault in trenary expressions.\n\t* Removed version checker.\n\t* Fixed sorting directories ('../' was not always on top).\n\t* Fixed escaping characters in config file, no need for '\\\\\\\\'.\n\t* Stronger checking for parse errors in configuration file.\n\n1.1.0  [2002-12-08]\n\t* Support for WAV.\n\t* Playlist without reading/writeing to file.\n\t* Option in the config file to automaticaly change process priority at\n\t  startup. Helpful on slow computers.\n\t* Recursivaly adding files from comamnd line and from the interface.\n\t* Shuffle and repeat.\n\t* Nice progress bar for longer operations.\n\t* Fixed segfault when using the END key.\n\t* Fixed segfault when playing OGG files.\n\t* Workaround for kde and gnome terminals.\n\t* Now MOC can be compiled on FreeBSD.\n\t* Other fixes.\n\n1.0.0\n\t* First release intended to be stable and user-friendly :)\n\t* Added man page.\n\t* Added version checker (simple command to see if a new version of MOC\n\t  has been released).\n\t* Fixed problem when compiling without ogg.\n\t* Added redraw command.\n\t* Other small fixes.\n\n0.9.2\n\t* Fixed -s option.\n\t* Added 'f' command (switching between short and full file names).\n\t* Added README file.\n\t* Added support for config file.\n\t* Added MusicDir option, 'm' command and '-m' switch.\n\t* Added ability to turning off messages about broken streams.\n\t* Fixed including dirent.h for various OS.\n\t* Fixed and updated configure.\n\n0.9.1\n\t* Added mixer (using OSS).\n\t* Added help screen.\n\t* Fixed some tiny buffer overflows.\n\t* Small fixes in code.\n\n0.9.0\n\t* First stable version.\n"
  },
  {
    "path": "README",
    "content": "                                 MOC\n                      m u s i c  o n  c o n s o l e\n\n                          http://moc.daper.net/\n\n\n--------------------------------------------------------------------------------\nWhat Is It?\n--------------------------------------------------------------------------------\n\nMOC (music on console) is a console audio player for LINUX/UNIX designed to be\npowerful and easy to use.\n\nYou just need to select a file from some directory using the menu similar to\nMidnight Commander, and MOC will start playing all files in this directory\nbeginning from the chosen file.  There is no need to create playlists as in\nother players.\n\nIf you want to combine some files from one or more directories in one playlist,\nyou can do this.  The playlist will be remembered between runs or you can save\nit as an m3u file to load it whenever you want.\n\nNeed the console where MOC is running for more important things?  Need to close\nthe X terminal emulator?  You don't have to stop playing - just press q and the\ninterface will be detached leaving the server running.  You can attach it later,\nor you can attach one interface in the console, and another in the X terminal\nemulator, no need to switch just to play another file.\n\nMOC plays smoothly, regardless of system or I/O load because it uses the output\nbuffer in a separate thread.  The transition between files is gapless, because\nthe next file to be played is precached while the current file is playing.\n\nSupported file formats are: MP3, Ogg Vorbis, FLAC, Musepack (mpc), Speex, Opus,\nWAVE, those supported by FFmpeg/LibAV (e.g., WMA, RealAudio, AAC, MP4), AIFF,\nAU, SVX, Sphere Nist WAV, IRCAM SF, Creative VOC, SID, wavpack, MIDI and\nmodplug.\n\nOther features:\n\n  - Simple mixer\n  - Color themes\n  - Menu searching (playlist or directory) like M-s in Midnight Commander\n  - The way MOC creates titles from tags is configurable\n  - Optional character set conversion for file tags using iconv()\n  - OSS, ALSA, SNDIO and JACK output\n  - User defined keys\n  - Cache for files' tags\n\n--------------------------------------------------------------------------------\nDocumentation and The MOC Forum\n--------------------------------------------------------------------------------\n\nThis file is only a brief description of MOC, for more information is\navailable on the home page (http://moc.daper.net/documentation).\n\nYou can also find a discussion forum on the MOC home page.\n\n--------------------------------------------------------------------------------\nWhat Software Is Required To Build It?\n--------------------------------------------------------------------------------\n\nTo build MOC from the distribution tarball you will need:\n\n  - A POSIX.1-2001 compatible UNIX system with POSIX threads\n    (e.g., Linux or OSX)\n  - A C compiler which is C99 capable and a C++ compiler (MOC is written\n    in C, but libtool and some decoder plugins require a C++ compiler)\n  - ncurses (probably already installed in your system)\n  - POPT (libpopt) (probably already installed in your system)\n  - Berkeley DB (libdb) version 4.1 (unless configured with --disable-cache)\n  - GnuPG (gpg) if you are going to verify the tarball (and you should)\n\nIf you are building from the SVN repository you will also need:\n\n  - Subversion or git-svn (to checkout the source directory tree)\n  - Autoconf version 2.64 and the associated Automake and Libtool\n\nYou should choose which of the following audio formats you wish to play and\nprovide the libraries needed to support them:\n\n  - AAC - libfaad2 version 2.7 (http://www.audiocoding.com/), and\n          libid3tag (http://www.underbit.com/products/mad/)\n  - FLAC - libFLAC version 1.1.3 (http://flac.sourceforge.net/)\n  - MIDI - libtimidity version 0.1 (http://timidity.sourceforge.net/)\n  - modplug - libmodplug version 0.7 (http://modplug-xmms.sourceforge.net/)\n  - MP3 - libmad with libid3tag (ftp://ftp.mars.org/pub/mpeg/)\n  - Musepack (mpc) - libmpc (http://www.musepack.net/), and\n                   - taglib version 1.3.1\n                     (http://developer.kde.org/~wheeler/taglib.html)\n  - Ogg Vorbis - libvorbis, libogg and libvorbisfile (all version 1.0)\n                 (http://www.xiph.org/ogg/), or\n               - libvorbisidec and libogg (both version 1.0)\n                 (http://svn.xiph.org/trunk/Tremor)\n  - SID - libsidplay2 version 2.1.1 and libsidutils version 1.0.4\n          (http://sidplay2.sourceforge.net/)\n  - Speex - libspeex version 1.0 (http://www.speex.org/), and\n          - libogg version 1.0 (http://www.xiph.org/ogg/)\n  - WMA, RealAudio (.ra), MP4 - FFmpeg version 1.0 (http://www.ffmpeg.org/), or\n                              - LibAV version 10.0 (http://www.libav.org/)\n  - WAVE, AU, AIFF, SVX, SPH, IRC, VOC - libsndfile version 1.0\n                                         (http://www.mega-nerd.com/libsndfile/)\n  - wavpack - libwavpack version 4.31 (http://www.wavpack.com/)\n\nFor interfacing to the sound sub-system, you will need libraries for one or\nmore of the following:\n\n  - ALSA - alsa-lib version 1.0.11 (http://www.alsa-project.org/)\n  - OSS - the OSS libraries (http://www.opensound.com/)\n  - BSD's SNDIO - SNDIO libraries\n  - JACK low-latency audio server - JACK version 0.4\n                                    (http://jackit.sourceforge.net/)\n\nFor network streams:\n\n  - libcurl version 7.15.1 (http://curl.haxx.se/)\n\nFor resampling (playing files with sample rate not supported by your\nhardware):\n\n  - libresamplerate version 0.1.2 (http://www.mega-nerd.com/SRC/)\n\nFor librcc (fixes encoding in broken mp3 tags):\n\n  - http://rusxmms.sourceforge.net/\n\nNote that for Debian-based distributions, you will also require any '-dev'\nsuffixed versions of the packages above if building from source.\n\nThe versions given above are minimum versions and later versions should also\nwork.  However, MOC may not yet have caught up with the very latest changes\nto library interfaces and these may cause problems if they break backwards\ncompatibility.\n\n--------------------------------------------------------------------------------\nOn Which Systems Is MOC Running?\n--------------------------------------------------------------------------------\n\nMOC is developed and tested on GNU/Linux.\n\nMOC is now C99 and POSIX.1-2001 compliant and so should build and run on\nany system which has a C99 capable compiler and is POSIX.1-2001 compatible.\nHowever, there may still be cases where MOC breaks this compliance and any\nreports of such breakage are welcome.\n\nThere is no intention to support MOC on MS-Windows (so please don't ask).\n\n--------------------------------------------------------------------------------\nHow Do I Verify the Authenticity of the Tarball?\n--------------------------------------------------------------------------------\n\nIf you downloaded the official MOC distribution you should have the\nfollowing files:\n\n\tmoc-2.6-alpha3.tar.asc\n\tmoc-2.6-alpha3.tar.md5\n\tmoc-2.6-alpha3.tar.xz\n\nwould check the integrity of the download:\n\n\tmd5sum -c moc-2.6-alpha3.tar.md5\n\nand then verify the tarball thusly:\n\n\txzcat moc-2.6-alpha3.tar.xz | gpg --verify moc-2.6-alpha3.tar.asc -\n\nThe signature file (*.asc) was made against the uncompressed tarball,\nso if the tarball you have has been recompressed using a tool other\nthan XZ then uncompress it using the appropriate tool then verify that:\n\n\tgunzip moc-2.6-alpha3.tar.gz\n\tgpg --verify moc-2.6-alpha3.tar.asc\n\nIf the tool can output to stdout (and most can) then you can also verify\nverify it in a single step similar to the way in which the XZ tarball\nwas verified above.\n\nOf course, you'll also need the MOC Release Signing Key:\n\n\tgpg --recv-key 0x2885A7AA\n\nfor which the fingerprint is:\n\n\t5935 9B80 406D 9E73 E805  99BE F312 1E4F 2885 A7AA\n\n--------------------------------------------------------------------------------\nHow Do I Build and Install It?\n--------------------------------------------------------------------------------\n\nGeneric installation instruction is included in the INSTALL file.\n\nIn short, if you are building from an SVN checkout of MOC (but not if you\nare building from a downloaded tarball) then you will first need to run:\n\n\tautoreconf -if\n\nand then proceed as shown below for a tarball.  (If you are using the\ntarball but have applied additional patches then you may also need to run\nautoreconf.)\n\nTo build MOC from a downloaded tarball just type:\n\n\t./configure\n\tmake\n\nAnd as root:\n\n\tmake install\n\nUnder FreeBSD and NetBSD (and possibly other systems) it is necessary to\nrun the configure script this way:\n\n\t./configure LDFLAGS=-L/usr/local/lib CPPFLAGS=-I/usr/local/include\n\nIn addition to the standard configure options documented in the INSTALL\nfile, there are some MOC-specific options:\n\n\t--enable-cache=[yes|no]\n\n\t  Specifying 'no' will disable the tags cache support.  If your\n\t  intent is to remove the Berkeley DB dependancy (rather than\n\t  simply removing the on-disk cache) then you should also either\n\t  build MOC without RCC support or use a librcc built with BDB\n\t  disabled.\n\n\t--enable-debug=[yes|no|gdb]\n\n\t  Using 'gdb' will cause MOC to be built with options tailored to\n\t  use with GDB.  (Note that in release 2.6 this option will be\n\t  split into separate debugging and logging options.)\n\n\t--with-oss=[yes|no|DIR]\n\n\t  Where DIR is the location of the OSS include directory (and\n\t  defaults to '/usr/lib/oss').\n\n\t--with-vorbis=[yes|no|tremor]\n\n\t  Using 'tremor' will cause MOC to build against the integer-only\n\t  implementation of the Vorbis library (libvorbisidec).\n\nYou can install MOC into its own source directory tree and run it from there\nso you do not have to install it permanently on your system.  If you're just\nwanting to try it out or test some patches, then this is something you may\nwish to do:\n\n\t./configure --prefix=\"$PWD\" --without-timidity\n\tmake\n\tmake install\n\tbin/mocp -M .moc\n\n--------------------------------------------------------------------------------\nHow Do I Use It?\n--------------------------------------------------------------------------------\n\nRun program with the 'mocp' command.  The usage is simple; if you need help,\npress 'h' and/or read mocp manpage.  There is no complicated command line or\ncryptic commands.  Using MOC is as easy as using basic functions of Midnight\nCommander.\n\nYou can use a configuration file placed in ~/.moc/config, but it's not required.\nSee config.example provided with MOC.\n\n--------------------------------------------------------------------------------\nUsing Themes\n--------------------------------------------------------------------------------\n\nYes, there are themes, because people wanted them. :)\n\nThemes can change all colors and only colors.  An example theme file with a\nexhaustive description is included (themes/example_theme) and is the\ndefault MOC appearance.\n\nTheme files should be placed in ~/.moc/themes/ or $(datadir)/moc/themes/\n(e.g., /usr/local/share/moc/themes) directory, and can be selected with\nthe Theme configuration options or the -T command line option (see the\nmanpage and the example configuration file).\n\nFeel free to share the themes you have created.\n\n--------------------------------------------------------------------------------\nDefining Keys\n--------------------------------------------------------------------------------\n\nYou can redefine standard keys.  See the instructions in the keymap.example\nfile.\n\n--------------------------------------------------------------------------------\nHow Do I Report A Problem?\n--------------------------------------------------------------------------------\n\nNot every release is extensively tested on every system, so the particular\nconfiguration of software, libraries, versions and hardware on your system\nmight expose a problem.\n\nIf you find any problems then you should search the MOC Forum for a solution;\nyour problem may not be unique.  If you do find an existing topic which\nmatches your problem but does not offer a solution, or the solution offered\ndoes not work for you and the topic appears still active, then please add your\nexperience to it; it may be that additional information you can provide will\ncontain the clue needed to resolve the problem.\n\nIf you don't find an answer there and you installed MOC from your Linux\ndistribution's repository then you should report it via your distribution's\nusual reporting channels in the first instance.  If the problem is ultimately\nidentified as actually being in MOC itself, it should then be reported to the\nMOC Maintainer (preferably by the distribution's MOC package maintainer).\n\nIf you built MOC from source yourself or you get no resolution from your\ndistribution then start a new topic on the MOC Forum for your problem or\ncontact the MOC Maintainer.\n\nBefore reporting a problem, you should first read this Forum post:\n\n   Linkname: How to Report Bugs Effectively\n        URL: http://moc.daper.net/node/1035\n\nand the essay it references:\n\n   Linkname: How to Report Bugs Effectively\n        URL: http://www.chiark.greenend.org.uk/~sgtatham/bugs.html\n\nThere are two things you must do if at all possible:\n\n1. Make sure you are using the current stable MOC release or, even better,\n   can reproduce it on the latest development release or SVN HEAD, and\n2. Make sure you include the version and revision information (which you\n   can obtain by running 'mocp --version').\n\nIf you do not do those two things (and don't offer a good explanation as to\nwhy you didn't) your problem report is likely to be ignored until such time\nas you do.\n\n--------------------------------------------------------------------------------\nHacking\n--------------------------------------------------------------------------------\n\nWant to modify MOC?  You're welcome to do so, and patch contributions are\nalso welcome.\n\nMOC is written in C, so you must at least know this language to make simple\nchanges.  It is multi-threaded program, but there are places where you don't\nneed to worry about that (the interface is only a single thread process).  It\nuses autoconf, automake and libtool chain to generate configuration/compilation\nstuff, so you must know how to use it, for example, if you need to link to an\nadditional library.\n\nThe documentation for some parts of the internal API for creating decoder\nplugins (file format support) and sound output drivers can be generated using\nDoxygen (http://www.doxygen.org/).  Just run the doxygen command from the MOC\nsource directory.\n\nBefore you change anything it is a good idea to check for the latest development\nversion (check out from the Subversion repository is the best).  Your changes\nmight conflict with changes already made to the source or your feature might be\nalready implemented.  See also the TODO file as it is updated regularly and\ncontains quite detailed information on future plans.\n\nIf you need help, just contact MOC's Maintainer via e-mail.  And if you are\nplanning anything non-trivial it's a good idea to discuss your intentions\nwith the MOC Maintainer once you've clarified your ideas but before spending\ntoo much time implementing them; it will be more productive if your work fits\nwith MOC's future direction.\n\n--------------------------------------------------------------------------------\nWho Wrote It?  Where Can I Send Bug Reports, Questions or Comments?\n--------------------------------------------------------------------------------\n\n\t* Original author is Damian Pietras\n\t* Current maintainer is John Fitzgerald\n\t* For comments and questions see the official forum:\n\t              http://moc.daper.net/forum\n\t* Need to report a bug?  You can reach the maintainer(s) at:\n\t                 <mocmaint@daper.net>\n\n--------------------------------------------------------------------------------\n"
  },
  {
    "path": "README.md",
    "content": "# MOC: Music on Console\n\n  http://moc.daper.net\n\n![Screenshot](themes/transparent-background_screenshot_thumb.png?raw=true)\n\n\n\nWhat Is It?\n--------------------------------------------------------------------------------\n\nMOC (music on console) is a console audio player for Linux/Unix designed to be\npowerful and easy to use.\n\nThis is an unofficial mirror, with a few small aesthetic tweaks.  It syncs with \nthe subversion upstream every few weeks.\nMOC makes it easy to use multimedia keys on your keyboard, which is discussed below.  \n\nPrerequisites\n-------------\nOn Debian/Ubuntu systems, you minimally need the following packages:\n```Bash\nsudo apt-get install gcc autoconf libtool gettext libdb-dev libpopt-dev libncursesw5-dev\n```\nI recommend the following packages as well:\n```Bash\nsudo apt-get install libasound2-dev libcurl4-openssl-dev libogg-dev libvorbis-dev libflac-dev libopus-dev libid3tag0-dev libsndfile1-dev libfaad-dev libavcodec-dev libsamplerate0-dev librcc-dev\n```\n**Optional**: FFmpeg adds many, many more file formats, including AAC, Opus, MP4, and WMA. You may need to first add www.deb-multimedia.org to your apt-get sources.  Then get FFmpeg:\n```Bash\nsudo apt-get install libavformat-dev\n```\n\nCompilation\n-----------\n```Bash\nautoreconf -if\n./configure\nmake -j 2\nsudo make install\n```\n\nKeyboard Shortcuts\n------------------\nFor Xfce, go to `Settings -> Keyboard -> Application Shortcuts`, then add shortcuts with\ncommands like `mocp --next` and others listed in `mocp --help`.  I find that the most\nuseful keyboard shortcuts are for the following:\n\n* `mocp --toggle-pause` - Play/pause\n* `mocp --toggle shuffle` - Enable/disable shuffle\n* `mocp --next` - Skip to the next song\n* `mocp --previous` - Go to the previous song\n* `mocp --seek +5` - Jump 5 seconds forward\n* `mocp --seek -5` - Jump 5 seconds back\n\nIn Fluxbox you can add the following to your `.fluxbox/keys` file (after using `xev`\nto discover key numbers):\n\n```bash\n# Play/pause\n179 :Exec mocp --toggle-pause\n# Skip to next song\n225 :Exec mocp --next\n# Go to previous song\nMod1 225 :Exec mocp --previous\n# Move forward a few seconds: ALT + >\nMod1 60 :Exec mocp --seek +5\n# Move backward a few seconds: ALT + <\nMod1 59 :Exec mocp --seek -5\n# Go to MOCP tab (should be first tab)\n128 :Tab 1\n# Toggle shuffle\n152 :Exec mocp --toggle shuffle\n``` \n\nRemote Controls\n---------------\nIt's really easy to use a remote control with MOC.  You can use any remote that appears to your computer as a keyboard, like [this one](https://smile.amazon.com/dp/B01MSX306Z) or similar ones.  Then setup keyboard shortcuts as described above for each button you want to use to control MOC.\n\n\nOriginal Text\n-------------\nThe rest of the upstream README is as follows:\n\nYou just need to select a file from some directory using the menu similar to\nMidnight Commander, and MOC will start playing all files in this directory\nbeginning from the chosen file.  There is no need to create playlists as in\nother players.\n\nIf you want to combine some files from one or more directories in one playlist,\nyou can do this.  The playlist will be remembered between runs or you can save\nit as an m3u file to load it whenever you want.\n\nNeed the console where MOC is running for more important things?  Need to close\nthe X terminal emulator?  You don't have to stop playing - just press q and the\ninterface will be detached leaving the server running.  You can attach it later,\nor you can attach one interface in the console, and another in the X terminal\nemulator, no need to switch just to play another file.\n\nMOC plays smoothly, regardless of system or I/O load because it uses the output\nbuffer in a separate thread.  The transition between files is gapless, because\nthe next file to be played is precached while the current file is playing.\n\nSupported file formats are: MP3, Ogg Vorbis, FLAC, Musepack (mpc), Speex, Opus,\nWAVE, those supported by FFmpeg/LibAV (e.g., WMA, RealAudio, AAC, MP4), AIFF,\nAU, SVX, Sphere Nist WAV, IRCAM SF, Creative VOC, SID, wavpack, MIDI and\nmodplug.\n\nOther features:\n\n  - Simple mixer\n  - Color themes\n  - Menu searching (playlist or directory) like M-s in Midnight Commander\n  - The way MOC creates titles from tags is configurable\n  - Optional character set conversion for file tags using iconv()\n  - OSS, ALSA, SNDIO and JACK output\n  - User defined keys\n  - Cache for files' tags\n\n\nDocumentation and The MOC Forum\n--------------------------------------------------------------------------------\n\nThis file is only a brief description of MOC, for more information is\navailable on the home page (http://moc.daper.net/documentation).\n\nYou can also find a discussion forum on the MOC home page.\n\n\nWhat Software Is Required To Build It?\n--------------------------------------------------------------------------------\n\nTo build MOC from the distribution tarball you will need:\n\n  - A POSIX.1-2001 compatible UNIX system with POSIX threads\n    (e.g., Linux or OSX)\n  - A C compiler which is C99 capable and a C++ compiler (MOC is written\n    in C, but libtool and some decoder plugins require a C++ compiler)\n  - ncurses (probably already installed in your system)\n  - POPT (libpopt) (probably already installed in your system)\n  - Berkeley DB (libdb) version 4.1 (unless configured with --disable-cache)\n  - GnuPG (gpg) if you are going to verify the tarball (and you should)\n\nIf you are building from the SVN repository you will also need:\n\n  - Subversion or git-svn (to checkout the source directory tree)\n  - Autoconf version 2.64 and the associated Automake and Libtool\n\nYou should choose which of the following audio formats you wish to play and\nprovide the libraries needed to support them:\n\n  - AAC - libfaad2 version 2.7 (http://www.audiocoding.com/), and\n    libid3tag (http://www.underbit.com/products/mad/)\n  - FLAC - libFLAC version 1.1.3 (http://flac.sourceforge.net/)\n  - MIDI - libtimidity version 0.1 (http://timidity.sourceforge.net/)\n  - modplug - libmodplug version 0.7 (http://modplug-xmms.sourceforge.net/)\n  - MP3 - libmad with libid3tag (ftp://ftp.mars.org/pub/mpeg/)\n  - Musepack (mpc)\n    - libmpcdec (http://www.musepack.net/), and\n    - taglib version 1.3.1 (http://developer.kde.org/~wheeler/taglib.html)\n  - Ogg Vorbis\n    - libvorbis, libogg and libvorbisfile (all version 1.0) (http://www.xiph.org/ogg/), or\n    - libvorbisidec and libogg (both version 1.0) (http://svn.xiph.org/trunk/Tremor)\n  - SID - libsidplay2 version 2.1.1 and libsidutils version 1.0.4\n    (http://sidplay2.sourceforge.net/)\n  - Speex\n    - libspeex version 1.0 (http://www.speex.org/), and\n    - libogg version 1.0 (http://www.xiph.org/ogg/)\n  - WMA, RealAudio (.ra), MP4\n    - FFmpeg version 0.7 (http://www.ffmpeg.org/), or\n    - LibAV version 0.7 (http://www.libav.org/)\n  - WAVE, AU, AIFF, SVX, SPH, IRC, VOC - libsndfile version 1.0 (http://www.mega-nerd.com/libsndfile/)\n  - wavpack - libwavpack version 4.31 (http://www.wavpack.com/)\n\nFor interfacing to the sound sub-system, you will need libraries for one or\nmore of the following:\n\n  - ALSA - alsa-lib version 1.0.11 (http://www.alsa-project.org/)\n  - OSS - the OSS libraries (http://www.opensound.com/)\n  - BSD's SNDIO - SNDIO libraries\n  - JACK low-latency audio server - JACK version 0.4 (http://jackit.sourceforge.net/)\n\nFor network streams:\n\n  - libcurl version 7.15.1 (http://curl.haxx.se/)\n\nFor resampling (playing files with sample rate not supported by your\nhardware):\n\n  - libresamplerate version 0.1.2 (http://www.mega-nerd.com/SRC/)\n\nFor librcc (fixes encoding in broken mp3 tags):\n\n  - http://rusxmms.sourceforge.net\n\nNote that for Debian-based distributions, you will also require any '-dev'\nsuffixed versions of the packages above if building from source.\n\nThe versions given above are minimum versions and later versions should also\nwork.  However, MOC may not yet have caught up with the very latest changes\nto library interfaces and these may cause problems if they break backwards\ncompatibility.\n\n\nOn Which Systems Is MOC Running?\n--------------------------------------------------------------------------------\n\nMOC is developed and tested on GNU/Linux.\n\nMOC is now C99 and POSIX.1-2001 compliant and so should build and run on\nany system which has a C99 capable compiler and is POSIX.1-2001 compatible.\nHowever, there may still be cases where MOC breaks this compliance and any\nreports of such breakage are welcome.\n\nThere is no intention to support MOC on MS-Windows (so please don't ask).\n\n\nHow Do I Verify the Authenticity of the Tarball?\n--------------------------------------------------------------------------------\n\nIf you downloaded the official MOC distribution you should have the\nfollowing files:\n\n\tmoc-2.6-alpha2.tar.asc\n\tmoc-2.6-alpha2.tar.md5\n\tmoc-2.6-alpha2.tar.xz\n\nwould check the integrity of the download:\n\n\tmd5sum -c moc-2.6-alpha2.tar.md5\n\nand then verify the tarball thusly:\n\n\txzcat moc-2.6-alpha2.tar.xz | gpg --verify moc-2.6-alpha2.tar.asc -\n\nThe signature file (\\*.asc) was made against the uncompressed tarball,\nso if the tarball you have has been recompressed using a tool other\nthan XZ then uncompress it using the appropriate tool then verify that:\n\n\tgunzip moc-2.6-alpha2.tar.gz\n\tgpg --verify moc-2.6-alpha2.tar.asc\n\nIf the tool can output to stdout (and most can) then you can also verify\nverify it in a single step similar to the way in which the XZ tarball\nwas verified above.\n\nOf course, you'll also need the MOC Release Signing Key:\n\n\tgpg --recv-key 0x2885A7AA\n\nfor which the fingerprint is:\n\n\t5935 9B80 406D 9E73 E805  99BE F312 1E4F 2885 A7AA\n\n\nHow Do I Build and Install It?\n--------------------------------------------------------------------------------\n\nGeneric installation instruction is included in the INSTALL file.\n\nIn short, if you are building from an SVN checkout of MOC (but not if you\nare building from a downloaded tarball) then you will first need to run:\n\n\tautoreconf -if\n\nand then proceed as shown below for a tarball.  (If you are using the\ntarball but have applied additional patches then you may also need to run\nautoreconf.)\n\nTo build MOC from a downloaded tarball just type:\n\n\t./configure\n\tmake\n\nAnd as root:\n\n\tmake install\n\nUnder FreeBSD and NetBSD (and possibly other systems) it is necessary to\nrun the configure script this way:\n\n\t./configure LDFLAGS=-L/usr/local/lib CPPFLAGS=-I/usr/local/include\n\nIn addition to the standard configure options documented in the INSTALL\nfile, there are some MOC-specific options:\n\n\t--enable-cache=[yes|no]\n\n\t  Specifying 'no' will disable the tags cache support.  If your\n\t  intent is to remove the Berkeley DB dependancy (rather than\n\t  simply removing the on-disk cache) then you should also either\n\t  build MOC without RCC support or use a librcc built with BDB\n\t  disabled.\n\n\t--enable-debug=[yes|no|gdb]\n\n\t  Using 'gdb' will cause MOC to be built with options tailored to\n\t  use with GDB.  (Note that in release 2.6 this option will be\n\t  split into separate debugging and logging options.)\n\n\t--with-oss=[yes|no|DIR]\n\n\t  Where DIR is the location of the OSS include directory (and\n\t  defaults to '/usr/lib/oss').\n\n\t--with-vorbis=[yes|no|tremor]\n\n\t  Using 'tremor' will cause MOC to build against the integer-only\n\t  implementation of the Vorbis library (libvorbisidec).\n\nYou can install MOC into its own source directory tree and run it from there\nso you do not have to install it permanently on your system.  If you're just\nwanting to try it out or test some patches, then this is something you may\nwish to do:\n\n\t./configure --prefix=\"$PWD\" --without-timidity\n\tmake\n\tmake install\n\tbin/mocp -M .moc\n\n\nHow Do I Use It?\n--------------------------------------------------------------------------------\n\nRun program with the 'mocp' command.  The usage is simple; if you need help,\npress 'h' and/or read mocp manpage.  There is no complicated command line or\ncryptic commands.  Using MOC is as easy as using basic functions of Midnight\nCommander.\n\nYou can use a configuration file placed in ~/.moc/config, but it's not required.\nSee config.example provided with MOC.\n\n\nUsing Themes\n--------------------------------------------------------------------------------\n\nYes, there are themes, because people wanted them. :)\n\nThemes can change all colors and only colors.  An example theme file with a\nexhaustive description is included (themes/example_theme) and is the\ndefault MOC appearance.\n\nTheme files should be placed in ~/.moc/themes/ or $(datadir)/moc/themes/\n(e.g., /usr/local/share/moc/themes) directory, and can be selected with\nthe Theme configuration options or the -T command line option (see the\nmanpage and the example configuration file).\n\nFeel free to share the themes you have created.\n\n\nDefining Keys\n--------------------------------------------------------------------------------\n\nYou can redefine standard keys.  See the instructions in the keymap.example file.\n\n\nHow Do I Report A Problem?\n--------------------------------------------------------------------------------\n\nNot every release is extensively tested on every system, so the particular\nconfiguration of software, libraries, versions and hardware on your system\nmight expose a problem.\n\nIf you find any problems then you should search the MOC Forum for a solution;\nyour problem may not be unique.  If you do find an existing topic which\nmatches your problem but does not offer a solution, or the solution offered\ndoes not work for you and the topic appears still active, then please add your\nexperience to it; it may be that additional information you can provide will\ncontain the clue needed to resolve the problem.\n\nIf you don't find an answer there and you installed MOC from your Linux\ndistribution's repository then you should report it via your distribution's\nusual reporting channels in the first instance.  If the problem is ultimately\nidentified as actually being in MOC itself, it should then be reported to the\nMOC Maintainer (preferably by the distribution's MOC package maintainer).\n\nIf you built MOC from source yourself or you get no resolution from your\ndistribution then start a new topic on the MOC Forum for your problem or\ncontact the MOC Maintainer.\n\nBefore reporting a problem, you should first read this Forum post:\n\n   Linkname: How to Report Bugs Effectively\n        URL: http://moc.daper.net/node/1035\n\nand the essay it references:\n\n   Linkname: How to Report Bugs Effectively\n        URL: http://www.chiark.greenend.org.uk/~sgtatham/bugs.html\n\nThere are two things you must do if at all possible:\n\n1. Make sure you are using the current stable MOC release or, even better,\n   can reproduce it on the latest development release or SVN HEAD, and\n2. Make sure you include the version and revision information (which you\n   can obtain by running 'mocp --version').\n\nIf you do not do those two things (and don't offer a good explanation as to\nwhy you didn't) your problem report is likely to be ignored until such time\nas you do.\n\n\nHacking\n--------------------------------------------------------------------------------\n\nWant to modify MOC?  You're welcome to do so, and patch contributions are\nalso welcome.\n\nMOC is written in C, so you must at least know this language to make simple\nchanges.  It is multi-threaded program, but there are places where you don't\nneed to worry about that (the interface is only a single thread process).  It\nuses autoconf, automake and libtool chain to generate configuration/compilation\nstuff, so you must know how to use it, for example, if you need to link to an\nadditional library.\n\nThe documentation for some parts of the internal API for creating decoder\nplugins (file format support) and sound output drivers can be generated using\nDoxygen (http://www.doxygen.org/).  Just run the doxygen command from the MOC\nsource directory.\n\nBefore you change anything it is a good idea to check for the latest development\nversion (check out from the Subversion repository is the best).  Your changes\nmight conflict with changes already made to the source or your feature might be\nalready implemented.  See also the TODO file as it is updated regularly and\ncontains quite detailed information on future plans.\n\nIf you need help, just contact MOC's Maintainer via e-mail.  And if you are\nplanning anything non-trivial it's a good idea to discuss your intentions\nwith the MOC Maintainer once you've clarified your ideas but before spending\ntoo much time implementing them; it will be more productive if your work fits\nwith MOC's future direction.\n\n\nWho Wrote It?  Where Can I Send Bug Reports, Questions or Comments?\n--------------------------------------------------------------------------------\n\n- Original author is Damian Pietras\n- Current maintainer is John Fitzgerald\n- For comments and questions see the official forum: http://moc.daper.net/forum\n- Need to report a bug?  You can reach the maintainer(s) at: <mocmaint@daper.net>\n"
  },
  {
    "path": "README_equalizer",
    "content": "Preamble\n---\nThis document is meant to give you an overview on the idea of having a\nparametric equalizer for sound enhancement and how you can create your\nown presets.  Also the interaction with the equalizer in MOC is described.\n\nI would like to improve this document to make it more usable; so if you\nhave any comments and/or ideas feel free to contact me.\n\n- Hendrik Iben (hiben<at>tzi(dot)de)\n\n\nContent\n---\n0. Document History\n1. Motivation\n2. Usage\n3. Preset Format\n4. Creating Presets\n5. TODO\n6. References\n\n\n0. Document History\n---\n07.09.2008 - Initial version\n15.03.2011 - Reformatted\n\n\n1. Nuts and Bolts / Motivation for Implementing the Equalizer\n---\nThe equalizer is an implementation of a biquadratic peaking equalizer\nfilter looked up from the Audio EQ Cookbook[1].\n\nIt happens to be a parametric equalizer and this means that, different\nfrom other equalizer implementations, the number of bands* is not fixed.\nWhen I started the idea of implementing the equalizer I looked around\nin the source of other audio playback software and found that a lot of\nthem are recycling the code used by the famous XMMS[2] audio player.\nI also would have liked to recycle the code but I decided against it\nfor two reasons:\n\nThe first reason is that there is almost no documentation on the algorithm\nused.  Maybe the signal processing folks have fun finding out what makes\nthis thing work but I was totally lost.  So I decided that I wanted to\n*know* what I am doing if I do it.\n\nAs for the second reason, the code used by XMMS is totally optimized for\ninteger arithmetic.  There is no problem with this in general but I had\nthe goal of implementing something that was as accurate as I could and\nI wanted to use floating point arithmetic.\n\nSo I am no signals processing guy, but I have -- I think -- a solid\nunderstanding of the matter.  I sat down and started to read about\nequalizing, audio processing and signal theory in general.  After some\ntime I found a mathematical description and a C implementation of\nbiquadratic filters in the Audio Cookbook.  I made an implementation of\nthe XMMS equalizer and the biquadratic filter using Octave[3] to compare\nthe outcome of both filters.  I was a bit surprised how different filters\ncan be but in the end succeeded (?) in finding a quite good biquadratic\nfilter set that would produce results not unlike the XMMS equalizer.\n\nAlthough I did not use the XMMS-code I think that people will be more\nhappy to accept this equalizer if they can use their presets with it.\nThere is some conversion needed, but it's a straightforward process.\nI converted all presets provided by XMMS into presets for this mixer.\nThey should be available at [4].\n\n* A band is a chosen center frequency where a filter has most impact.\n  If you look at WinAmp / XMMS / Beep Media Player you will find that\n  they settled on a common set of 10 bands.\n\n\n2. Using the Equalizer\n---\nThe default keys for the equalizer are:\n\n'e' - Refresh equalizer\n'E' - Toggle equalizer (on/off)\n'k' - Select next preset\n'K' - Select previous preset\n\nEach of these actions results in a message displayed in the message area.\nThis message will be overridden by the next action.\n\n\n3. Preset Format\n---\nPresets for the equalizer are to be placed in a directory called 'eqsets'\nin MOC's home directory (e.g., $HOME/.moc/eqsets).  There is no convention\nfor the filename, but it will serve as the name in the selection process.\n\nFile format in pseudo EBNF:\n\n  EQSET\n  ((<CF> <BW> <AMP>)|(0 <PREAMP>))*\n\n  CF:     Center frequency (sane values are from ~20 to ~20000).\n  BW:     Bandwith in Octaves.  This defines how fast the bands\n          influence vanishes over the frequencies.\n  AMP:    Amplification factor (in dB) to apply to the band.\n  PREAMP: Specifies an amplification factor applied before equalizing.\n\nSo a valid equalizer set would be:\n\n  # this is a comment\n  EQSET\n  # amplify audio by 1.4dB\n  0 1.4\n  # damp frequencies at 100Hz by -4dB, filter bandwidth 1.5 octaves\n  100   1.5 -4\n  # amplify frequencies at 4000Hz by 2dB, filter bandwidth 1.5 octaves\n  4000  1.5 2\n\nThere is no order to stick to when specifying frequencies.\n\n\n4. Creating Your Own Presets\n---\nFor a start you should have a look at the converted presets[4].  The\nbandwidths used in the conversion have been extracted by taking a look\nat the filters signal response (implementation and analysis in Octave).\nI tried to do this as accurately as possible but I don't know if I made\na mistake.  They sound correct though... :-)\n\nYou might note that there is never a positive amplification factor in\nthe presets although there are in the original preset.  The reason for\nthis is that I used the maximum amplification in the preset as zero\namplification and adjusted the other values accordingly.\n\nIn general, when creating a preset get used to the following idea: Do not\namplify the frequencies you want but damp those that are of no interest.\nThis has the same effect but avoids clipping and this equalizer type seems\nto be very prone to clipping.  Also be very careful with pre-amplifying\nthe audio for the same reason.\n\nWith that said, the next confusing thing is the bandwidth definition.\nEvery band needs a defined bandwidth in octaves where the bandwidth\ndefines where the filter's effect has been reduced by 3dB*.  This means\nthat if you define a band at 1000Hz with a bandwidth of 1.5 octaves and\nan amplification of -10dB, at 353.6Hz** and at 2828.4Hz the amplification\nwill be reduced to -7dB.\n\nIf unsure, stay in between 1.0 and 2.0.  Just keep in mind that if two\nbands overlap you might get an undesired amplification.\n\nWhen designing presets, just save the preset and select it in MOC.  After\neach change press the refresh key (default 'e').  This will re-create the\nequalizer reflecting your changes.\n\nIf your preset is not found, have a look at the output of MOC's server\nthread.  Parsing errors are emitted there.\n\n*  3dB is commonly used for bandwidth.  -3dB equals about 70.7% of\n   original amplification.\n** 353.6 =~ 1000*(2^-1.5), 2828.4 =~ 1000*(2^1.5)\n\n\n5. TODO\n---\n- The equalizer is currently not optimized in any way.\n\n- It converts all sound data into floating point values to perform the\n  equalization and converts them back afterwards.  A better approach\n  would be either to provide integer algorithms for equalization or to\n  leave the audio data in floating point format.\n\n- There is no sorting for the presets; their order is defined by reading\n  the directory content.\n\n- Maybe it would be nice to add a name to the preset different from the\n  filename.\n\n\n6. References\n---\n[1] Cookbook formulae for audio EQ biquad filter coefficients\n    http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt\n[2] X Multimedia System\n    http://www.xmms.org/\n[3] GNU Octave\n    http://www.gnu.org/software/octave/\n[4] Converted WinAmp / XMMS Equalizer sets\n    http://www.informatik.uni-bremen.de/~hiben/moc/eqsets.tar.gz\n"
  },
  {
    "path": "THANKS",
    "content": "Thanks to all people who have helped us make MOC better, suggesting\nchanges or notifing about bugs:\n\nAlexis Ballier:\n\t* Adapt to FFmpeg's changed include directory layout.\n\nRastislav Barlik:\n\t* Fixed segfault on deleted equalizer file.\n\nDaniel T. Borelli:\n\t* Added support for key to switch on/off the display of percent played.\n\t* Added a configuration option to set the initial percent played state.\n\t* Fixed miscellaneous coding errors.\n\t* Assisted with testing.\n\t* Provided basis for fixing foreground mode unreaped children.\n\nMorten Grunnet Buhl:\n\t* Provided Yellow/Red theme.\n\nAndreas Cadhalpun:\n\t* Resolved the deprecated 'request_channels' puzzle.\n\nNuno Cardoso:\n\t* Options in a hash table.\n\t* Added -j command line option to jump to a given position.\n\nJosh Coalson:\n\t* Fixes for compilation with FLAC 1.1.3.\n\nBrent Cook:\n\t* Various OpenBSD-related patches.\n\nNiels Aan de Brugh:\n\t* Improved error detection for terminal height limits.\n\nJonathan Derque:\n\t* Support for GNU screen title.\n\nJoseph Dunn:\n\t* Bug reports.\n\t* He gave me access to his FreeBSD box that allowed me to fix a bug.\n\nDennis Felsing:\n\t* Fixed compilation of sidplay2.\n\nFilippo Giunchedi:\n\t* Added directories to the 'a' command.\n\nAlexey Gladkov:\n\t* Support for filenames and directory names recoding using librcc.\n\nTomasz Golinski:\n\t* Assisted greatly with testing and debugging.\n\t* Headed the effort to port MOC to OpenWRT.\n\t* Provided signficant information on DTS, AAC and other formats.\n\t* Also contributed much time in the refinement of feature ideas.\n\t* Provided 24-bit format conversion bug fixes.\n\t* Fixed volume control problem.\n\t* Provided check for valid output channels and formats.\n\nJuho Hämäläinen:\n\t* Added -Q (--format) FORMAT_STRING option to display file information.\n\nHendrik Iben:\n\t* Added TiMidity decoder for MIDI.\n\t* Added SidPlay2 decoder for SID.\n\t* Added Modplug decoder.\n\t* Added check for newer faad2-library (AAC).\n\t* Added software mixer.\n\t* Added parametric equalizer.\n\t* Merged parametric equalizer and mono-mixing code.\n\t* Fixed miscellaneous coding errors.\n\t* Fixed logging of deleted filenames.\n\t* Assisted with testing.\n\nDaniel Kalør:\n\t* Provided spelling fixes.\n\t* Fixed clearing chars when displaying file information.\n\t* Fixed field overflow when fast-forwarding yields very large bit rates.\n\t* Repositioned selected track to middle after scrolling off screen.\n\t* Fixed the symbol for \"kilo\", use \"k\" (lowercase).\n\t* Allowed seeking to beginning of a file in Vorbis/FLAC.\n\nKari Karvonen:\n\t* Suggested code for the AutoNext option.\n\nHasan Keler:\n\t* Assisted with testing.\n\t* Also assisted by commenting on feature ideas.\n\nMax Klinger:\n\t* Silenced compiler warnings on various platforms.\n\t* Migrated AAC decoder to later FAAD2 API.\n\t* Replaced GNU-specific getline() with read_line().\n\t* Resolved JACK deprecation warnings.\n\t* Prompted option to autostart JACK if the server isn't running.\n\t* Assisted with testing.\n\t* Also assisted by commenting on feature ideas.\n\nAdam Kowalski:\n\t* Many bug reports (also tests).\n\nFlorian Kriener:\n\t* Provided title building code.\n\t* Corrected many typos and fixed many bugs.\n\nVladimir Krylov:\n\t* Added 24-bit support to the OSS sound driver.\n\nMaciej Kubiak:\n\t* Suggestions and bug reports.\n\nGéraud Le Falher:\n\t* Fixed crash in lyrics code with overly long filenames.\n\t* Display lyrics saved in files together with music.\n\nJacek Lehmann:\n\t* Provided Green theme and fixes for a few terminals.\n\nTero Marttila:\n\t* Changed minimum screen height to 7 lines.\n\nGregory Maxwell:\n\t* Provided patch for off_t in io_* functions.\n\nAlex Merenstein:\n\t* Fixed theme menu cursor placement.\n\t* Fixed new attributes application during theme switching.\n\t* Assisted with debugging and testing.\n\nMarcin Michałowski:\n\t* Added default and grey colours, and made first nice theme.\n\nMartin Milata:\n\t* Resolved Clang Static Analyzer warnings.\n\t* Dead code removal.\n\t* Miscellaneous code fixes.\n\t* Fix segfault when using -k command line option.\n\t* Added -q option to queue files from command line.\n\t* Provided play queue feature.\n\t* Fixed race condition between two clients and playlist request servicing.\n\nJack Miller:\n\t* Added average bitrate field to the output of mocp -i.\n\t* Provided command line option to toggle/on/off playback options.\n\t* Provided command line option for setting the volume.\n\t* Added OnSongChange option which runs a command when song is changed.\n\nAlex Norman:\n\t* Added JACK output.\n\t* Assisted with reported JACK issues.\n\t* Added FastDir option.\n\t* Other improvements.\n\nSebastian Parborg:\n\t* Silenced compiler warnings on various platforms.\n\t* Fixed bug attempting to read from unopened OSS mixer.\n\t* Assisted with testing.\n\t* Also assisted by commenting on feature ideas.\n\nTed Phelps:\n\t* Fixed incorrect referencing of ALSA mixer channels.\n\nPetr Pisar:\n\t* Provided patch upon which the initial locale support was based.\n\nJonathan Poelen:\n\t* Removed redundant call to file_type().\n\nAlexander Polakov:\n\t* Fixed miscoloured frame when switching themes.\n\t* Provided SNDIO sound driver for OpenBSD systems.\n\t* Fixed call for bit rate after file open has failed.\n\t* Assisted with testing.\n\nElimar Riesebieter:\n\t* Tested on PPC (made to work on big endian architectures).\n\t* Builder of the official Debian package.\n\t* Described --seek option in the manpage.\n\t* Added JACK to '-R' option sound drivers on manpage.\n\nAlexandrov Sergey:\n\t* Added Wavpack decoder.\n\t* Fixed 8-, 24- and 32-bit decoding in Wavpack decoder.\n\nAleks Sherikov:\n\t* Added EnforceTagsEncoding, FileNamesIconv, and FileNamesIconv options.\n\nJoerg Sonnenberger:\n\t* Fixed using ncurses on NetBSD.\n\t* Fixed detecting curses if ncurses is not present.\n\nWim Speekenbrink:\n\t* Author of nightly_theme.\n\nDaniel Stenberg:\n\t* Fixed CURL timeout so internet streaming errors don't hang MOC.\n\nOndřej Svoboda:\n\t* Fixed a fatal error when opening an MP3 file.\n\t* Fixed a compilation warning in the FFmpeg plugin.\n\t* Spelling fixes.\n\t* Source files encoding fixes.\n\nKamil Tarkowski:\n\t* Provided 'back' command.\n\t* Some fixes and small improvements.\n\nReuben Thomas:\n\t* Fixed typos in documentation.\n\t* Fixed and simplify parameters substitution in --format command.\n\t* Don't run the server if the user doesn't really want to do that when\n\t  using few commands from command line like --info.\n\t* Reorganised code that parses command line options.\n\t* Allowed the use of FormatString tags in --format arguments.\n\nRichard Toohey:\n\t* Assisted with testing on OpenBSD.\n\nAntonio Trande:\n\t* Assisted with testing.\n\t* Also assisted by commenting on feature ideas.\n\t* Fedora's MOC package builder.\n\nMarc Tschiesche:\n\t* Provided highlighted playlist numbers and file times.\n\nZhiming Wang:\n\t* Assisted with testing on OSX.\n\nJenny Wong:\n\t* Provided minor memory corruption patch.\n\t* Introduced FFmpeg to Internet streams.\n\nMarien Zwart:\n\t* Assisted with testing.\n\n\"cbass\":\n\t* Fixed segfault when trying to play a file using FFmpeg.\n\t* Migrated to newer FFmpeg API.\n\n\"exc\":\n\t* Pointed to an ALSA optimisation with joined channels.\n\n\"firejox\":\n\t* Fixed screen upsets due to UTF-8 character handing.\n\n\"fluxid\":\n\t* Fixed incorrect setting for themes red channel value.\n\n\"GenghisKhan\":\n\t* Reported bugs and significantly helped debugging them.\n\t* Greatly assisted with debugging the ALSA stutter bug.\n\n\"meh\":\n\t* Provided code to prefer reading ID3 tags v2 over v1.\n\n\"scorched\":\n\t* Assisted with testing.\n\n\"thotypous\":\n\t* Provided code to allow use of the OSSv4 per-application mixer API.\n\n\"tokapix\":\n\t* Provided additional proving of the ALSA stutter bug fix.\n\n\"tyranix\":\n\t* Provided new command 'Y' to prune unreadable files from the playlist.\n\n\"vectis\":\n\t* Assisted with debugging the ALSA stutter bug.\n\n\"zaphod\":\n\t* Some strcpy() to strncpy() changes.\n\nThere are many people who have contributed in various ways to the\ndevelopment of MOC.  I hope I've listed all who deserve thanks, but if\nnot then I apologise and you should remind me so I can include you.\n"
  },
  {
    "path": "TODO",
    "content": "This file does not list all bugs which:\n\n- are known but rarely encountered,\n- are not visible to the user,\n- are programming errors detectable only with the aid of testing tools,\n- are benign, or\n- have been reported but not yet confirmed to be attributable to MOC.\n\nIt also does not contain many \"To Do\" items as these are often quite fluid\nand can evolve rapidly over time.\n\nThe MOC Maintainer does hold a master list containing bugs, feature requests,\nprogram change requests and future requirements, and once it is possible to\ngenerate this file from that list automatically it may be updated more\nfrequently so it is current and comprehensive.\n\n\nKnown Bugs:\n\n* Character set problems:\n  - Do we assume filesystem in UTF-8?  It's incorrect.\n  - id3tags v2.3 with UTF-16 are not properly handled by libid3tag, taglib\n    has no problems.  Need to use libtag here?\n  - Recognition of ID3 tags v1 is broken (example: small.mp3).  [node/234]\n  - Perhaps MOC can add support for the frame field_type to differentiate\n    between ID3_FIELD_TYPE_LATIN1 and ID3_FIELD_TYPE_STRING.  [node/234]\n  - Some national characters in file and directory names don't get displayed\n    correctly.\n* Program crashes:\n  - I have a problem with MOC 2.4.0 on FreeBSD 6.1-PRERELEASE.  When I build\n    MOC with musepack (libmpcdec) support MOC always core dumps on exit.\n    (This may have been fixed by other storage-related patches and will be\n    removed in MOC 2.7 if no one has confirmed it still exists.)\n  - Using DB_THREAD in combination with DB_CREATE and DB_PRIVATE seems to\n    cause a segfault on OpenWRT.  The circumvention is to configure MOC on\n    OpenWRT with '--disable-cache' or set the MOC configuration option\n    'TagsCacheSize' to zero.\n* Valgrind-detected memory problems:\n  - libasound: memory leaks\n  - libdb: uninitialised parameter storage\n  - libffmpeg: uninitialised storage in conditions and reads\n  - libgme: uninitialised storage in conditions and reads\n  - libmagic: uninitialised storage in reads\n  - librcc: uninitialised storage in conditions\n  - libspeex: uninitialised storage in conditions and reads\n  - The problems above occur in libraries but it needs to be determined\n    whether they somehow arise from MOC's misuse of them.\n  - There are many data race and mutex handling problems (particularly\n    around the time of file-to-file transitions), but these are not visible\n    to the users and difficult to solve without compromising gapless playback.\n* Directory and playlists:\n  - When sorting by file name (directories), put files beginning with a\n    non-alphanumeric character at the top.\n  - mocp -a playlist.pls should not sort added files.  [node/240]\n  - Client does not show changes when a file's tags are updated by another\n    program.\n  - Client continues to show file names after ReadTags is toggled when\n    MOC is started with 'ShowTime=yes' and 'ReadTags=no'.\n* Decoder problems:\n  - Seeking in some formats does not work in the FFmpeg decoder because of\n    the limitations of the FFmpeg libraries (and seeking is disabled for\n    them).  FLV also has seeking issues and is disabled but will be enabled\n    if SEEK_IN_DECODER is set in the FFmpeg decoder plug-in.  (This may be\n    parameterised in due course.)\n  - Some decoders do not currently pass the MD5 sum or sample count checks.\n  - The duration of some AAC audios is incorrectly reported.  This problem\n    arises from the limitations of the format, but MOC now makes much more\n    accurate guesses.\n\n\nTransitional Code Removal:\n\n* Remove TagLib version 1.5 requirement warning.\n* Remove Musepack libmpc (instead of libmpcdec) requirement warning.\n* Remove librcc raised requirement warning.\n\n\nPending 2.6 Changes:\n\n* Require at least TagLib version 1.5.\n* Require Musepack libmpc (instead of libmpcdec).\n* Require at least librcc version 0.2.10.\n\n\nPending 2.7 Changes:\n\n* Remove FreeBSD program crash bug if no one has confirmed it still exists.\n* Remove 'ALSAStutterDefeat' start-up warning.\n\n\nFuture Wishlist (items marked with $ are bigger changes requiring more time):\n\n* Move TODO to moc.daper.net/roadmap .\n* A string-valued variable \"TitleFormatString\", similar to \"FormatString\".\n  I'd suggest all the same %-escapes, plus:\n  %F -- expands to (the expansion of) the current FormatString\n  if there is a song playing or paused, and empty string if stopped.\n  %S -- expands to play/stop/pause.\n* Command line option to play from a specified file/first from the last\n  added files.  [node/286]\n* Multiple file selection to move many files at once up and down.  [node/288]\n* Review strcat()/strcpy()/sprintf() usage, change to\n  strncat()/strncpy()/snprintf() where possible.\n* More side menus: many directory/playlist views.\n* LADSPA/LV2\n* Editing the title of an item on the playlist (for Internet streams).\n*$ Media library: tree-like menu for artists and albums.\n* Seek by a % value (also using keys for 10%, 20%, etc).\n* Ability to play from a playlist made of items found after searching.\n* JACK: intelligent behaviour when we get disconnected from the server\n  - detect it, try connect and exit if it fails.\n* FastGo instead of FastDir: go to a directory, URL or a playlist.\n* Read tags for sndfile formats.\n*$ http://www.peercast.org/\n* Crossfade.\n* Command to see all information about a file with all tags, also all\n  information about an internet stream (from IceCast headers like icy-url,\n  icy-pub).\n*$ Lyrics downloaded from the Internet.\n* lirc.\n* Don't use PATH_MAX.\n* Seek forward using the content of the output buffer.\n*$ Locales.\n*$ Song ratings or something like Q in XMMS.\n* Configurable sorting.\n* Add a key for switching sort modes.\n*$ Equalizer like in Beep Media Player\n*$ Make equal volume level for every song like in http://volnorm.sourceforge.net\n*$ Replaygain.\n* Seek to arbitrary position (by typing a number).\n*$ ESD.\n*$ CUE-sheet.\n* Command line option to delete the currently played item from the playlist.\n* Scripting.\n* Some options can be changed at run-time using a menu.\n* posix_fadvise() with POSIX_FADV_WILLNEED instead of the input buffer thread\n  for files.\n* Recording played sound to a file.\n* Upgrade TiMidity decoder plug-in to TiMidity++.  This would add support\n  for soundfonts (.sf2) which are now more common.\n\n\nMaybe Never:\n\n* Report ignored server-specific options when the server is not being run.\n* Funny ASCII equalizer.\n* Mouse support.  [node/743]\n"
  },
  {
    "path": "alsa.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* Based on aplay copyright (c) by Jaroslav Kysela <perex@suse.cz> */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdlib.h>\n#include <inttypes.h>\n#include <alsa/asoundlib.h>\n#include <assert.h>\n#include <string.h>\n#include <errno.h>\n#include <unistd.h>\n\n#define DEBUG\n\n#define STRERROR_FN alsa_strerror\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"audio.h\"\n#include \"options.h\"\n#include \"log.h\"\n\n#define BUFFER_MAX_USEC\t300000\n\n/* Check that ALSA's and MOC's byte/sample/frame conversions agree. */\n#ifndef NDEBUG\n# define ALSA_CHECK(fn,val) \\\n\t do { \\\n\t\tlong v = val; \\\n\t\tssize_t ssz = snd_pcm_##fn (handle, 1); \\\n\t\tif (ssz < 0) \\\n\t\t\tdebug (\"CHECK: snd_pcm_%s() failed: %s\", #fn, alsa_strerror (ssz)); \\\n\t\telse if (v != ssz) \\\n\t\t\tdebug (\"CHECK: snd_pcm_%s() = %zd (vs %ld)\", #fn, ssz, v); \\\n\t} while (0)\n#else\n# define ALSA_CHECK(...) do {} while (0)\n#endif\n\nstatic snd_pcm_t *handle = NULL;\n\nstatic struct\n{\n\tunsigned int channels;\n\tunsigned int rate;\n\tsnd_pcm_format_t format;\n} params = { 0, 0, SND_PCM_FORMAT_UNKNOWN };\n\nstatic snd_pcm_uframes_t buffer_frames;\nstatic snd_pcm_uframes_t chunk_frames;\nstatic int chunk_bytes = -1;\nstatic char alsa_buf[512 * 1024];\nstatic int alsa_buf_fill = 0;\nstatic int bytes_per_frame;\nstatic int bytes_per_sample;\n\nstatic snd_mixer_t *mixer_handle = NULL;\nstatic snd_mixer_elem_t *mixer_elem1 = NULL;\nstatic snd_mixer_elem_t *mixer_elem2 = NULL;\nstatic snd_mixer_elem_t *mixer_elem_curr = NULL;\n\n/* Percentage volume setting for first and second mixer. */\nstatic int volume1 = -1;\nstatic int volume2 = -1;\n\n/* ALSA-provided error code to description function wrapper. */\nstatic inline char *alsa_strerror (int errnum)\n{\n\tchar *result;\n\n\tif (errnum < 0)\n\t\terrnum = -errnum;\n\n\tif (errnum < SND_ERROR_BEGIN)\n\t\tresult = xstrerror (errnum);\n\telse\n\t\tresult = xstrdup (snd_strerror (errnum));\n\n\treturn result;\n}\n\n/* Map ALSA's mask to MOC's format (and visa versa). */\nstatic const struct {\n\tsnd_pcm_format_t mask;\n\tlong format;\n} format_masks[] = {\n\t{SND_PCM_FORMAT_S8, SFMT_S8},\n\t{SND_PCM_FORMAT_U8, SFMT_U8},\n\t{SND_PCM_FORMAT_S16, SFMT_S16},\n\t{SND_PCM_FORMAT_U16, SFMT_U16},\n\t{SND_PCM_FORMAT_S32, SFMT_S32},\n\t{SND_PCM_FORMAT_U32, SFMT_U32}\n};\n\n/* Given an ALSA mask, return a MOC format or zero if unknown. */\nstatic inline long mask_to_format (const snd_pcm_format_mask_t *mask)\n{\n\tlong result = 0;\n\n\tfor (size_t ix = 0; ix < ARRAY_SIZE(format_masks); ix += 1) {\n\t\tif (snd_pcm_format_mask_test (mask, format_masks[ix].mask))\n\t\t\tresult |= format_masks[ix].format;\n\t}\n\n#if 0\n\tif (snd_pcm_format_mask_test (mask, SND_PCM_FORMAT_S24))\n\t\tresult |= SFMT_S32; /* conversion needed */\n#endif\n\n\treturn result;\n}\n\n/* Given a MOC format, return an ALSA mask.\n * Return SND_PCM_FORMAT_UNKNOWN if unknown. */\nstatic inline snd_pcm_format_t format_to_mask (long format)\n{\n\tsnd_pcm_format_t result = SND_PCM_FORMAT_UNKNOWN;\n\n\tfor (size_t ix = 0; ix < ARRAY_SIZE(format_masks); ix += 1) {\n\t\tif (format_masks[ix].format == format) {\n\t\t\tresult = format_masks[ix].mask;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n#ifndef NDEBUG\nstatic void alsa_log_cb (const char *unused1 ATTR_UNUSED,\n                         int unused2 ATTR_UNUSED,\n                         const char *unused3 ATTR_UNUSED,\n                         int unused4 ATTR_UNUSED, const char *fmt, ...)\n{\n\tchar *msg;\n\tva_list va;\n\n\tassert (fmt);\n\n\tva_start (va, fmt);\n\tmsg = format_msg_va (fmt, va);\n\tva_end (va);\n\n\tlogit (\"ALSA said: %s\", msg);\n\tfree (msg);\n}\n#endif\n\nstatic snd_pcm_hw_params_t *alsa_open_device (const char *device)\n{\n\tint rc;\n\tsnd_pcm_hw_params_t *result;\n\n\tassert (!handle);\n\n\trc = snd_pcm_open (&handle, device, SND_PCM_STREAM_PLAYBACK,\n\t                                    SND_PCM_NONBLOCK);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't open audio\", rc);\n\t\tgoto err1;\n\t}\n\n\trc = snd_pcm_hw_params_malloc (&result);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't allocate hardware parameters structure\", rc);\n\t\tgoto err2;\n\t}\n\n\trc = snd_pcm_hw_params_any (handle, result);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't initialize hardware parameters structure\", rc);\n\t\tgoto err3;\n\t}\n\n\tif (0) {\n\terr3:\n\t\tsnd_pcm_hw_params_free (result);\n\terr2:\n\t\tsnd_pcm_close (handle);\n\terr1:\n\t\tresult = NULL;\n\t\thandle = NULL;\n\t}\n\n\treturn result;\n}\n\n/* Fill caps with the device capabilities. Return 0 on error. */\nstatic int fill_capabilities (struct output_driver_caps *caps)\n{\n\tint result = 0;\n\tsnd_pcm_hw_params_t *hw_params;\n\n\tassert (!handle);\n\n\thw_params = alsa_open_device (options_get_str (\"ALSADevice\"));\n\tif (!hw_params)\n\t\treturn 0;\n\n\tdo {\n\t\tint rc;\n\t\tunsigned int val;\n\t\tsnd_pcm_format_mask_t *format_mask;\n\n\t\trc = snd_pcm_hw_params_get_channels_min (hw_params, &val);\n\t\tif (rc < 0) {\n\t\t\terror_errno (\"Can't get the minimum number of channels\", rc);\n\t\t\tbreak;\n\t\t}\n\t\tcaps->min_channels = val;\n\n\t\trc = snd_pcm_hw_params_get_channels_max (hw_params, &val);\n\t\tif (rc < 0) {\n\t\t\terror_errno (\"Can't get the maximum number of channels\", rc);\n\t\t\tbreak;\n\t\t}\n\t\tcaps->max_channels = val;\n\n\t\trc = snd_pcm_format_mask_malloc (&format_mask);\n\t\tif (rc < 0) {\n\t\t\terror_errno (\"Can't allocate format mask\", rc);\n\t\t\tbreak;\n\t\t}\n\t\tsnd_pcm_hw_params_get_format_mask (hw_params, format_mask);\n\t\tcaps->formats = mask_to_format (format_mask) | SFMT_NE;\n\t\tsnd_pcm_format_mask_free (format_mask);\n\n\t\tresult = 1;\n\t} while (0);\n\n\tsnd_pcm_hw_params_free (hw_params);\n\tsnd_pcm_close (handle);\n\thandle = NULL;\n\n\treturn result;\n}\n\nstatic void handle_mixer_events (snd_mixer_t *mixer_handle)\n{\n\tstruct pollfd *fds = NULL;\n\n\tassert (mixer_handle);\n\n\tdo {\n\t\tint rc, count;\n\n\t\tcount = snd_mixer_poll_descriptors_count (mixer_handle);\n\t\tif (count < 0) {\n\t\t\tlog_errno (\"snd_mixer_poll_descriptors_count() failed\", count);\n\t\t\tbreak;\n\t\t}\n\n\t\tfds = xcalloc (count, sizeof (struct pollfd));\n\n\t\trc = snd_mixer_poll_descriptors (mixer_handle, fds, count);\n\t\tif (rc < 0) {\n\t\t\tlog_errno (\"snd_mixer_poll_descriptors() failed\", rc);\n\t\t\tbreak;\n\t\t}\n\n\t\trc = poll (fds, count, 0);\n\t\tif (rc < 0) {\n\t\t\terror_errno (\"poll() failed\", errno);\n\t\t\tbreak;\n\t\t}\n\n\t\tif (rc == 0)\n\t\t\tbreak;\n\n\t\tdebug (\"Mixer event\");\n\n\t\trc = snd_mixer_handle_events (mixer_handle);\n\t\tif (rc < 0)\n\t\t\tlog_errno (\"snd_mixer_handle_events() failed\", rc);\n\t} while (0);\n\n\tfree (fds);\n}\n\nstatic int alsa_read_mixer_raw (snd_mixer_elem_t *elem)\n{\n\tint rc, nchannels = 0, volume = 0;\n\tbool joined;\n\tsnd_mixer_selem_channel_id_t chan_id;\n\n\tif (!mixer_handle)\n\t\treturn -1;\n\n\tassert (elem);\n\n\thandle_mixer_events (mixer_handle);\n\n\tjoined = snd_mixer_selem_has_playback_volume_joined (elem);\n\n\tfor (chan_id = 0; chan_id < SND_MIXER_SCHN_LAST; chan_id += 1) {\n\t\tif (snd_mixer_selem_has_playback_channel (elem, chan_id)) {\n\t\t\tlong vol;\n\n\t\t\tnchannels += 1;\n\t\t\trc = snd_mixer_selem_get_playback_volume (elem, chan_id, &vol);\n\t\t\tif (rc < 0) {\n\t\t\t\terror_errno (\"Can't read mixer\", rc);\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\tassert (RANGE(0, vol, 100));\n\n#if 0\n\t\t\t{\n\t\t\t\tstatic int prev_vol[SND_MIXER_SCHN_LAST] = {0};\n\n\t\t\t\tif (vol != prev_vol[chan_id]) {\n\t\t\t\t\tprev_vol[chan_id] = vol;\n\t\t\t\t\tdebug (\"Vol %d: %ld\", chan_id, vol);\n\t\t\t\t}\n\t\t\t}\n#endif\n\n\t\t\tvolume += vol;\n\t\t}\n\n\t\tif (joined)\n\t\t\tbreak;\n\t}\n\n\tif (nchannels == 0) {\n\t\tlogit (\"Mixer has no channels\");\n\t\treturn -1;\n\t}\n\n\tvolume /= nchannels;\n\n\treturn volume;\n}\n\nstatic snd_mixer_elem_t *alsa_init_mixer_channel (const char *name)\n{\n\tsnd_mixer_selem_id_t *sid;\n\tsnd_mixer_elem_t *result = NULL;\n\n\tassert (mixer_handle);\n\n\tsnd_mixer_selem_id_malloc (&sid);\n\tsnd_mixer_selem_id_set_index (sid, 0);\n\tsnd_mixer_selem_id_set_name (sid, name);\n\n\tdo {\n\t\tsnd_mixer_elem_t *elem = NULL;\n\n\t\telem = snd_mixer_find_selem (mixer_handle, sid);\n\t\tif (!elem) {\n\t\t\terror (\"Can't find mixer %s\", name);\n\t\t\tbreak;\n\t\t}\n\n\t\tif (!snd_mixer_selem_has_playback_volume (elem)) {\n\t\t\terror (\"Mixer device has no playback volume (%s).\", name);\n\t\t\tbreak;\n\t\t}\n\n\t\tif (snd_mixer_selem_set_playback_volume_range (elem, 0, 100) < 0) {\n\t\t\terror (\"Cannot set playback volume range (%s).\", name);\n\t\t\tbreak;\n\t\t}\n\n\t\tlogit (\"Opened mixer (%s)\", name);\n\t\tresult = elem;\n\t} while (0);\n\n\tsnd_mixer_selem_id_free (sid);\n\n\treturn result;\n}\n\nstatic void alsa_close_mixer ()\n{\n\tif (mixer_handle) {\n\t\tint rc;\n\n\t\trc = snd_mixer_close (mixer_handle);\n\t\tif (rc < 0)\n\t\t\tlog_errno (\"Can't close mixer\", rc);\n\n\t\tmixer_handle = NULL;\n\t}\n}\n\nstatic void alsa_open_mixer (const char *device)\n{\n\tint rc;\n\n\tassert (!mixer_handle);\n\n\trc = snd_mixer_open (&mixer_handle, 0);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't open ALSA mixer\", rc);\n\t\tgoto err;\n\t}\n\n\trc = snd_mixer_attach (mixer_handle, device);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't attach mixer\", rc);\n\t\tgoto err;\n\t}\n\n\trc = snd_mixer_selem_register (mixer_handle, NULL, NULL);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't register mixer\", rc);\n\t\tgoto err;\n\t}\n\n\trc = snd_mixer_load (mixer_handle);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't load mixer\", rc);\n\t\tgoto err;\n\t}\n\n\tif (0) {\n\terr:\n\t\talsa_close_mixer ();\n\t}\n}\n\nstatic void alsa_set_current_mixer ()\n{\n\tint vol;\n\n\tif (mixer_elem1 && (vol = alsa_read_mixer_raw (mixer_elem1)) != -1) {\n\t\tassert (RANGE(0, vol, 100));\n\t\tvolume1 = vol;\n\t}\n\telse {\n\t\tmixer_elem1 = NULL;\n\t\tmixer_elem_curr = mixer_elem2;\n\t}\n\n\tif (mixer_elem2 && (vol = alsa_read_mixer_raw (mixer_elem2)) != -1) {\n\t\tassert (RANGE(0, vol, 100));\n\t\tvolume2 = vol;\n\t}\n\telse {\n\t\tmixer_elem2 = NULL;\n\t\tmixer_elem_curr = mixer_elem1;\n\t}\n}\n\nstatic void alsa_shutdown ()\n{\n\talsa_close_mixer ();\n\n#ifndef NDEBUG\n\tsnd_lib_error_set_handler (NULL);\n#endif\n}\n\nstatic int alsa_init (struct output_driver_caps *caps)\n{\n\tint result = 0;\n\tconst char *device;\n\n\tassert (!mixer_handle);\n\n\tdevice = options_get_str (\"ALSADevice\");\n\tlogit (\"Initialising ALSA device: %s\", device);\n\n#ifndef NDEBUG\n\tsnd_lib_error_set_handler (alsa_log_cb);\n#endif\n\n\talsa_open_mixer (device);\n\n\tif (mixer_handle) {\n\t\tmixer_elem1 = alsa_init_mixer_channel (options_get_str (\"ALSAMixer1\"));\n\t\tmixer_elem2 = alsa_init_mixer_channel (options_get_str (\"ALSAMixer2\"));\n\t}\n\n\tmixer_elem_curr = mixer_elem1 ? mixer_elem1 : mixer_elem2;\n\n\tif (mixer_elem_curr)\n\t\talsa_set_current_mixer ();\n\n\tif (!mixer_elem_curr)\n\t\tgoto err;\n\n\tresult = fill_capabilities (caps);\n\tif (result == 0)\n\t\tgoto err;\n\n\tif (sizeof (long) < 8 && options_was_defaulted (\"ALSAStutterDefeat\")) {\n\t\tfprintf (stderr,\n\t\t         \"\\n\"\n\t\t         \"Warning: Your system may be vulnerable to stuttering audio.\\n\"\n\t\t         \"         You should read the example configuration file comments\\n\"\n\t\t         \"         for the 'ALSAStutterDefeat' option and set it accordingly.\\n\"\n\t\t         \"         Setting the option will remove this warning.\\n\"\n\t\t         \"\\n\");\n\t\txsleep (5, 1);\n\t}\n\n\tif (0) {\n\terr:\n\t\talsa_shutdown ();\n\t}\n\n\treturn result;\n}\n\nstatic int alsa_open (struct sound_params *sound_params)\n{\n\tint rc, result = 0;\n\tunsigned int period_time, buffer_time;\n\tchar fmt_name[128];\n\tconst char *device;\n\tsnd_pcm_hw_params_t *hw_params;\n\n\tassert (!handle);\n\n\tparams.format = format_to_mask (sound_params->fmt & SFMT_MASK_FORMAT);\n\tif (params.format == SND_PCM_FORMAT_UNKNOWN) {\n\t\terror (\"Unknown sample format: %s\",\n\t\t        sfmt_str (sound_params->fmt, fmt_name, sizeof (fmt_name)));\n\t\treturn 0;\n\t}\n\n\tdevice = options_get_str (\"ALSADevice\");\n\tlogit (\"Opening ALSA device: %s\", device);\n\n\thw_params = alsa_open_device (device);\n\tif (!hw_params)\n\t\treturn 0;\n\n\trc = snd_pcm_hw_params_set_access (handle, hw_params,\n\t                                   SND_PCM_ACCESS_RW_INTERLEAVED);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't set ALSA access type\", rc);\n\t\tgoto err;\n\t}\n\n\trc = snd_pcm_hw_params_set_format (handle, hw_params, params.format);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't set sample format\", rc);\n\t\tgoto err;\n\t}\n\n\tbytes_per_sample = sfmt_Bps (sound_params->fmt);\n\tlogit (\"Set sample width: %d bytes\", bytes_per_sample);\n\n\tif (options_get_bool (\"ALSAStutterDefeat\")) {\n\t\trc = snd_pcm_hw_params_set_rate_resample (handle, hw_params, 0);\n\t\tif (rc == 0)\n\t\t\tlogit (\"ALSA resampling disabled\");\n\t\telse\n\t\t\tlog_errno (\"Unable to disable ALSA resampling\", rc);\n\t}\n\n\tparams.rate = sound_params->rate;\n\trc = snd_pcm_hw_params_set_rate_near (handle, hw_params, &params.rate, 0);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't set sample rate\", rc);\n\t\tgoto err;\n\t}\n\n\tlogit (\"Set rate: %uHz\", params.rate);\n\n\trc = snd_pcm_hw_params_set_channels (handle, hw_params,\n\t                                     sound_params->channels);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't set number of channels\", rc);\n\t\tgoto err;\n\t}\n\n\tlogit (\"Set channels: %d\", sound_params->channels);\n\n\trc = snd_pcm_hw_params_get_buffer_time_max (hw_params, &buffer_time, 0);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't get maximum buffer time\", rc);\n\t\tgoto err;\n\t}\n\n\tbuffer_time = MIN(buffer_time, BUFFER_MAX_USEC);\n\tperiod_time = buffer_time / 4;\n\n\trc = snd_pcm_hw_params_set_period_time_near (handle, hw_params,\n\t                                             &period_time, 0);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't set period time\", rc);\n\t\tgoto err;\n\t}\n\n\trc = snd_pcm_hw_params_set_buffer_time_near (handle, hw_params,\n\t                                             &buffer_time, 0);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't set buffer time\", rc);\n\t\tgoto err;\n\t}\n\n\trc = snd_pcm_hw_params (handle, hw_params);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't set audio parameters\", rc);\n\t\tgoto err;\n\t}\n\n\tsnd_pcm_hw_params_get_period_size (hw_params, &chunk_frames, 0);\n\tdebug (\"Chunk size: %lu frames\", chunk_frames);\n\n\tsnd_pcm_hw_params_get_buffer_size (hw_params, &buffer_frames);\n\tdebug (\"Buffer size: %lu frames\", buffer_frames);\n\tdebug (\"Buffer time: %\"PRIu64\"us\",\n\t        (uint64_t) buffer_frames * UINT64_C(1000000) / params.rate);\n\n\tbytes_per_frame = sound_params->channels * bytes_per_sample;\n\tdebug (\"Frame size: %d bytes\", bytes_per_frame);\n\n\tchunk_bytes = chunk_frames * bytes_per_frame;\n\n\tif (chunk_frames == buffer_frames) {\n\t\terror (\"Can't use period equal to buffer size (%lu == %lu)\",\n\t\t\t\tchunk_frames, buffer_frames);\n\t\tgoto err;\n\t}\n\n\trc = snd_pcm_prepare (handle);\n\tif (rc < 0) {\n\t\terror_errno (\"Can't prepare audio interface for use\", rc);\n\t\tgoto err;\n\t}\n\n\tALSA_CHECK (samples_to_bytes, bytes_per_sample);\n\tALSA_CHECK (frames_to_bytes, bytes_per_frame);\n\n\tlogit (\"ALSA device opened\");\n\n\tparams.channels = sound_params->channels;\n\talsa_buf_fill = 0;\n\tresult = 1;\n\nerr:\n\tsnd_pcm_hw_params_free (hw_params);\n\n\treturn result;\n}\n\n/* Play from alsa_buf as many chunks as possible. Move the remaining data\n * to the beginning of the buffer. Return the number of bytes written\n * or -1 on error. */\nstatic int play_buf_chunks ()\n{\n\tint written = 0;\n\tbool zero_logged = false;\n\n\twhile (alsa_buf_fill >= chunk_bytes) {\n\t\tint rc;\n\n\t\trc = snd_pcm_writei (handle, alsa_buf + written, chunk_frames);\n\n\t\tif (rc == 0) {\n\t\t\tif (!zero_logged) {\n\t\t\t\tdebug (\"Played 0 bytes\");\n\t\t\t\tzero_logged = true;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tzero_logged = false;\n\n\t\tif (rc > 0) {\n\t\t\tint written_bytes = rc * bytes_per_frame;\n\n\t\t\twritten += written_bytes;\n\t\t\talsa_buf_fill -= written_bytes;\n\n\t\t\tdebug (\"Played %d bytes\", written_bytes);\n\t\t\tcontinue;\n\t\t}\n\n\t\trc = snd_pcm_recover (handle, rc, 0);\n\n\t\tswitch (rc) {\n\t\tcase 0:\n\t\t\tbreak;\n\t\tcase -EAGAIN:\n\t\t\tif (snd_pcm_wait (handle, 500) < 0)\n\t\t\t\tlogit (\"snd_pcm_wait() failed\");\n\t\t\tbreak;\n\t\tdefault:\n\t\t\terror_errno (\"Can't play\", rc);\n\t\t\treturn -1;\n\t\t}\n\t}\n\n\tdebug (\"%d bytes remain in alsa_buf\", alsa_buf_fill);\n\tmemmove (alsa_buf, alsa_buf + written, alsa_buf_fill);\n\n\treturn written;\n}\n\nstatic void alsa_close ()\n{\n\tsnd_pcm_sframes_t delay;\n\n\tassert (handle != NULL);\n\n\t/* play what remained in the buffer */\n\tif (alsa_buf_fill > 0) {\n\t\tunsigned int samples_required;\n\n\t\tassert (alsa_buf_fill < chunk_bytes);\n\n\t\tsamples_required = (chunk_bytes - alsa_buf_fill) / bytes_per_sample;\n\t\tsnd_pcm_format_set_silence (params.format, alsa_buf + alsa_buf_fill,\n\t\t\t\t\t\t\t\t\tsamples_required);\n\n\t\talsa_buf_fill = chunk_bytes;\n\t\tplay_buf_chunks ();\n\t}\n\n\t/* Wait for ALSA buffers to empty.\n\t * Do not be tempted to use snd_pcm_nonblock() and snd_pcm_drain()\n\t * here; there are two bugs in ALSA which make it a bad idea (see\n\t * the SVN commit log for r2550).  Instead we sleep for the duration\n\t * of the still unplayed samples. */\n\tif (snd_pcm_delay (handle, &delay) == 0 && delay > 0)\n\t\txsleep (delay, params.rate);\n\tsnd_pcm_close (handle);\n\tlogit (\"ALSA device closed\");\n\n\tparams.format = 0;\n\tparams.rate = 0;\n\tparams.channels = 0;\n\tbuffer_frames = 0;\n\tchunk_frames = 0;\n\tchunk_bytes = -1;\n\thandle = NULL;\n}\n\nstatic int alsa_play (const char *buff, const size_t size)\n{\n\tint to_write = size;\n\tint buf_pos = 0;\n\n\tassert (chunk_bytes > 0);\n\n\tdebug (\"Got %zu bytes to play\", size);\n\n\twhile (to_write) {\n\t\tint to_copy;\n\n\t\tto_copy = MIN(to_write, ssizeof(alsa_buf) - alsa_buf_fill);\n\t\tmemcpy (alsa_buf + alsa_buf_fill, buff + buf_pos, to_copy);\n\t\tto_write -= to_copy;\n\t\tbuf_pos += to_copy;\n\t\talsa_buf_fill += to_copy;\n\n\t\tdebug (\"Copied %d bytes to alsa_buf (now filled with %d bytes)\",\n\t\t\t\tto_copy, alsa_buf_fill);\n\n\t\tif (play_buf_chunks() < 0)\n\t\t\treturn -1;\n\t}\n\n\tdebug (\"Played everything\");\n\n\treturn size;\n}\n\nstatic int alsa_read_mixer ()\n{\n\tint actual_vol, *vol;\n\n\tactual_vol = alsa_read_mixer_raw (mixer_elem_curr);\n\n\tassert (RANGE(0, actual_vol, 100));\n\n\tif (mixer_elem_curr == mixer_elem1)\n\t\tvol = &volume1;\n\telse\n\t\tvol = &volume2;\n\n\tif (*vol != actual_vol) {\n\t\t*vol = actual_vol;\n\t\tlogit (\"Mixer volume has changed since we last read it.\");\n\t}\n\n\treturn actual_vol;\n}\n\nstatic void alsa_set_mixer (int vol)\n{\n\tint rc;\n\n\tassert (RANGE(0, vol, 100));\n\n\tif (!mixer_handle)\n\t\treturn;\n\n\tif (mixer_elem_curr == mixer_elem1)\n\t\tvolume1 = vol;\n\telse\n\t\tvolume2 = vol;\n\n\tdebug (\"Setting vol to %d\", vol);\n\n\trc = snd_mixer_selem_set_playback_volume_all (mixer_elem_curr, vol);\n\tif (rc < 0)\n\t\terror_errno (\"Can't set mixer\", rc);\n}\n\nstatic int alsa_get_buff_fill ()\n{\n\tint result = 0;\n\n\tdo {\n\t\tint rc;\n\t\tsnd_pcm_sframes_t delay;\n\n\t\tif (!handle)\n\t\t\tbreak;\n\n\t\trc = snd_pcm_delay (handle, &delay);\n\t\tif (rc < 0) {\n\t\t\tlog_errno (\"snd_pcm_delay() failed\", rc);\n\t\t\tbreak;\n\t\t}\n\n\t\t/* delay can be negative if an underrun occurs */\n\t\tresult = MAX(delay, 0) * bytes_per_frame;\n\t} while (0);\n\n\treturn result;\n}\n\nstatic int alsa_reset ()\n{\n\tint result = 0;\n\n\tdo {\n\t\tint rc;\n\n\t\tif (!handle) {\n\t\t\tlogit (\"alsa_reset() when the device is not opened.\");\n\t\t\tbreak;\n\t\t}\n\n\t\trc = snd_pcm_drop (handle);\n\t\tif (rc < 0) {\n\t\t\terror_errno (\"Can't reset the device\", rc);\n\t\t\tbreak;\n\t\t}\n\n\t\trc = snd_pcm_prepare (handle);\n\t\tif (rc < 0) {\n\t\t\terror_errno (\"Can't prepare after reset\", rc);\n\t\t\tbreak;\n\t\t}\n\n\t\talsa_buf_fill = 0;\n\t\tresult = 1;\n\t} while (0);\n\n\treturn result;\n}\n\nstatic int alsa_get_rate ()\n{\n\treturn params.rate;\n}\n\nstatic void alsa_toggle_mixer_channel ()\n{\n\tif (mixer_elem_curr == mixer_elem1 && mixer_elem2)\n\t\tmixer_elem_curr = mixer_elem2;\n\telse if (mixer_elem1)\n\t\tmixer_elem_curr = mixer_elem1;\n}\n\nstatic char *alsa_get_mixer_channel_name ()\n{\n\tchar *result;\n\n\tif (mixer_elem_curr == mixer_elem1)\n\t\tresult = xstrdup (options_get_str (\"ALSAMixer1\"));\n\telse\n\t\tresult = xstrdup (options_get_str (\"ALSAMixer2\"));\n\n\treturn result;\n}\n\nvoid alsa_funcs (struct hw_funcs *funcs)\n{\n\tfuncs->init = alsa_init;\n\tfuncs->shutdown = alsa_shutdown;\n\tfuncs->open = alsa_open;\n\tfuncs->close = alsa_close;\n\tfuncs->play = alsa_play;\n\tfuncs->read_mixer = alsa_read_mixer;\n\tfuncs->set_mixer = alsa_set_mixer;\n\tfuncs->get_buff_fill = alsa_get_buff_fill;\n\tfuncs->reset = alsa_reset;\n\tfuncs->get_rate = alsa_get_rate;\n\tfuncs->toggle_mixer_channel = alsa_toggle_mixer_channel;\n\tfuncs->get_mixer_channel_name = alsa_get_mixer_channel_name;\n}\n"
  },
  {
    "path": "alsa.h",
    "content": "#ifndef ALSA_H\n#define ALSA_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid alsa_funcs (struct hw_funcs *funcs);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "audio.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004-2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * Contributors:\n *  - Kamil Tarkowski <kamilt@interia.pl> - \"previous\" request\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <pthread.h>\n#include <string.h>\n#include <strings.h>\n#include <errno.h>\n#include <assert.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"decoder.h\"\n#include \"playlist.h\"\n#include \"log.h\"\n#include \"lists.h\"\n\n#ifdef HAVE_OSS\n# include \"oss.h\"\n#endif\n#ifdef HAVE_SNDIO\n# include \"sndio_out.h\"\n#endif\n#ifdef HAVE_ALSA\n# include \"alsa.h\"\n#endif\n#ifndef NDEBUG\n# include \"null_out.h\"\n#endif\n#ifdef HAVE_JACK\n# include \"jack.h\"\n#endif\n\n#include \"softmixer.h\"\n#include \"equalizer.h\"\n\n#include \"out_buf.h\"\n#include \"protocol.h\"\n#include \"options.h\"\n#include \"player.h\"\n#include \"audio.h\"\n#include \"files.h\"\n#include \"io.h\"\n#include \"audio_conversion.h\"\n\nstatic pthread_t playing_thread = 0;  /* tid of play thread */\nstatic int play_thread_running = 0;\n\n/* currently played file */\nstatic int curr_playing = -1;\n/* file we played before playing songs from queue */\nstatic char *before_queue_fname = NULL;\nstatic char *curr_playing_fname = NULL;\n/* This flag is set 1 if audio_play() was called with nonempty queue,\n * so we know that when the queue is empty, we should play the regular\n * playlist from the beginning. */\nstatic int started_playing_in_queue = 0;\nstatic pthread_mutex_t curr_playing_mtx = PTHREAD_MUTEX_INITIALIZER;\n\nstatic struct out_buf *out_buf;\nstatic struct hw_funcs hw;\nstatic struct output_driver_caps hw_caps; /* capabilities of the output\n\t\t\t\t\t     driver */\n\n/* Player state. */\nstatic int state = STATE_STOP;\nstatic int prev_state = STATE_STOP;\n\n/* requests for playing thread */\nstatic int stop_playing = 0;\nstatic int play_next = 0;\nstatic int play_prev = 0;\nstatic pthread_mutex_t request_mtx = PTHREAD_MUTEX_INITIALIZER;\n\n/* Playlists. */\nstatic struct plist playlist;\nstatic struct plist shuffled_plist;\nstatic struct plist queue;\nstatic struct plist *curr_plist; /* currently used playlist */\nstatic pthread_mutex_t plist_mtx = PTHREAD_MUTEX_INITIALIZER;\n\n/* Is the audio device opened? */\nstatic int audio_opened = 0;\n\n/* Current sound parameters (with which the device is opened). */\nstatic struct sound_params driver_sound_params = { 0, 0, 0 };\n\n/* Sound parameters requested by the decoder. */\nstatic struct sound_params req_sound_params = { 0, 0, 0 };\n\nstatic struct audio_conversion sound_conv;\nstatic int need_audio_conversion = 0;\n\n/* URL of the last played stream. Used to fake pause/unpause of internet\n * streams. Protected by curr_playing_mtx. */\nstatic char *last_stream_url = NULL;\n\nstatic int current_mixer = 0;\n\n/* Check if the two sample rates don't differ so much that we can't play. */\n#define sample_rate_compat(sound, device) ((device) * 1.05 >= sound \\\n\t\t&& (device) * 0.95 <= sound)\n\n/* Make a human readable description of the sound sample format(s).\n * Put the description in msg which is of size buf_size.\n * Return msg. */\nchar *sfmt_str (const long format, char *msg, const size_t buf_size)\n{\n\tassert (sound_format_ok(format));\n\n\tassert (buf_size > 0);\n\tmsg[0] = 0;\n\n\tif (format & SFMT_S8)\n\t\tstrncat (msg, \", 8-bit signed\", buf_size - strlen(msg) - 1);\n\tif (format & SFMT_U8)\n\t\tstrncat (msg, \", 8-bit unsigned\", buf_size - strlen(msg) - 1);\n\tif (format & SFMT_S16)\n\t\tstrncat (msg, \", 16-bit signed\", buf_size - strlen(msg) - 1);\n\tif (format & SFMT_U16)\n\t\tstrncat (msg, \", 16-bit unsigned\", buf_size - strlen(msg) - 1);\n\tif (format & SFMT_S32)\n\t\tstrncat (msg, \", 24-bit signed (as 32-bit samples)\",\n\t\t\t\tbuf_size - strlen(msg) - 1);\n\tif (format & SFMT_U32)\n\t\tstrncat (msg, \", 24-bit unsigned (as 32-bit samples)\",\n\t\t\t\tbuf_size - strlen(msg) - 1);\n\tif (format & SFMT_FLOAT)\n\t\tstrncat (msg, \", float\",\n\t\t\t\tbuf_size - strlen(msg) - 1);\n\n\tif (format & SFMT_LE)\n\t\tstrncat (msg, \" little-endian\", buf_size - strlen(msg) - 1);\n\telse if (format & SFMT_BE)\n\t\tstrncat (msg, \" big-endian\", buf_size - strlen(msg) - 1);\n\tif (format & SFMT_NE)\n\t\tstrncat (msg, \" (native)\", buf_size - strlen(msg) - 1);\n\n\t/* skip first \", \" */\n\tif (msg[0])\n\t\tmemmove (msg, msg + 2, strlen(msg) + 1);\n\n\treturn msg;\n}\n\n/* Return != 0 if fmt1 and fmt2 have the same sample width. */\nint sfmt_same_bps (const long fmt1, const long fmt2)\n{\n\tif (fmt1 & (SFMT_S8 | SFMT_U8)\n\t\t\t&& fmt2 & (SFMT_S8 | SFMT_U8))\n\t\treturn 1;\n\tif (fmt1 & (SFMT_S16 | SFMT_U16)\n\t\t\t&& fmt2 & (SFMT_S16 | SFMT_U16))\n\t\treturn 1;\n\tif (fmt1 & (SFMT_S32 | SFMT_U32)\n\t\t\t&& fmt2 & (SFMT_S32 | SFMT_U32))\n\t\treturn 1;\n\tif (fmt1 & fmt2 & SFMT_FLOAT)\n\t\treturn 1;\n\n\treturn 0;\n}\n\n/* Return the best matching sample format for the requested format and\n * available format mask. */\nstatic long sfmt_best_matching (const long formats_with_endian,\n\t\tconst long req_with_endian)\n{\n\tlong formats = formats_with_endian & SFMT_MASK_FORMAT;\n\tlong req = req_with_endian & SFMT_MASK_FORMAT;\n\tlong best = 0;\n\n\tchar fmt_name1[SFMT_STR_MAX] DEBUG_ONLY;\n\tchar fmt_name2[SFMT_STR_MAX] DEBUG_ONLY;\n\n\tif (formats & req)\n\t\tbest = req;\n\telse if (req == SFMT_S8 || req == SFMT_U8) {\n\t\tif (formats & SFMT_S8)\n\t\t\tbest = SFMT_S8;\n\t\telse if (formats & SFMT_U8)\n\t\t\tbest = SFMT_U8;\n\t\telse if (formats & SFMT_S16)\n\t\t\tbest = SFMT_S16;\n\t\telse if (formats & SFMT_U16)\n\t\t\tbest = SFMT_U16;\n\t\telse if (formats & SFMT_S32)\n\t\t\tbest = SFMT_S32;\n\t\telse if (formats & SFMT_U32)\n\t\t\tbest = SFMT_U32;\n\t\telse if (formats & SFMT_FLOAT)\n\t\t\tbest = SFMT_FLOAT;\n\t}\n\telse if (req == SFMT_S16 || req == SFMT_U16) {\n\t\tif (formats & SFMT_S16)\n\t\t\tbest = SFMT_S16;\n\t\telse if (formats & SFMT_U16)\n\t\t\tbest = SFMT_U16;\n\t\telse if (formats & SFMT_S32)\n\t\t\tbest = SFMT_S32;\n\t\telse if (formats & SFMT_U32)\n\t\t\tbest = SFMT_U32;\n\t\telse if (formats & SFMT_FLOAT)\n\t\t\tbest = SFMT_FLOAT;\n\t\telse if (formats & SFMT_S8)\n\t\t\tbest = SFMT_S8;\n\t\telse if (formats & SFMT_U8)\n\t\t\tbest = SFMT_U8;\n\t}\n\telse if (req == SFMT_S32 || req == SFMT_U32 || req == SFMT_FLOAT) {\n\t\tif (formats & SFMT_S32)\n\t\t\tbest = SFMT_S32;\n\t\telse if (formats & SFMT_U32)\n\t\t\tbest = SFMT_U32;\n\t\telse if (formats & SFMT_S16)\n\t\t\tbest = SFMT_S16;\n\t\telse if (formats & SFMT_U16)\n\t\t\tbest = SFMT_U16;\n\t\telse if (formats & SFMT_FLOAT)\n\t\t\tbest = SFMT_FLOAT;\n\t\telse if (formats & SFMT_S8)\n\t\t\tbest = SFMT_S8;\n\t\telse if (formats & SFMT_U8)\n\t\t\tbest = SFMT_U8;\n\t}\n\n\tassert (best != 0);\n\n\tif (!(best & (SFMT_S8 | SFMT_U8))) {\n\t\tif ((formats_with_endian & SFMT_LE)\n\t\t\t\t&& (formats_with_endian & SFMT_BE))\n\t\t\tbest |= SFMT_NE;\n\t\telse\n\t\t\tbest |= formats_with_endian & SFMT_MASK_ENDIANNESS;\n\t}\n\n\tdebug (\"Chose %s as the best matching %s\",\n\t\t\tsfmt_str(best, fmt_name1, sizeof(fmt_name1)),\n\t\t\tsfmt_str(req_with_endian, fmt_name2, sizeof(fmt_name2)));\n\n\treturn best;\n}\n\n/* Return the number of bytes per sample for the given format. */\nint sfmt_Bps (const long format)\n{\n\tint Bps = -1;\n\n\tswitch (format & SFMT_MASK_FORMAT) {\n\t\tcase SFMT_S8:\n\t\tcase SFMT_U8:\n\t\t\tBps = 1;\n\t\t\tbreak;\n\t\tcase SFMT_S16:\n\t\tcase SFMT_U16:\n\t\t\tBps = 2;\n\t\t\tbreak;\n\t\tcase SFMT_S32:\n\t\tcase SFMT_U32:\n\t\t\tBps = 4;\n\t\t\tbreak;\n\t\tcase SFMT_FLOAT:\n\t\t\tBps = sizeof (float);\n\t\t\tbreak;\n\t}\n\n\tassert (Bps > 0);\n\n\treturn Bps;\n}\n\n/* Move to the next file depending on the options set, the user\n * request and whether or not there are files in the queue. */\nstatic void go_to_another_file ()\n{\n\tbool shuffle = options_get_bool (\"Shuffle\");\n\tbool go_next = (play_next || options_get_bool(\"AutoNext\"));\n\tint curr_playing_curr_pos;\n\t/* XXX: Shouldn't play_next be protected by mutex? */\n\n\tLOCK (curr_playing_mtx);\n\tLOCK (plist_mtx);\n\n\t/* If we move forward in the playlist and there are some songs in\n\t * the queue, then play them. */\n\tif (plist_count(&queue) && go_next) {\n\t\tlogit (\"Playing file from queue\");\n\n\t\tif (!before_queue_fname && curr_playing_fname)\n\t\t\tbefore_queue_fname = xstrdup (curr_playing_fname);\n\n\t\tcurr_plist = &queue;\n\t\tcurr_playing = plist_next (&queue, -1);\n\n\t\tserver_queue_pop (queue.items[curr_playing].file);\n\t\tplist_delete (&queue, curr_playing);\n\t}\n\telse {\n\t\t/* If we just finished playing files from the queue and the\n\t\t * appropriate option is set, continue with the file played\n\t\t * before playing the queue. */\n\t\tif (before_queue_fname && options_get_bool (\"QueueNextSongReturn\")) {\n\t\t\tfree (curr_playing_fname);\n\t\t\tcurr_playing_fname = before_queue_fname;\n\t\t\tbefore_queue_fname = NULL;\n\t\t}\n\n\t\tif (shuffle) {\n\t\t\tcurr_plist = &shuffled_plist;\n\n\t\t\tif (plist_count(&playlist)\n\t\t\t\t\t&& !plist_count(&shuffled_plist)) {\n\t\t\t\tplist_cat (&shuffled_plist, &playlist);\n\t\t\t\tplist_shuffle (&shuffled_plist);\n\n\t\t\t\tif (curr_playing_fname)\n\t\t\t\t\tplist_swap_first_fname (&shuffled_plist,\n\t\t\t\t\t\t\tcurr_playing_fname);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tcurr_plist = &playlist;\n\n\t\tcurr_playing_curr_pos = plist_find_fname (curr_plist,\n\t\t\t\tcurr_playing_fname);\n\n\t\t/* If we came from the queue and the last file in\n\t\t * queue wasn't in the playlist, we try to revert to\n\t\t * the QueueNextSongReturn == true behaviour. */\n\t\tif (curr_playing_curr_pos == -1 && before_queue_fname) {\n\t\t\tcurr_playing_curr_pos = plist_find_fname (curr_plist,\n\t\t\t\t\tbefore_queue_fname);\n\t\t}\n\n\t\tif (play_prev && plist_count(curr_plist)) {\n\t\t\tlogit (\"Playing previous...\");\n\n\t\t\tif (curr_playing_curr_pos == -1\n\t\t\t\t\t|| started_playing_in_queue) {\n\t\t\t\tcurr_playing = plist_prev (curr_plist, -1);\n\t\t\t\tstarted_playing_in_queue = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t\tcurr_playing = plist_prev (curr_plist,\n\t\t\t\t\t\tcurr_playing_curr_pos);\n\n\t\t\tif (curr_playing == -1) {\n\t\t\t\tif (options_get_bool(\"Repeat\"))\n\t\t\t\t\tcurr_playing = plist_last (curr_plist);\n\t\t\t\tlogit (\"Beginning of the list.\");\n\t\t\t}\n\t\t\telse\n\t\t\t\tlogit (\"Previous item.\");\n\t\t}\n\t\telse if (go_next && plist_count(curr_plist)) {\n\t\t\tlogit (\"Playing next...\");\n\n\t\t\tif (curr_playing_curr_pos == -1\n\t\t\t\t\t|| started_playing_in_queue) {\n\t\t\t\tcurr_playing = plist_next (curr_plist, -1);\n\t\t\t\tstarted_playing_in_queue = 0;\n\t\t\t}\n\t\t\telse\n\t\t\t\tcurr_playing = plist_next (curr_plist,\n\t\t\t\t\t\tcurr_playing_curr_pos);\n\n\t\t\tif (curr_playing == -1 && options_get_bool(\"Repeat\")) {\n\t\t\t\tif (shuffle) {\n\t\t\t\t\tplist_clear (&shuffled_plist);\n\t\t\t\t\tplist_cat (&shuffled_plist, &playlist);\n\t\t\t\t\tplist_shuffle (&shuffled_plist);\n\t\t\t\t}\n\t\t\t\tcurr_playing = plist_next (curr_plist, -1);\n\t\t\t\tlogit (\"Going back to the first item.\");\n\t\t\t}\n\t\t\telse if (curr_playing == -1)\n\t\t\t\tlogit (\"End of the list\");\n\t\t\telse\n\t\t\t\tlogit (\"Next item\");\n\n\t\t}\n\t\telse if (!options_get_bool(\"Repeat\")) {\n\t\t\tcurr_playing = -1;\n\t\t}\n\t\telse\n\t\t\tdebug (\"Repeating file\");\n\n\t\tif (before_queue_fname)\n\t\t\tfree (before_queue_fname);\n\t\tbefore_queue_fname = NULL;\n\t}\n\n\tUNLOCK (plist_mtx);\n\tUNLOCK (curr_playing_mtx);\n}\n\nstatic void *play_thread (void *unused ATTR_UNUSED)\n{\n\tlogit (\"Entering playing thread\");\n\n\twhile (curr_playing != -1) {\n\t\tchar *file;\n\n\t\tLOCK (plist_mtx);\n\t\tfile = plist_get_file (curr_plist, curr_playing);\n\t\tUNLOCK (plist_mtx);\n\n\t\tplay_next = 0;\n\t\tplay_prev = 0;\n\n\t\tif (file) {\n\t\t\tint next;\n\t\t\tchar *next_file;\n\n\t\t\tLOCK (curr_playing_mtx);\n\t\t\tLOCK (plist_mtx);\n\t\t\tlogit (\"Playing item %d: %s\", curr_playing, file);\n\n\t\t\tif (curr_playing_fname)\n\t\t\t\tfree (curr_playing_fname);\n\t\t\tcurr_playing_fname = xstrdup (file);\n\n\t\t\tout_buf_time_set (out_buf, 0.0);\n\n\t\t\tnext = plist_next (curr_plist, curr_playing);\n\t\t\tnext_file = next != -1 ? plist_get_file (curr_plist, next) : NULL;\n\t\t\tUNLOCK (plist_mtx);\n\t\t\tUNLOCK (curr_playing_mtx);\n\n\t\t\tplayer (file, next_file, out_buf);\n\t\t\tif (next_file)\n\t\t\t\tfree (next_file);\n\n\t\t\tset_info_rate (0);\n\t\t\tset_info_bitrate (0);\n\t\t\tset_info_channels (1);\n\t\t\tout_buf_time_set (out_buf, 0.0);\n\t\t\tfree (file);\n\t\t}\n\n\t\tLOCK (curr_playing_mtx);\n\t\tif (last_stream_url) {\n\t\t\tfree (last_stream_url);\n\t\t\tlast_stream_url = NULL;\n\t\t}\n\t\tUNLOCK (curr_playing_mtx);\n\n\t\tif (stop_playing) {\n\t\t\tLOCK (curr_playing_mtx);\n\t\t\tcurr_playing = -1;\n\t\t\tUNLOCK (curr_playing_mtx);\n\t\t\tlogit (\"stopped\");\n\t\t}\n\t\telse\n\t\t\tgo_to_another_file ();\n\t}\n\n\tprev_state = state;\n\tstate = STATE_STOP;\n\tstate_change ();\n\n\tif (curr_playing_fname) {\n\t\tfree (curr_playing_fname);\n\t\tcurr_playing_fname = NULL;\n\t}\n\n\taudio_close ();\n\tlogit (\"Exiting\");\n\n\treturn NULL;\n}\n\nvoid audio_reset ()\n{\n\tif (hw.reset)\n\t\thw.reset ();\n}\n\nvoid audio_stop ()\n{\n\tint rc;\n\n\tif (play_thread_running) {\n\t\tlogit (\"audio_stop()\");\n\t\tLOCK (request_mtx);\n\t\tstop_playing = 1;\n\t\tUNLOCK (request_mtx);\n\t\tplayer_stop ();\n\t\tlogit (\"pthread_join (playing_thread, NULL)\");\n\t\trc = pthread_join (playing_thread, NULL);\n\t\tif (rc != 0)\n\t\t\tlog_errno (\"pthread_join() failed\", rc);\n\t\tplaying_thread = 0;\n\t\tplay_thread_running = 0;\n\t\tstop_playing = 0;\n\t\tlogit (\"done stopping\");\n\t}\n\telse if (state == STATE_PAUSE) {\n\n\t\t/* Paused internet stream - we are in fact stopped already. */\n\t\tif (curr_playing_fname) {\n\t\t\tfree (curr_playing_fname);\n\t\t\tcurr_playing_fname = NULL;\n\t\t}\n\n\t\tprev_state = state;\n\t\tstate = STATE_STOP;\n\t\tstate_change ();\n\t}\n}\n\n/* Start playing from the file fname. If fname is an empty string,\n * start playing from the first file on the list. */\nvoid audio_play (const char *fname)\n{\n\tint rc;\n\n\taudio_stop ();\n\tplayer_reset ();\n\n\tLOCK (curr_playing_mtx);\n\tLOCK (plist_mtx);\n\n\t/* If we have songs in the queue and fname is empty string, start\n\t * playing file from the queue. */\n\tif (plist_count(&queue) && !(*fname)) {\n\t\tcurr_plist = &queue;\n\t\tcurr_playing = plist_next (&queue, -1);\n\n\t\t/* remove the file from queue */\n\t\tserver_queue_pop (queue.items[curr_playing].file);\n\t\tplist_delete (curr_plist, curr_playing);\n\n\t\tstarted_playing_in_queue = 1;\n\t}\n\telse if (options_get_bool(\"Shuffle\")) {\n\t\tplist_clear (&shuffled_plist);\n\t\tplist_cat (&shuffled_plist, &playlist);\n\t\tplist_shuffle (&shuffled_plist);\n\t\tplist_swap_first_fname (&shuffled_plist, fname);\n\n\t\tcurr_plist = &shuffled_plist;\n\n\t\tif (*fname)\n\t\t\tcurr_playing = plist_find_fname (curr_plist, fname);\n\t\telse if (plist_count(curr_plist)) {\n\t\t\tcurr_playing = plist_next (curr_plist, -1);\n\t\t}\n\t\telse\n\t\t\tcurr_playing = -1;\n\t}\n\telse {\n\t\tcurr_plist = &playlist;\n\n\t\tif (*fname)\n\t\t\tcurr_playing = plist_find_fname (curr_plist, fname);\n\t\telse if (plist_count(curr_plist))\n\t\t\tcurr_playing = plist_next (curr_plist, -1);\n\t\telse\n\t\t\tcurr_playing = -1;\n\t}\n\n\trc = pthread_create (&playing_thread, NULL, play_thread, NULL);\n\tif (rc != 0)\n\t\terror_errno (\"Can't create thread\", rc);\n\tplay_thread_running = 1;\n\n\tUNLOCK (plist_mtx);\n\tUNLOCK (curr_playing_mtx);\n}\n\nvoid audio_next ()\n{\n\tif (play_thread_running) {\n\t\tplay_next = 1;\n\t\tplayer_stop ();\n\t}\n}\n\nvoid audio_prev ()\n{\n\tif (play_thread_running) {\n\t\tplay_prev = 1;\n\t\tplayer_stop ();\n\t}\n}\n\nvoid audio_pause ()\n{\n\tLOCK (curr_playing_mtx);\n\tLOCK (plist_mtx);\n\n\tif (curr_playing != -1) {\n\t\tchar *sname = plist_get_file (curr_plist, curr_playing);\n\n\t\tif (file_type(sname) == F_URL) {\n\t\t\tUNLOCK (curr_playing_mtx);\n\t\t\tUNLOCK (plist_mtx);\n\t\t\taudio_stop ();\n\t\t\tLOCK (curr_playing_mtx);\n\t\t\tLOCK (plist_mtx);\n\n\t\t\tif (last_stream_url)\n\t\t\t\tfree (last_stream_url);\n\t\t\tlast_stream_url = xstrdup (sname);\n\n\t\t\t/* Pretend that we are paused on this. */\n\t\t\tcurr_playing_fname = xstrdup (sname);\n\t\t}\n\t\telse\n\t\t\tout_buf_pause (out_buf);\n\n\t\tprev_state = state;\n\t\tstate = STATE_PAUSE;\n\t\tstate_change ();\n\n\t\tfree (sname);\n\t}\n\n\tUNLOCK (plist_mtx);\n\tUNLOCK (curr_playing_mtx);\n}\n\nvoid audio_unpause ()\n{\n\tLOCK (curr_playing_mtx);\n\tif (last_stream_url && file_type(last_stream_url) == F_URL) {\n\t\tchar *url = xstrdup (last_stream_url);\n\n\t\tUNLOCK (curr_playing_mtx);\n\t\taudio_play (url);\n\t\tfree (url);\n\t}\n\telse if (curr_playing != -1) {\n\t\tout_buf_unpause (out_buf);\n\t\tprev_state = state;\n\t\tstate = STATE_PLAY;\n\t\tUNLOCK (curr_playing_mtx);\n\t\tstate_change ();\n\t}\n\telse\n\t\tUNLOCK (curr_playing_mtx);\n}\n\nstatic void reset_sound_params (struct sound_params *params)\n{\n\tparams->rate = 0;\n\tparams->channels = 0;\n\tparams->fmt = 0;\n}\n\n/* Return 0 on error. If sound params == NULL, open the device using\n * the previous parameters. */\nint audio_open (struct sound_params *sound_params)\n{\n\tint res;\n\tstatic struct sound_params last_params = { 0, 0, 0 };\n\n\tif (!sound_params)\n\t\tsound_params = &last_params;\n\telse\n\t\tlast_params = *sound_params;\n\n\tassert (sound_format_ok(sound_params->fmt));\n\n\tif (audio_opened) {\n\t\tif (sound_params_eq(req_sound_params, *sound_params)) {\n\t\t\tif (audio_get_bps() >= 88200) {\n\t\t\t\tlogit (\"Audio device already opened with such parameters.\");\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t/* Not closing the device would cause that much\n\t\t\t * sound from the previous file to stay in the buffer\n\t\t\t * and the user will hear old data, so close it. */\n\t\t\tlogit (\"Reopening device due to low bps.\");\n\t\t}\n\n\t\taudio_close ();\n\t}\n\n\treq_sound_params = *sound_params;\n\n\t/* Set driver_sound_params to parameters supported by the driver that\n\t * are nearly the requested parameters. */\n\n\tif (options_get_int(\"ForceSampleRate\")) {\n\t\tdriver_sound_params.rate = options_get_int(\"ForceSampleRate\");\n\t\tlogit (\"Setting forced driver sample rate to %dHz\",\n\t\t\t\tdriver_sound_params.rate);\n\t}\n\telse\n\t\tdriver_sound_params.rate = req_sound_params.rate;\n\n\tdriver_sound_params.fmt = sfmt_best_matching (hw_caps.formats,\n\t\t\treq_sound_params.fmt);\n\n\t/* number of channels */\n\tdriver_sound_params.channels = CLAMP(hw_caps.min_channels,\n\t                                     req_sound_params.channels,\n\t                                     hw_caps.max_channels);\n\n\tres = hw.open (&driver_sound_params);\n\n\tif (res) {\n\t\tchar fmt_name[SFMT_STR_MAX] LOGIT_ONLY;\n\n\t\tdriver_sound_params.rate = hw.get_rate ();\n\t\tif (driver_sound_params.fmt != req_sound_params.fmt\n\t\t\t\t|| driver_sound_params.channels\n\t\t\t\t!= req_sound_params.channels\n\t\t\t\t|| (!sample_rate_compat(\n\t\t\t\t\t\treq_sound_params.rate,\n\t\t\t\t\t\tdriver_sound_params.rate))) {\n\t\t\tlogit (\"Conversion of the sound is needed.\");\n\t\t\tif (!audio_conv_new (&sound_conv, &req_sound_params,\n\t\t\t\t\t&driver_sound_params)) {\n\t\t\t\thw.close ();\n\t\t\t\treset_sound_params (&req_sound_params);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t\tneed_audio_conversion = 1;\n\t\t}\n\t\taudio_opened = 1;\n\n\t\tlogit (\"Requested sound parameters: %s, %d channels, %dHz\",\n\t\t\t\tsfmt_str(req_sound_params.fmt, fmt_name, sizeof(fmt_name)),\n\t\t\t\treq_sound_params.channels,\n\t\t\t\treq_sound_params.rate);\n\t\tlogit (\"Driver sound parameters: %s, %d channels, %dHz\",\n\t\t\t\tsfmt_str(driver_sound_params.fmt, fmt_name, sizeof(fmt_name)),\n\t\t\t\tdriver_sound_params.channels,\n\t\t\t\tdriver_sound_params.rate);\n\t}\n\n\treturn res;\n}\n\nint audio_send_buf (const char *buf, const size_t size)\n{\n\tsize_t out_data_len = size;\n\tint res;\n\tchar *converted = NULL;\n\n\tif (need_audio_conversion)\n\t\tconverted = audio_conv (&sound_conv, buf, size, &out_data_len);\n\n\tif (need_audio_conversion && converted)\n\t\tres = out_buf_put (out_buf, converted, out_data_len);\n\telse if (!need_audio_conversion)\n\t\tres = out_buf_put (out_buf, buf, size);\n\telse\n\t\tres = 0;\n\n\tif (converted)\n\t\tfree (converted);\n\n\treturn res;\n}\n\n/* Get the current audio format bytes per frame value.\n * May return 0 if the audio device is closed. */\nint audio_get_bpf ()\n{\n\treturn driver_sound_params.channels\n\t\t* (driver_sound_params.fmt ? sfmt_Bps(driver_sound_params.fmt)\n\t\t\t\t: 0);\n}\n\n/* Get the current audio format bytes per second value.\n * May return 0 if the audio device is closed. */\nint audio_get_bps ()\n{\n\treturn driver_sound_params.rate * audio_get_bpf ();\n}\n\nint audio_get_buf_fill ()\n{\n\treturn hw.get_buff_fill ();\n}\n\nint audio_send_pcm (const char *buf, const size_t size)\n{\n\tchar *softmixed = NULL;\n\tchar *equalized = NULL;\n\n\tif (equalizer_is_active ())\n\t{\n\t\tequalized = xmalloc (size);\n\t\tmemcpy (equalized, buf, size);\n\n\t\tequalizer_process_buffer (equalized, size, &driver_sound_params);\n\n\t\tbuf = equalized;\n\t}\n\n\tif (softmixer_is_active () || softmixer_is_mono ())\n\t{\n\t\tif (equalized)\n\t\t{\n\t\t\tsoftmixed = equalized;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tsoftmixed = xmalloc (size);\n\t\t\tmemcpy (softmixed, buf, size);\n\t\t}\n\n\t\tsoftmixer_process_buffer (softmixed, size, &driver_sound_params);\n\n\t\tbuf = softmixed;\n\t}\n\n\tint played;\n\n\tplayed = hw.play (buf, size);\n\n\tif (played < 0)\n\t\tfatal (\"Audio output error!\");\n\n\tif (softmixed && !equalized)\n\t\tfree (softmixed);\n\n\tif (equalized)\n\t\tfree (equalized);\n\n\treturn played;\n}\n\n/* Get current time of the song in seconds. */\nint audio_get_time ()\n{\n\treturn state != STATE_STOP ? out_buf_time_get (out_buf) : 0;\n}\n\nvoid audio_close ()\n{\n\tif (audio_opened) {\n\t\treset_sound_params (&req_sound_params);\n\t\treset_sound_params (&driver_sound_params);\n\t\thw.close ();\n\t\tif (need_audio_conversion) {\n\t\t\taudio_conv_destroy (&sound_conv);\n\t\t\tneed_audio_conversion = 0;\n\t\t}\n\t\taudio_opened = 0;\n\t}\n}\n\n/* Try to initialize drivers from the list and fill funcs with\n * those of the first working driver. */\nstatic void find_working_driver (lists_t_strs *drivers, struct hw_funcs *funcs)\n{\n\tint ix;\n\n\tmemset (funcs, 0, sizeof(*funcs));\n\n\tfor (ix = 0; ix < lists_strs_size (drivers); ix += 1) {\n\t\tconst char *name;\n\n\t\tname = lists_strs_at (drivers, ix);\n\n#ifdef HAVE_SNDIO\n\t\tif (!strcasecmp(name, \"sndio\")) {\n\t\t\tsndio_funcs (funcs);\n\t\t\tprintf (\"Trying SNDIO...\\n\");\n\t\t\tif (funcs->init(&hw_caps))\n\t\t\t\treturn;\n\t\t}\n#endif\n\n#ifdef HAVE_OSS\n\t\tif (!strcasecmp(name, \"oss\")) {\n\t\t\toss_funcs (funcs);\n\t\t\tprintf (\"Trying OSS...\\n\");\n\t\t\tif (funcs->init(&hw_caps))\n\t\t\t\treturn;\n\t\t}\n#endif\n\n#ifdef HAVE_ALSA\n\t\tif (!strcasecmp(name, \"alsa\")) {\n\t\t\talsa_funcs (funcs);\n\t\t\tprintf (\"Trying ALSA...\\n\");\n\t\t\tif (funcs->init(&hw_caps))\n\t\t\t\treturn;\n\t\t}\n#endif\n\n#ifdef HAVE_JACK\n\t\tif (!strcasecmp(name, \"jack\")) {\n\t\t\tmoc_jack_funcs (funcs);\n\t\t\tprintf (\"Trying JACK...\\n\");\n\t\t\tif (funcs->init(&hw_caps))\n\t\t\t\treturn;\n\t\t}\n#endif\n\n#ifndef NDEBUG\n\t\tif (!strcasecmp(name, \"null\")) {\n\t\t\tnull_funcs (funcs);\n\t\t\tprintf (\"Trying NULL...\\n\");\n\t\t\tif (funcs->init(&hw_caps))\n\t\t\t\treturn;\n\t\t}\n#endif\n\t}\n\n\tfatal (\"No valid sound driver!\");\n}\n\nstatic void print_output_capabilities\n            (const struct output_driver_caps *caps LOGIT_ONLY)\n{\n\tchar fmt_name[SFMT_STR_MAX] LOGIT_ONLY;\n\n\tlogit (\"Sound driver capabilities: channels %d - %d, formats: %s\",\n\t\t\tcaps->min_channels, caps->max_channels,\n\t\t\tsfmt_str(caps->formats, fmt_name, sizeof(fmt_name)));\n}\n\nvoid audio_initialize ()\n{\n\tfind_working_driver (options_get_list (\"SoundDriver\"), &hw);\n\n\tif (hw_caps.max_channels < hw_caps.min_channels)\n\t\tfatal (\"Error initializing audio device: \"\n\t\t       \"device reports incorrect number of channels.\");\n\tif (!sound_format_ok (hw_caps.formats))\n\t\tfatal (\"Error initializing audio device: \"\n\t\t       \"device reports no usable formats.\");\n\n\tprint_output_capabilities (&hw_caps);\n\tif (!options_get_bool (\"Allow24bitOutput\")\n\t\t\t&& hw_caps.formats & (SFMT_S32 | SFMT_U32)) {\n\t\tlogit (\"Disabling 24bit modes because Allow24bitOutput is set to no.\");\n\t\thw_caps.formats &= ~(SFMT_S32 | SFMT_U32);\n\t\tif (!sound_format_ok (hw_caps.formats))\n\t\t\tfatal (\"No available sound formats after disabling 24bit modes. \"\n\t\t\t       \"Consider setting Allow24bitOutput to yes.\");\n\t}\n\n\tout_buf = out_buf_new (options_get_int(\"OutputBuffer\") * 1024);\n\n\tsoftmixer_init();\n\tequalizer_init();\n\n\tplist_init (&playlist);\n\tplist_init (&shuffled_plist);\n\tplist_init (&queue);\n\tplayer_init ();\n}\n\nvoid audio_exit ()\n{\n\tint rc;\n\n\taudio_stop ();\n\tif (hw.shutdown)\n\t\thw.shutdown ();\n\tout_buf_free (out_buf);\n\tout_buf = NULL;\n\tplist_free (&playlist);\n\tplist_free (&shuffled_plist);\n\tplist_free (&queue);\n\tplayer_cleanup ();\n\trc = pthread_mutex_destroy (&curr_playing_mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy curr_playing_mtx\", rc);\n\trc = pthread_mutex_destroy (&plist_mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy plist_mtx\", rc);\n\trc = pthread_mutex_destroy (&request_mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy request_mtx\", rc);\n\n\tif (last_stream_url)\n\t\tfree (last_stream_url);\n\n\tsoftmixer_shutdown();\n\tequalizer_shutdown();\n}\n\nvoid audio_seek (const int sec)\n{\n\tint playing;\n\n\tLOCK (curr_playing_mtx);\n\tplaying = curr_playing;\n\tUNLOCK (curr_playing_mtx);\n\n\tif (playing != -1 && state == STATE_PLAY)\n\t\tplayer_seek (sec);\n\telse\n\t\tlogit (\"Seeking when nothing is played.\");\n}\n\nvoid audio_jump_to (const int sec)\n{\n\tint playing;\n\n\tLOCK (curr_playing_mtx);\n\tplaying = curr_playing;\n\tUNLOCK (curr_playing_mtx);\n\n\tif (playing != -1 && state == STATE_PLAY)\n\t\tplayer_jump_to (sec);\n\telse\n\t\tlogit (\"Jumping when nothing is played.\");\n}\n\nint audio_get_state ()\n{\n\treturn state;\n}\n\nint audio_get_prev_state ()\n{\n\treturn prev_state;\n}\n\nvoid audio_plist_add (const char *file)\n{\n\tLOCK (plist_mtx);\n\tplist_clear (&shuffled_plist);\n\tif (plist_find_fname(&playlist, file) == -1)\n\t\tplist_add (&playlist, file);\n\telse\n\t\tlogit (\"Wanted to add a file already present: %s\", file);\n\tUNLOCK (plist_mtx);\n}\n\nvoid audio_queue_add (const char *file)\n{\n\tLOCK (plist_mtx);\n\tif (plist_find_fname(&queue, file) == -1)\n\t\tplist_add (&queue, file);\n\telse\n\t\tlogit (\"Wanted to add a file already present: %s\", file);\n\tUNLOCK (plist_mtx);\n}\n\nvoid audio_plist_clear ()\n{\n\tLOCK (plist_mtx);\n\tplist_clear (&shuffled_plist);\n\tplist_clear (&playlist);\n\tUNLOCK (plist_mtx);\n}\n\nvoid audio_queue_clear ()\n{\n\tLOCK (plist_mtx);\n\tplist_clear (&queue);\n\tUNLOCK (plist_mtx);\n}\n\n/* Returned memory is malloc()ed. */\nchar *audio_get_sname ()\n{\n\tchar *sname;\n\n\tLOCK (curr_playing_mtx);\n\tsname = xstrdup (curr_playing_fname);\n\tUNLOCK (curr_playing_mtx);\n\n\treturn sname;\n}\n\nint audio_get_mixer ()\n{\n\tif (current_mixer == 2)\n\t\treturn softmixer_get_value ();\n\n\treturn hw.read_mixer ();\n}\n\nvoid audio_set_mixer (const int val)\n{\n\tif (!RANGE(0, val, 100)) {\n\t\tlogit (\"Tried to set mixer to volume out of range.\");\n\t\treturn;\n\t}\n\n\tif (current_mixer == 2)\n\t\tsoftmixer_set_value (val);\n\telse\n\t\thw.set_mixer (val);\n}\n\nvoid audio_plist_delete (const char *file)\n{\n\tint num;\n\n\tLOCK (plist_mtx);\n\tnum = plist_find_fname (&playlist, file);\n\tif (num != -1)\n\t\tplist_delete (&playlist, num);\n\n\tnum = plist_find_fname (&shuffled_plist, file);\n\tif (num != -1)\n\t\tplist_delete (&shuffled_plist, num);\n\tUNLOCK (plist_mtx);\n}\n\nvoid audio_queue_delete (const char *file)\n{\n\tint num;\n\n\tLOCK (plist_mtx);\n\tnum = plist_find_fname (&queue, file);\n\tif (num != -1)\n\t\tplist_delete (&queue, num);\n\tUNLOCK (plist_mtx);\n}\n\n/* Get the time of a file if the file is on the playlist and\n * the time is available. */\nint audio_get_ftime (const char *file)\n{\n\tint i;\n\tint time;\n\ttime_t mtime;\n\n\tmtime = get_mtime (file);\n\n\tLOCK (plist_mtx);\n\ti = plist_find_fname (&playlist, file);\n\tif (i != -1) {\n\t\ttime = get_item_time (&playlist, i);\n\t\tif (time != -1) {\n\t\t\tif (playlist.items[i].mtime == mtime) {\n\t\t\t\tdebug (\"Found time for %s\", file);\n\t\t\t\tUNLOCK (plist_mtx);\n\t\t\t\treturn time;\n\t\t\t}\n\t\t\tlogit (\"mtime for %s has changed\", file);\n\t\t}\n\t}\n\tUNLOCK (plist_mtx);\n\n\treturn -1;\n}\n\n/* Set the time for a file on the playlist. */\nvoid audio_plist_set_time (const char *file, const int time)\n{\n\tint i;\n\n\tLOCK (plist_mtx);\n\tif ((i = plist_find_fname(&playlist, file)) != -1) {\n\t\tplist_set_item_time (&playlist, i, time);\n\t\tplaylist.items[i].mtime = get_mtime (file);\n\t\tdebug (\"Setting time for %s\", file);\n\t}\n\telse\n\t\tlogit (\"Request for updating time for a file not present on the\"\n\t\t\t\t\" playlist!\");\n\tUNLOCK (plist_mtx);\n}\n\n/* Notify that the state was changed (used by the player). */\nvoid audio_state_started_playing ()\n{\n\tprev_state = state;\n\tstate = STATE_PLAY;\n\tstate_change ();\n}\n\nint audio_plist_get_serial ()\n{\n\tint serial;\n\n\tLOCK (plist_mtx);\n\tserial = plist_get_serial (&playlist);\n\tUNLOCK (plist_mtx);\n\n\treturn serial;\n}\n\nvoid audio_plist_set_serial (const int serial)\n{\n\tLOCK (plist_mtx);\n\tplist_set_serial (&playlist, serial);\n\tUNLOCK (plist_mtx);\n}\n\n/* Swap 2 files on the playlist. */\nvoid audio_plist_move (const char *file1, const char *file2)\n{\n\tLOCK (plist_mtx);\n\tplist_swap_files (&playlist, file1, file2);\n\tUNLOCK (plist_mtx);\n}\n\nvoid audio_queue_move (const char *file1, const char *file2)\n{\n\tLOCK (plist_mtx);\n\tplist_swap_files (&queue, file1, file2);\n\tUNLOCK (plist_mtx);\n}\n\n/* Return a copy of the song queue.  We cannot just return constant\n * pointer, because it will be used in a different thread.\n * It obviously needs to be freed after use. */\nstruct plist* audio_queue_get_contents ()\n{\n\tstruct plist *ret = (struct plist *)xmalloc (sizeof(struct plist));\n\tplist_init (ret);\n\n\tLOCK (plist_mtx);\n\tplist_cat (ret, &queue);\n\tUNLOCK (plist_mtx);\n\n\treturn ret;\n}\n\nstruct file_tags *audio_get_curr_tags ()\n{\n\treturn player_get_curr_tags ();\n}\n\nchar *audio_get_mixer_channel_name ()\n{\n\tif (current_mixer == 2)\n\t\treturn softmixer_name ();\n\n\treturn hw.get_mixer_channel_name ();\n}\n\nvoid audio_toggle_mixer_channel ()\n{\n\tcurrent_mixer = (current_mixer + 1) % 3;\n\tif (current_mixer < 2)\n\t\thw.toggle_mixer_channel ();\n}\n"
  },
  {
    "path": "audio.h",
    "content": "#ifndef AUDIO_H\n#define AUDIO_H\n\n#include <stdlib.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/** Sound formats.\n *\n * Sound format bits. Only one can be set in the format, the exception is\n * when we want to hold a list of supported formats - they can be bitwise-or'd.\n */\nenum sfmt_fmt\n{\n\tSFMT_S8 =    0x00000001, /*!< signed 8-bit */\n\tSFMT_U8\t=    0x00000002, /*!< unsigned 8-bit */\n\tSFMT_S16 =   0x00000004, /*!< signed 16-bit */\n\tSFMT_U16 =   0x00000008, /*!< unsigned 16-bit */\n\tSFMT_S32 =   0x00000010, /*!< signed 24-bit (LSB is 0) */\n\tSFMT_U32 =   0x00000020, /*!< unsigned 24-bit (LSB set to 0) */\n\tSFMT_FLOAT = 0x00000040  /*!< float in range -1.0 to 1.0 */\n};\n\n/** Sample endianness.\n *\n * Sample endianness - one of them must be set for 16-bit and 24-bit formats.\n */\nenum sfmt_endianness\n{\n\tSFMT_LE = 0x00001000, /*!< little-endian */\n\tSFMT_BE = 0x00002000, /*!< big-endian */\n\n/** Define native endianness to SFMT_LE or SFMT_BE. */\n#ifdef WORDS_BIGENDIAN\n\tSFMT_NE = SFMT_BE\n#else\n\tSFMT_NE = SFMT_LE\n#endif\n};\n\n/** @name Masks for the sample format.\n *\n * Masks used to extract only one type of information from the sound format.\n */\n/*@{*/\n#define SFMT_MASK_FORMAT     0x00000fff /*!< sample format */\n#define SFMT_MASK_ENDIANNESS 0x00003000 /*!< sample endianness */\n/*@}*/\n\n/** Return a value other than 0 if the sound format seems to be proper. */\n#define sound_format_ok(f) (((f) & SFMT_MASK_FORMAT) \\\n\t\t&& (((f) & (SFMT_S8 | SFMT_U8 | SFMT_FLOAT)) \\\n\t\t\t|| (f) & SFMT_MASK_ENDIANNESS))\n\n/** Change the sample format to new_fmt (without endianness). */\n#define sfmt_set_fmt(f, new_fmt) (((f) & ~SFMT_MASK_FORMAT) | (new_fmt))\n\n/** Change the sample format endianness to endian. */\n#define sfmt_set_endian(f, endian) (((f) & ~SFMT_MASK_ENDIANNESS) | (endian))\n\n/** Sound parameters.\n *\n * A structure describing sound parameters. The format is always PCM signed,\n * native endian for this machine.\n */\nstruct sound_params\n{\n\tint channels; /*!< Number of channels: 1 or 2 */\n\tint rate; /*!< Rate in Hz */\n\tlong fmt; /*!< Format of the samples (SFMT_* bits) */\n};\n\n/** Output driver capabilities.\n *\n * A structure describing the output driver capabilities.\n */\nstruct output_driver_caps\n{\n\tint min_channels; /*!< Minimum number of channels */\n\tint max_channels; /*!< Maximum number of channels */\n\tlong formats; /*!< Supported sample formats (or'd sfmt_fmt mask\n\t\t\twith endianness') */\n};\n\n/** \\struct hw_funcs\n * Functions to control the audio \"driver\".\n *\n * The structure holds pointers to functions that must be provided by the audio\n * \"driver\". All functions are executed only by one thread, so you don't need\n * to worry if they are thread safe.\n */\nstruct hw_funcs\n{\n\t/** Initialize the driver.\n\t *\n\t * This function is invoked only once when the MOC server starts.\n\t *\n\t * \\param caps Capabilities of the driver which must be filled by the\n\t * function.\n\t * \\return 1 on success and 0 otherwise.\n\t */\n\tint (*init) (struct output_driver_caps *caps);\n\n\t/** Clean up at exit.\n\t *\n\t * This function is invoked only once when the MOC server exits. The\n\t * audio device is not in use at this moment. The function should close\n\t * any opened devices and free any resources the driver allocated.\n\t * After this function was used, no other functions will be invoked.\n\t */\n\tvoid (*shutdown) ();\n\n\t/** Open the sound device.\n\t *\n\t * This function should open the sound device with the proper\n\t * parameters. The function should return 1 on success and 0 otherwise.\n\t * After returning 1 functions like play(), get_buff_fill() can be used.\n\t *\n\t * The sample rate of the driver can differ from the requested rate.\n\t * If so, get_rate() should return the actual rate.\n\t *\n\t * \\param sound_params Pointer to the sound_params structure holding\n\t * the required parameters.\n\t * \\return 1 on success and 0 otherwise.\n\t */\n\tint (*open) (struct sound_params *sound_params);\n\n\t/** Close the device.\n\t *\n\t * Request for closing the device.\n\t */\n\tvoid (*close) ();\n\n\t/** Play sound.\n\t *\n\t * Play sound provided in the buffer. The sound is in the format\n\t * requested when the open() function was invoked. The function should\n\t * play all sound in the buffer.\n\t *\n\t * \\param buff Pointer to the buffer with the sound.\n\t * \\param size Size (in bytes) of the buffer.\n\t *\n\t * \\return The number of bytes played or a value less than zero on\n\t * error.\n\t */\n\tint (*play) (const char *buff, const size_t size);\n\n\t/** Read the volume setting.\n\t *\n\t * Read the current volume setting. This must work regardless if the\n\t * functions open()/close() where used.\n\t *\n\t * \\return Volume value from 0% to 100%.\n\t */\n\tint (*read_mixer) ();\n\n\t/** Set the volume setting.\n\t *\n\t * Set the volume. This must work regardless if the functions\n\t * open()/close() where used.\n\t *\n\t * \\param vol Volume from 0% to 100%.\n\t */\n\tvoid (*set_mixer) (int vol);\n\n\t/** Read the hardware/internal buffer fill.\n\t *\n\t * The function should return the number of bytes of any\n\t * hardware or internal buffers are filled. For example: if we play()\n\t * 4KB, but only 1KB was really played (could be heard by the user),\n\t * the function should return 3072 (3KB).\n\t *\n\t * \\return Current hardware/internal buffer fill in bytes.\n\t */\n\tint (*get_buff_fill) ();\n\n\t/** Stop playing immediately.\n\t *\n\t * Request that the sound should not be played. This should involve\n\t * flushing any internal buffer filled with data sent by the play()\n\t * function and resetting the device to flush its buffer (if possible).\n\t *\n\t * \\return 1 on success or 0 otherwise.\n\t */\n\tint (*reset) ();\n\n\t/** Get the current sample rate setting.\n\t *\n\t * Get the actual sample rate setting of the audio driver.\n\t *\n\t * \\return Sample rate in Hz.\n\t */\n\tint (*get_rate) ();\n\n\t/** Toggle the mixer channel.\n\t *\n\t * Toggle between the first and the second mixer channel.\n\t */\n\tvoid (*toggle_mixer_channel) ();\n\n\t/** Get the mixer channel's name.\n\t *\n\t * Get the currently used mixer channel's name.\n\t *\n\t * \\return malloc()ed channel's name.\n\t */\n\tchar * (*get_mixer_channel_name) ();\n};\n\n/* Are the parameters p1 and p2 equal? */\n#define sound_params_eq(p1, p2) ((p1).fmt == (p2).fmt \\\n\t\t&& (p1).channels == (p2).channels && (p1).rate == (p2).rate)\n\n/* Maximum size of a string needed to hold the value returned by sfmt_str(). */\n#define SFMT_STR_MAX\t265\n\nchar *sfmt_str (const long format, char *msg, const size_t buf_size);\nint sfmt_Bps (const long format);\nint sfmt_same_bps (const long fmt1, const long fmt2);\n\nvoid audio_stop ();\nvoid audio_play (const char *fname);\nvoid audio_next ();\nvoid audio_prev ();\nvoid audio_pause ();\nvoid audio_unpause ();\nvoid audio_initialize ();\nvoid audio_exit ();\nvoid audio_seek (const int sec);\nvoid audio_jump_to (const int sec);\n\nint audio_open (struct sound_params *sound_params);\nint audio_send_buf (const char *buf, const size_t size);\nint audio_send_pcm (const char *buf, const size_t size);\nvoid audio_reset ();\nint audio_get_bpf ();\nint audio_get_bps ();\nint audio_get_buf_fill ();\nvoid audio_close ();\nint audio_get_time ();\nint audio_get_state ();\nint audio_get_prev_state ();\nvoid audio_plist_add (const char *file);\nvoid audio_plist_clear ();\nchar *audio_get_sname ();\nvoid audio_set_mixer (const int val);\nint audio_get_mixer ();\nvoid audio_plist_delete (const char *file);\nint audio_get_ftime (const char *file);\nvoid audio_plist_set_time (const char *file, const int time);\nvoid audio_state_started_playing ();\nint audio_plist_get_serial ();\nvoid audio_plist_set_serial (const int serial);\nstruct file_tags *audio_get_curr_tags ();\nchar *audio_get_mixer_channel_name ();\nvoid audio_toggle_mixer_channel ();\nvoid audio_plist_move (const char *file1, const char *file2);\nvoid audio_queue_add (const char *file);\nvoid audio_queue_delete (const char *file);\nvoid audio_queue_clear ();\nvoid audio_queue_move (const char *file1, const char *file2);\nstruct plist* audio_queue_get_contents ();\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "audio_conversion.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * Code for conversion between float and fixed point types is based on\n * libsamplerate:\n * Copyright (C) 2002-2004 Erik de Castro Lopo <erikd@mega-nerd.com>\n */\n\n/* For future: audio conversion should be performed in order:\n * channels -> rate -> format\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <assert.h>\n#include <stdlib.h>\n#include <math.h>\n#include <string.h>\n#include <strings.h>\n\n#ifdef HAVE_SAMPLERATE\n# include <samplerate.h>\n#endif\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"audio_conversion.h\"\n#include \"log.h\"\n#include \"options.h\"\n\nstatic void float_to_u8 (const float *in, unsigned char *out,\n\t\tconst size_t samples)\n{\n\tsize_t i;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++) {\n\t\tfloat f = in[i] * INT32_MAX;\n\n\t\tif (f >= INT32_MAX)\n\t\t\tout[i] = UINT8_MAX;\n\t\telse if (f <= INT32_MIN)\n\t\t\tout[i] = 0;\n\t\telse\n\t\t\tout[i] = (unsigned int)((lrintf(f) >> 24) - INT8_MIN);\n\t}\n}\n\nstatic void float_to_s8 (const float *in, char *out, const size_t samples)\n{\n\tsize_t i;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++) {\n\t\tfloat f = in[i] * INT32_MAX;\n\n\t\tif (f >= INT32_MAX)\n\t\t\tout[i] = INT8_MAX;\n\t\telse if (f <= INT32_MIN)\n\t\t\tout[i] = INT8_MIN;\n\t\telse\n\t\t\tout[i] = lrintf(f) >> 24;\n\t}\n}\n\nstatic void float_to_u16 (const float *in, unsigned char *out,\n\t\tconst size_t samples)\n{\n\tsize_t i;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++) {\n\t\tuint16_t *out_val = (uint16_t *)(out + i * sizeof (uint16_t));\n\t\tfloat f = in[i] * INT32_MAX;\n\n\t\tif (f >= INT32_MAX)\n\t\t\t*out_val = UINT16_MAX;\n\t\telse if (f <= INT32_MIN)\n\t\t\t*out_val = 0;\n\t\telse\n\t\t\t*out_val = (unsigned int)((lrintf(f) >> 16) - INT16_MIN);\n\t}\n}\n\nstatic void float_to_s16 (const float *in, char *out, const size_t samples)\n{\n\tsize_t i;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++) {\n\t\tint16_t *out_val = (int16_t *)(out + i * sizeof (int16_t));\n\t\tfloat f = in[i] * INT32_MAX;\n\n\t\tif (f >= INT32_MAX)\n\t\t\t*out_val = INT16_MAX;\n\t\telse if (f <= INT32_MIN)\n\t\t\t*out_val = INT16_MIN;\n\t\telse\n\t\t\t*out_val = lrintf(f) >> 16;\n\t}\n}\n\nstatic void float_to_u32 (const float *in, unsigned char *out,\n\t\tconst size_t samples)\n{\n\tsize_t i;\n\n\t/* maximum and minimum values of 32-bit samples */\n\tconst unsigned int U32_MAX = (1 << 24) - 1;\n\tconst int S32_MAX = (1 << 23) - 1;\n\tconst int S32_MIN = -(1 << 23);\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++) {\n\t\tuint32_t *out_val = (uint32_t *)(out + i * sizeof (uint32_t));\n\t\tfloat f = in[i] * S32_MAX;\n\n\t\tif (f >= S32_MAX)\n\t\t\t*out_val = U32_MAX << 8;\n\t\telse if (f <= S32_MIN)\n\t\t\t*out_val = 0;\n\t\telse\n\t\t\t*out_val = (uint32_t)(lrintf(f) - S32_MIN) << 8;\n\t}\n}\n\nstatic void float_to_s32 (const float *in, char *out, const size_t samples)\n{\n\tsize_t i;\n\n\t/* maximum and minimum values of 32-bit samples */\n\tconst int S32_MAX = (1 << 23) - 1;\n\tconst int S32_MIN = -(1 << 23);\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++) {\n\t\tint32_t *out_val = (int32_t *)(out + i * sizeof (int32_t));\n\t\tfloat f = in[i] * S32_MAX;\n\n\t\tif (f >= S32_MAX)\n\t\t\t*out_val = S32_MAX << 8;\n\t\telse if (f <= S32_MIN)\n\t\t\t*out_val = S32_MIN * 256;\n\t\telse\n\t\t\t*out_val = lrintf(f) << 8;\n\t}\n}\n\nstatic void u8_to_float (const unsigned char *in, float *out,\n\t\tconst size_t samples)\n{\n\tsize_t i;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++)\n\t\tout[i] = (((int)*in++) + INT8_MIN) / (float)(INT8_MAX + 1);\n}\n\nstatic void s8_to_float (const char *in, float *out, const size_t samples)\n{\n\tsize_t i;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++)\n\t\tout[i] = *in++ / (float)(INT8_MAX + 1);\n}\n\nstatic void u16_to_float (const unsigned char *in, float *out,\n\t\tconst size_t samples)\n{\n\tsize_t i;\n\tconst uint16_t *in_16 = (uint16_t *)in;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++)\n\t\tout[i] = ((int)*in_16++ + INT16_MIN) / (float)(INT16_MAX + 1);\n}\n\nstatic void s16_to_float (const char *in, float *out, const size_t samples)\n{\n\tsize_t i;\n\tconst int16_t *in_16 = (int16_t *)in;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++)\n\t\tout[i] = *in_16++ / (float)(INT16_MAX + 1);\n}\n\nstatic void u32_to_float (const unsigned char *in, float *out,\n\t\tconst size_t samples)\n{\n\tsize_t i;\n\tconst uint32_t *in_32 = (uint32_t *)in;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++)\n\t\tout[i] = ((float)*in_32++ + (float)INT32_MIN) / ((float)INT32_MAX + 1.0);\n}\n\nstatic void s32_to_float (const char *in, float *out, const size_t samples)\n{\n\tsize_t i;\n\tconst int32_t *in_32 = (int32_t *)in;\n\n\tassert (in != NULL);\n\tassert (out != NULL);\n\n\tfor (i = 0; i < samples; i++)\n\t\tout[i] = *in_32++ / ((float)INT32_MAX + 1.0);\n}\n\n/* Convert fixed point samples in format fmt (size in bytes) to float.\n * Size of converted sound is put in new_size. Returned memory is malloc()ed. */\nstatic float *fixed_to_float (const char *buf, const size_t size,\n\t\tconst long fmt, size_t *new_size)\n{\n\tfloat *out = NULL;\n\tchar fmt_name[SFMT_STR_MAX];\n\n\tassert ((fmt & SFMT_MASK_FORMAT) != SFMT_FLOAT);\n\n\tswitch (fmt & SFMT_MASK_FORMAT) {\n\t\tcase SFMT_U8:\n\t\t\t*new_size = sizeof(float) * size;\n\t\t\tout = (float *)xmalloc (*new_size);\n\t\t\tu8_to_float ((unsigned char *)buf, out, size);\n\t\t\tbreak;\n\t\tcase SFMT_S8:\n\t\t\t*new_size = sizeof(float) * size;\n\t\t\tout = (float *)xmalloc (*new_size);\n\t\t\ts8_to_float (buf, out, size);\n\t\t\tbreak;\n\t\tcase SFMT_U16:\n\t\t\t*new_size = sizeof(float) * size / 2;\n\t\t\tout = (float *)xmalloc (*new_size);\n\t\t\tu16_to_float ((unsigned char *)buf, out, size / 2);\n\t\t\tbreak;\n\t\tcase SFMT_S16:\n\t\t\t*new_size = sizeof(float) * size / 2;\n\t\t\tout = (float *)xmalloc (*new_size);\n\t\t\ts16_to_float (buf, out, size / 2);\n\t\t\tbreak;\n\t\tcase SFMT_U32:\n\t\t\t*new_size = sizeof(float) * size / 4;\n\t\t\tout = (float *)xmalloc (*new_size);\n\t\t\tu32_to_float ((unsigned char *)buf, out, size / 4);\n\t\t\tbreak;\n\t\tcase SFMT_S32:\n\t\t\t*new_size = sizeof(float) * size / 4;\n\t\t\tout = (float *)xmalloc (*new_size);\n\t\t\ts32_to_float (buf, out, size / 4);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\terror (\"Can't convert from %s to float!\",\n\t\t\t       sfmt_str (fmt, fmt_name, sizeof (fmt_name)));\n\t\t\tabort ();\n\t}\n\n\treturn out;\n}\n\n/* Convert float samples to fixed point format fmt. Returned samples of size\n * new_size bytes is malloc()ed. */\nstatic char *float_to_fixed (const float *buf, const size_t samples,\n\t\tconst long fmt, size_t *new_size)\n{\n\tchar fmt_name[SFMT_STR_MAX];\n\tchar *new_snd = NULL;\n\n\tassert ((fmt & SFMT_MASK_FORMAT) != SFMT_FLOAT);\n\n\tswitch (fmt & SFMT_MASK_FORMAT) {\n\t\tcase SFMT_U8:\n\t\t\t*new_size = samples;\n\t\t\tnew_snd = (char *)xmalloc (samples);\n\t\t\tfloat_to_u8 (buf, (unsigned char *)new_snd, samples);\n\t\t\tbreak;\n\t\tcase SFMT_S8:\n\t\t\t*new_size = samples;\n\t\t\tnew_snd = (char *)xmalloc (samples);\n\t\t\tfloat_to_s8 (buf, new_snd, samples);\n\t\t\tbreak;\n\t\tcase SFMT_U16:\n\t\t\t*new_size = samples * 2;\n\t\t\tnew_snd = (char *)xmalloc (*new_size);\n\t\t\tfloat_to_u16 (buf, (unsigned char *)new_snd, samples);\n\t\t\tbreak;\n\t\tcase SFMT_S16:\n\t\t\t*new_size = samples * 2;\n\t\t\tnew_snd = (char *)xmalloc (*new_size);\n\t\t\tfloat_to_s16 (buf, new_snd, samples);\n\t\t\tbreak;\n\t\tcase SFMT_U32:\n\t\t\t*new_size = samples * 4;\n\t\t\tnew_snd = (char *)xmalloc (*new_size);\n\t\t\tfloat_to_u32 (buf, (unsigned char *)new_snd, samples);\n\t\t\tbreak;\n\t\tcase SFMT_S32:\n\t\t\t*new_size = samples * 4;\n\t\t\tnew_snd = (char *)xmalloc (*new_size);\n\t\t\tfloat_to_s32 (buf, new_snd, samples);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\terror (\"Can't convert from float to %s!\",\n\t\t\t       sfmt_str (fmt, fmt_name, sizeof (fmt_name)));\n\t\t\tabort ();\n\t}\n\n\treturn new_snd;\n}\n\nstatic void change_sign_8 (uint8_t *buf, const size_t samples)\n{\n\tsize_t i;\n\n\tfor (i = 0; i < samples; i++)\n\t\t*buf++ ^= 1 << 7;\n}\n\nstatic void change_sign_16 (uint16_t *buf, const size_t samples)\n{\n\tsize_t i;\n\n\tfor (i = 0; i < samples; i++)\n\t\t*buf++ ^= 1 << 15;\n}\n\nstatic void change_sign_32 (uint32_t *buf, const size_t samples)\n{\n\tsize_t i;\n\n\tfor (i = 0; i < samples; i++)\n\t\t*buf++ ^= 1 << 31;\n}\n\n/* Change the signs of samples in format *fmt.  Also changes fmt to the new\n * format. */\nstatic void change_sign (char *buf, const size_t size, long *fmt)\n{\n\tchar fmt_name[SFMT_STR_MAX];\n\n\tswitch (*fmt & SFMT_MASK_FORMAT) {\n\t\tcase SFMT_S8:\n\t\tcase SFMT_U8:\n\t\t\tchange_sign_8 ((uint8_t *)buf, size);\n\t\t\tif (*fmt & SFMT_S8)\n\t\t\t\t*fmt = sfmt_set_fmt (*fmt, SFMT_U8);\n\t\t\telse\n\t\t\t\t*fmt = sfmt_set_fmt (*fmt, SFMT_S8);\n\t\t\tbreak;\n\t\tcase SFMT_S16:\n\t\tcase SFMT_U16:\n\t\t\tchange_sign_16 ((uint16_t *)buf, size / 2);\n\t\t\tif (*fmt & SFMT_S16)\n\t\t\t\t*fmt = sfmt_set_fmt (*fmt, SFMT_U16);\n\t\t\telse\n\t\t\t\t*fmt = sfmt_set_fmt (*fmt, SFMT_S16);\n\t\t\tbreak;\n\t\tcase SFMT_S32:\n\t\tcase SFMT_U32:\n\t\t\tchange_sign_32 ((uint32_t *)buf, size/4);\n\t\t\tif (*fmt & SFMT_S32)\n\t\t\t\t*fmt = sfmt_set_fmt (*fmt, SFMT_U32);\n\t\t\telse\n\t\t\t\t*fmt = sfmt_set_fmt (*fmt, SFMT_S32);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\terror (\"Request for changing sign of unknown format: %s\",\n\t\t\t       sfmt_str (*fmt, fmt_name, sizeof (fmt_name)));\n\t\t\tabort ();\n\t}\n}\n\nvoid audio_conv_bswap_16 (int16_t *buf, const size_t num)\n{\n\tsize_t i;\n\n\tfor (i = 0; i < num; i++)\n\t\tbuf[i] = bswap_16 (buf[i]);\n}\n\nvoid audio_conv_bswap_32 (int32_t *buf, const size_t num)\n{\n\tsize_t i;\n\n\tfor (i = 0; i < num; i++)\n\t\tbuf[i] = bswap_32 (buf[i]);\n}\n\n/* Swap endianness of fixed point samples. */\nstatic void swap_endian (char *buf, const size_t size, const long fmt)\n{\n\tif ((fmt & (SFMT_S8 | SFMT_U8 | SFMT_FLOAT)))\n\t\treturn;\n\n\tswitch (fmt & SFMT_MASK_FORMAT) {\n\t\tcase SFMT_S16:\n\t\tcase SFMT_U16:\n\t\t\taudio_conv_bswap_16 ((int16_t *)buf, size / 2);\n\t\t\tbreak;\n\t\tcase SFMT_S32:\n\t\tcase SFMT_U32:\n\t\t\taudio_conv_bswap_32 ((int32_t *)buf, size / 4);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\terror (\"Can't convert to native endian!\");\n\t\t\tabort (); /* we can't do anything smarter */\n\t}\n}\n\n/* Initialize the audio_conversion structure for conversion between parameters\n * from and to. Return 0 on error. */\nint audio_conv_new (struct audio_conversion *conv,\n\t\tconst struct sound_params *from,\n\t\tconst struct sound_params *to)\n{\n\tassert (from->rate != to->rate || from->fmt != to->fmt\n\t\t\t|| from->channels != to->channels);\n\n\tif (from->channels != to->channels) {\n\n\t\t/* the only conversion we can do */\n\t\tif (!(from->channels == 1 && to->channels == 2)) {\n\t\t\terror (\"Can't change number of channels (%d to %d)!\",\n\t\t\t        from->channels, to->channels);\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tif (from->rate != to->rate) {\n#ifdef HAVE_SAMPLERATE\n\t\tint err;\n\t\tint resample_type = -1;\n\t\tchar *method = options_get_symb (\"ResampleMethod\");\n\n\t\tif (!strcasecmp(method, \"SincBestQuality\"))\n\t\t\tresample_type = SRC_SINC_BEST_QUALITY;\n\t\telse if (!strcasecmp(method, \"SincMediumQuality\"))\n\t\t\tresample_type = SRC_SINC_MEDIUM_QUALITY;\n\t\telse if (!strcasecmp(method, \"SincFastest\"))\n\t\t\tresample_type = SRC_SINC_FASTEST;\n\t\telse if (!strcasecmp(method, \"ZeroOrderHold\"))\n\t\t\tresample_type = SRC_ZERO_ORDER_HOLD;\n\t\telse if (!strcasecmp(method, \"Linear\"))\n\t\t\tresample_type = SRC_LINEAR;\n\t\telse\n\t\t\tfatal (\"Bad ResampleMethod option: %s\", method);\n\n\t\tconv->src_state = src_new (resample_type, to->channels, &err);\n\t\tif (!conv->src_state) {\n\t\t\terror (\"Can't resample from %dHz to %dHz: %s\",\n\t\t\t\t\tfrom->rate, to->rate, src_strerror (err));\n\t\t\treturn 0;\n\t\t}\n#else\n\t\terror (\"Resampling not supported!\");\n\t\treturn 0;\n#endif\n\t}\n#ifdef HAVE_SAMPLERATE\n\telse\n\t\tconv->src_state = NULL;\n#endif\n\n\tconv->from = *from;\n\tconv->to = *to;\n\n#ifdef HAVE_SAMPLERATE\n\tconv->resample_buf = NULL;\n\tconv->resample_buf_nsamples = 0;\n#endif\n\n\treturn 1;\n}\n\n#ifdef HAVE_SAMPLERATE\nstatic float *resample_sound (struct audio_conversion *conv, const float *buf,\n\t\tconst size_t samples, const int nchannels, size_t *resampled_samples)\n{\n\tSRC_DATA resample_data;\n\tfloat *output;\n\tfloat *new_input_start;\n\tint output_samples = 0;\n\n\tresample_data.end_of_input = 0;\n\tresample_data.src_ratio = conv->to.rate / (double)conv->from.rate;\n\n\tresample_data.input_frames = samples / nchannels\n\t\t+ conv->resample_buf_nsamples / nchannels;\n\tresample_data.output_frames = resample_data.input_frames\n\t\t* resample_data.src_ratio;\n\n\tassert (conv->resample_buf || conv->resample_buf_nsamples == 0);\n\tconv->resample_buf = xrealloc (conv->resample_buf,\n\t                               sizeof(float) * nchannels *\n\t                               resample_data.input_frames);\n\tnew_input_start = conv->resample_buf + conv->resample_buf_nsamples;\n\n\toutput = (float *)xmalloc (sizeof(float) * resample_data.output_frames\n\t\t\t\t* nchannels);\n\n\t/*debug (\"Resampling %lu bytes of data by ratio %f\", (unsigned long)size,\n\t\t\tresample_data.src_ratio);*/\n\n\tmemcpy (new_input_start, buf, samples * sizeof(float));\n\tresample_data.data_in = conv->resample_buf;\n\tresample_data.data_out = output;\n\n\tdo {\n\t\tint err;\n\n\t\tif ((err = src_process(conv->src_state, &resample_data))) {\n\t\t\terror (\"Can't resample: %s\", src_strerror (err));\n\t\t\tfree (output);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tresample_data.data_in += resample_data.input_frames_used\n\t\t\t* nchannels;\n\t\tresample_data.input_frames -= resample_data.input_frames_used;\n\t\tresample_data.data_out += resample_data.output_frames_gen\n\t\t\t* nchannels;\n\t\tresample_data.output_frames -= resample_data.output_frames_gen;\n\t\toutput_samples += resample_data.output_frames_gen * nchannels;\n\t} while (resample_data.input_frames && resample_data.output_frames_gen\n\t\t\t&& resample_data.output_frames);\n\n\t*resampled_samples = output_samples;\n\n\tif (resample_data.input_frames) {\n\t\tconv->resample_buf_nsamples = resample_data.input_frames\n\t\t\t* nchannels;\n\t\tif (conv->resample_buf != resample_data.data_in) {\n\t\t\tfloat *new;\n\n\t\t\tnew = (float *)xmalloc (sizeof(float) *\n\t\t\t\t\tconv->resample_buf_nsamples);\n\t\t\tmemcpy (new, resample_data.data_in, sizeof(float) *\n\t\t\t\t\tconv->resample_buf_nsamples);\n\t\t\tfree (conv->resample_buf);\n\t\t\tconv->resample_buf = new;\n\t\t}\n\t}\n\telse {\n\t\tfree (conv->resample_buf);\n\t\tconv->resample_buf = NULL;\n\t\tconv->resample_buf_nsamples = 0;\n\t}\n\n\treturn output;\n}\n#endif\n\n/* Double the channels from */\nstatic char *mono_to_stereo (const char *mono, const size_t size,\n\t\tconst long format)\n{\n\tint Bps = sfmt_Bps (format);\n\tsize_t i;\n\tchar *stereo;\n\n\tstereo = (char *)xmalloc (size * 2);\n\n\tfor (i = 0; i < size; i += Bps) {\n\t\tmemcpy (stereo + (i * 2), mono + i, Bps);\n\t\tmemcpy (stereo + (i * 2 + Bps), mono + i, Bps);\n\t}\n\n\treturn stereo;\n}\n\nstatic int16_t *s32_to_s16 (int32_t *in, const size_t samples)\n{\n\tsize_t i;\n\tint16_t *new;\n\n\tnew = (int16_t *)xmalloc (samples * 2);\n\n\tfor (i = 0; i < samples; i++)\n\t\tnew[i] = in[i] >> 16;\n\n\treturn new;\n}\n\nstatic uint16_t *u32_to_u16 (uint32_t *in, const size_t samples)\n{\n\tsize_t i;\n\tuint16_t *new;\n\n\tnew = (uint16_t *)xmalloc (samples * 2);\n\n\tfor (i = 0; i < samples; i++)\n\t\tnew[i] = in[i] >> 16;\n\n\treturn new;\n}\n\n/* Do the sound conversion.  buf of length size is the sample buffer to\n * convert and the size of the converted sound is put into *conv_len.\n * Return the converted sound in malloc()ed memory. */\nchar *audio_conv (struct audio_conversion *conv, const char *buf,\n\t\tconst size_t size, size_t *conv_len)\n{\n\tchar *curr_sound;\n\tlong curr_sfmt = conv->from.fmt;\n\n\t*conv_len = size;\n\n\tcurr_sound = (char *)xmalloc (size);\n\tmemcpy (curr_sound, buf, size);\n\n\tif (!(curr_sfmt & SFMT_NE)) {\n\t\tswap_endian (curr_sound, *conv_len, curr_sfmt);\n\t\tcurr_sfmt = sfmt_set_endian (curr_sfmt, SFMT_NE);\n\t}\n\n\t/* Special case (optimization): if we only need to convert 32bit samples\n\t * to 16bit, we can do it very simply and quickly. */\n\tif ((curr_sfmt & (SFMT_S32 | SFMT_U32)) &&\n\t    (conv->to.fmt & (SFMT_S16 | SFMT_U16)) &&\n\t    conv->from.rate == conv->to.rate) {\n\t\tchar *new_sound;\n\n\t\tif ((curr_sfmt & SFMT_MASK_FORMAT) == SFMT_S32) {\n\t\t\tnew_sound = (char *)s32_to_s16 ((int32_t *)curr_sound,\n\t\t\t\t\t*conv_len / 4);\n\t\t\tcurr_sfmt = sfmt_set_fmt (curr_sfmt, SFMT_S16);\n\t\t}\n\t\telse {\n\t\t\tnew_sound = (char *)u32_to_u16 ((uint32_t *)curr_sound,\n\t\t\t\t\t*conv_len / 4);\n\t\t\tcurr_sfmt = sfmt_set_fmt (curr_sfmt, SFMT_U16);\n\t\t}\n\n\t\tif (curr_sound != buf)\n\t\t\tfree (curr_sound);\n\t\tcurr_sound = new_sound;\n\t\t*conv_len /= 2;\n\n\t\tlogit (\"Fast conversion!\");\n\t}\n\n\t/* convert to float if necessary */\n\tif ((conv->from.rate != conv->to.rate\n\t\t\t\t|| (conv->to.fmt & SFMT_MASK_FORMAT) == SFMT_FLOAT\n\t\t\t\t|| !sfmt_same_bps(conv->to.fmt, curr_sfmt))\n\t\t\t&& (curr_sfmt & SFMT_MASK_FORMAT) != SFMT_FLOAT) {\n\t\tchar *new_sound;\n\n\t\tnew_sound = (char *)fixed_to_float (curr_sound, *conv_len,\n\t\t\t\tcurr_sfmt, conv_len);\n\t\tcurr_sfmt = sfmt_set_fmt (curr_sfmt, SFMT_FLOAT);\n\n\t\tif (curr_sound != buf)\n\t\t\tfree (curr_sound);\n\t\tcurr_sound = new_sound;\n\t}\n\n#ifdef HAVE_SAMPLERATE\n\tif (conv->from.rate != conv->to.rate) {\n\t\tchar *new_sound = (char *)resample_sound (conv,\n\t\t\t\t(float *)curr_sound,\n\t\t\t\t*conv_len / sizeof(float), conv->to.channels,\n\t\t\t\tconv_len);\n\t\t*conv_len *= sizeof(float);\n\t\tif (curr_sound != buf)\n\t\t\tfree (curr_sound);\n\t\tcurr_sound = new_sound;\n\t}\n#endif\n\n\tif ((curr_sfmt & SFMT_MASK_FORMAT)\n\t\t\t!= (conv->to.fmt & SFMT_MASK_FORMAT)) {\n\n\t\tif (sfmt_same_bps(curr_sfmt, conv->to.fmt))\n\t\t\tchange_sign (curr_sound, size, &curr_sfmt);\n\t\telse {\n\t\t\tchar *new_sound;\n\n\t\t\tassert (curr_sfmt & SFMT_FLOAT);\n\n\t\t\tnew_sound = float_to_fixed ((float *)curr_sound,\n\t\t\t\t\t*conv_len / sizeof(float),\n\t\t\t\t\tconv->to.fmt, conv_len);\n\t\t\tcurr_sfmt = sfmt_set_fmt (curr_sfmt, conv->to.fmt);\n\n\t\t\tif (curr_sound != buf)\n\t\t\t\tfree (curr_sound);\n\t\t\tcurr_sound = new_sound;\n\t\t}\n\t}\n\n\tif ((curr_sfmt & SFMT_MASK_ENDIANNESS)\n\t\t\t!= (conv->to.fmt & SFMT_MASK_ENDIANNESS)) {\n\t\tswap_endian (curr_sound, *conv_len, curr_sfmt);\n\t\tcurr_sfmt = sfmt_set_endian (curr_sfmt,\n\t\t\t\tconv->to.fmt & SFMT_MASK_ENDIANNESS);\n\t}\n\n\tif (conv->from.channels == 1 && conv->to.channels == 2) {\n\t\tchar *new_sound;\n\n\t\tnew_sound = mono_to_stereo (curr_sound, *conv_len, curr_sfmt);\n\t\t*conv_len *= 2;\n\n\t\tif (curr_sound != buf)\n\t\t\tfree (curr_sound);\n\t\tcurr_sound = new_sound;\n\t}\n\n\treturn curr_sound;\n}\n\nvoid audio_conv_destroy (struct audio_conversion *conv ASSERT_ONLY)\n{\n\tassert (conv != NULL);\n\n#ifdef HAVE_SAMPLERATE\n\tif (conv->resample_buf)\n\t\tfree (conv->resample_buf);\n\tif (conv->src_state)\n\t\tsrc_delete (conv->src_state);\n#endif\n}\n"
  },
  {
    "path": "audio_conversion.h",
    "content": "#ifndef AUDIO_CONVERSION_H\n#define AUDIO_CONVERSION_H\n\n#ifdef HAVE_STDINT_H\n# include <stdint.h>\n#endif\n\n#include <sys/types.h>\n\n#ifdef HAVE_SAMPLERATE\n# include <samplerate.h>\n#endif\n\n#include \"audio.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct audio_conversion\n{\n\tstruct sound_params from;\n\tstruct sound_params to;\n\n#ifdef HAVE_SAMPLERATE\n\tSRC_STATE *src_state;\n\tfloat *resample_buf;\n\tsize_t resample_buf_nsamples; /* in samples ( sizeof(float) ) */\n#endif\n\n};\n\nint audio_conv_new (struct audio_conversion *conv,\n\t\tconst struct sound_params *from,\n\t\tconst struct sound_params *to);\nchar *audio_conv (struct audio_conversion *conv,\n\t\tconst char *buf, const size_t size, size_t *conv_len);\nvoid audio_conv_destroy (struct audio_conversion *conv);\n\nvoid audio_conv_bswap_16 (int16_t *buf, const size_t num);\nvoid audio_conv_bswap_32 (int32_t *buf, const size_t num);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "common.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 - 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n# undef malloc\n#endif\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <strings.h>\n#include <ctype.h>\n#include <assert.h>\n#include <unistd.h>\n#include <time.h>\n#include <sys/types.h>\n#include <pwd.h>\n#include <pthread.h>\n#include <signal.h>\n#include <errno.h>\n#ifdef HAVE_SYSLOG\n#include <syslog.h>\n#endif\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"interface.h\"\n#include \"interface_elements.h\"\n#include \"log.h\"\n#include \"options.h\"\n\nstatic int im_server = 0; /* Am I the server? */\n\nvoid internal_error (const char *file, int line, const char *function,\n                     const char *format, ...)\n{\n\tint saved_errno = errno;\n\tva_list va;\n\tchar *msg;\n\n\tva_start (va, format);\n\tmsg = format_msg_va (format, va);\n\tva_end (va);\n\n\tif (im_server)\n\t\tserver_error (file, line, function, msg);\n\telse\n\t\tinterface_error (msg);\n\n\tfree (msg);\n\n\terrno = saved_errno;\n}\n\n/* End program with a message. Use when an error occurs and we can't recover.\n * If we're the server, then also log the message to the system log. */\nvoid internal_fatal (const char *file LOGIT_ONLY, int line LOGIT_ONLY,\n                 const char *function LOGIT_ONLY, const char *format, ...)\n{\n\tva_list va;\n\tchar *msg;\n\n\twindows_reset ();\n\n\tva_start (va, format);\n\tmsg = format_msg_va (format, va);\n\tfprintf (stderr, \"\\nFATAL_ERROR: %s\\n\\n\", msg);\n#ifndef NDEBUG\n\tinternal_logit (file, line, function, \"FATAL ERROR: %s\", msg);\n#endif\n\tva_end (va);\n\n\tlog_close ();\n\n#ifdef HAVE_SYSLOG\n\tif (im_server)\n\t\tsyslog (LOG_USER|LOG_ERR, \"%s\", msg);\n#endif\n\n\tfree (msg);\n\n\texit (EXIT_FATAL);\n}\n\nvoid *xmalloc (size_t size)\n{\n\tvoid *p;\n\n#ifndef HAVE_MALLOC\n\tsize = MAX(1, size);\n#endif\n\n\tif ((p = malloc(size)) == NULL)\n\t\tfatal (\"Can't allocate memory!\");\n\treturn p;\n}\n\nvoid *xcalloc (size_t nmemb, size_t size)\n{\n\tvoid *p;\n\n\tif ((p = calloc(nmemb, size)) == NULL)\n\t\tfatal (\"Can't allocate memory!\");\n\treturn p;\n}\n\nvoid *xrealloc (void *ptr, const size_t size)\n{\n\tvoid *p;\n\n\tp = realloc (ptr, size);\n\tif (!p && size != 0)\n\t\tfatal (\"Can't allocate memory!\");\n\n\treturn p;\n}\n\nchar *xstrdup (const char *s)\n{\n\tchar *n;\n\n\tif (s && (n = strdup(s)) == NULL)\n\t\tfatal (\"Can't allocate memory!\");\n\n\treturn s ? n : NULL;\n}\n\n/* Sleep for the specified number of 'ticks'. */\nvoid xsleep (size_t ticks, size_t ticks_per_sec)\n{\n\tassert(ticks_per_sec > 0);\n\n\tif (ticks > 0) {\n\t\tint rc;\n\t\tstruct timespec delay = {.tv_sec = ticks};\n\n\t\tif (ticks_per_sec > 1) {\n\t\t\tuint64_t nsecs;\n\n\t\t\tdelay.tv_sec /= ticks_per_sec;\n\t\t\tnsecs = ticks % ticks_per_sec;\n\n\t\t\tif (nsecs > 0) {\n\t\t\t\tassert (nsecs < UINT64_MAX / UINT64_C(1000000000));\n\n\t\t\t\tdelay.tv_nsec = nsecs * UINT64_C(1000000000);\n\t\t\t\tdelay.tv_nsec /= ticks_per_sec;\n\t\t\t}\n\t\t}\n\n\t\tdo {\n\t\t\trc = nanosleep (&delay, &delay);\n\t\t\tif (rc == -1 && errno != EINTR)\n\t\t\t\tfatal (\"nanosleep() failed: %s\", xstrerror (errno));\n\t\t} while (rc != 0);\n\t}\n}\n\n#if !HAVE_DECL_STRERROR_R\nstatic pthread_mutex_t xstrerror_mtx = PTHREAD_MUTEX_INITIALIZER;\n#endif\n\n#if !HAVE_DECL_STRERROR_R\n/* Return error message in malloc() buffer (for strerror(3)). */\nchar *xstrerror (int errnum)\n{\n\tchar *result;\n\n\t/* The client is not threaded. */\n\tif (!im_server)\n\t\treturn xstrdup (strerror (errnum));\n\n\tLOCK (xstrerror_mtx);\n\n\tresult = xstrdup (strerror (errnum));\n\n\tUNLOCK (xstrerror_mtx);\n\n\treturn result;\n}\n#endif\n\n#if HAVE_DECL_STRERROR_R\n/* Return error message in malloc() buffer (for strerror_r(3)). */\nchar *xstrerror (int errnum)\n{\n\tint saved_errno = errno;\n\tchar *err_str, err_buf[256];\n\n#ifdef STRERROR_R_CHAR_P\n\t/* strerror_r(3) is GNU variant. */\n\terr_str = strerror_r (errnum, err_buf, sizeof (err_buf));\n#else\n\t/* strerror_r(3) is XSI variant. */\n\tif (strerror_r (errnum, err_buf, sizeof (err_buf)) < 0) {\n\t\tlogit (\"Error %d occurred obtaining error description for %d\",\n\t\t        errno, errnum);\n\t\tstrcpy (err_buf, \"Error occurred obtaining error description\");\n\t}\n\terr_str = err_buf;\n#endif\n\n\terrno = saved_errno;\n\n\treturn xstrdup (err_str);\n}\n#endif\n\n/* A signal(2) which is both thread safe and POSIXly well defined. */\nvoid xsignal (int signum, void (*func)(int))\n{\n\tstruct sigaction act;\n\n\tact.sa_handler = func;\n\tact.sa_flags = 0;\n\tsigemptyset (&act.sa_mask);\n\n\tif (sigaction(signum, &act, 0) == -1)\n\t\tfatal (\"sigaction() failed: %s\", xstrerror (errno));\n}\n\nvoid set_me_server ()\n{\n\tim_server = 1;\n}\n\nchar *str_repl (char *target, const char *oldstr, const char *newstr)\n{\n\tsize_t oldstr_len = strlen(oldstr);\n\tsize_t newstr_len = strlen(newstr);\n\tsize_t target_len = strlen(target);\n\tsize_t target_max = target_len;\n\tsize_t s, p;\n\tchar *needle;\n\n\tfor (s = 0; (needle = strstr(target + s, oldstr)) != NULL;\n\t            s = p + newstr_len) {\n\t\ttarget_len += newstr_len - oldstr_len;\n\t\tp = needle - target;\n\t\tif (target_len + 1 > target_max) {\n\t\t\ttarget_max = MAX(target_len + 1, target_max * 2);\n\t\t\ttarget = xrealloc(target, target_max);\n\t\t}\n\t\tmemmove(target + p + newstr_len, target + p + oldstr_len,\n\t\t                                 target_len - p - newstr_len + 1);\n\t\tmemcpy(target + p, newstr, newstr_len);\n\t}\n\n\ttarget = xrealloc(target, target_len + 1);\n\n\treturn target;\n}\n\n/* Extract a substring starting at 'src' for length 'len' and remove\n * any leading and trailing whitespace.  Return NULL if unable.  */\nchar *trim (const char *src, size_t len)\n{\n\tchar *result;\n\tconst char *first, *last;\n\n\tfor (last = &src[len - 1]; last >= src; last -= 1) {\n\t\tif (!isspace (*last))\n\t\t\tbreak;\n\t}\n\tif (last < src)\n\t\treturn NULL;\n\n\tfor (first = src; first <= last; first += 1) {\n\t\tif (!isspace (*first))\n\t\t\tbreak;\n\t}\n\tif (first > last)\n\t\treturn NULL;\n\n\tlast += 1;\n\tresult = xcalloc (last - first + 1, sizeof (char));\n\tstrncpy (result, first, last - first);\n\tresult[last - first] = 0x00;\n\n\treturn result;\n}\n\n/* Format argument values according to 'format' and return it as a\n * malloc()ed string. */\nchar *format_msg (const char *format, ...)\n{\n\tchar *result;\n\tva_list va;\n\n\tva_start (va, format);\n\tresult = format_msg_va (format, va);\n\tva_end (va);\n\n\treturn result;\n}\n\n/* Format a vararg list according to 'format' and return it as a\n * malloc()ed string. */\nchar *format_msg_va (const char *format, va_list va)\n{\n\tint len;\n\tchar *result;\n\tva_list va_copy;\n\n\tva_copy (va_copy, va);\n\tlen = vsnprintf (NULL, 0, format, va_copy) + 1;\n\tva_end (va_copy);\n\tresult = xmalloc (len);\n\tvsnprintf (result, len, format, va);\n\n\treturn result;\n}\n\n/* Return true iff the argument would be a syntactically valid symbol.\n * (Note that the so-called \"peculiar indentifiers\" are disallowed here.) */\nbool is_valid_symbol (const char *candidate)\n{\n\tsize_t len;\n\tbool result;\n\tconst char *first = \"+-.0123456789@\";\n\tconst char *valid = \"abcdefghijklmnopqrstuvwxyz\"\n\t                    \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\t                    \"0123456789\"\n\t                    \"@?!.+-*/<=>:$%^&_~\";\n\n\tresult = false;\n\tlen = strlen (candidate);\n\tif (len > 0 && len == strspn (candidate, valid) &&\n\t               strchr (first, candidate[0]) == NULL)\n\t\tresult = true;\n\n\treturn result;\n}\n\n/* Return path to a file in MOC config directory. NOT THREAD SAFE */\nchar *create_file_name (const char *file)\n{\n\tint rc;\n\tstatic char fname[PATH_MAX];\n\tchar *moc_dir = options_get_str (\"MOCDir\");\n\n\tif (moc_dir[0] == '~')\n\t\trc = snprintf(fname, sizeof(fname), \"%s/%s/%s\", get_home (),\n\t\t              (moc_dir[1] == '/') ? moc_dir + 2 : moc_dir + 1,\n\t\t              file);\n\telse\n\t\trc = snprintf(fname, sizeof(fname), \"%s/%s\", moc_dir, file);\n\n\tif (rc >= ssizeof(fname))\n\t\tfatal (\"Path too long!\");\n\n\treturn fname;\n}\n\nint get_realtime (struct timespec *ts)\n{\n\tint result;\n#ifdef HAVE_CLOCK_GETTIME\n\tresult = clock_gettime (CLOCK_REALTIME, ts);\n#else\n\tstruct timeval tv;\n\n\tresult = gettimeofday (&tv, NULL);\n\tif (result == 0) {\n\t\tts->tv_sec = tv.tv_sec;\n\t\tts->tv_nsec = tv.tv_usec * 1000L;\n\t}\n#endif\n    return result;\n}\n\n/* Convert time in second to min:sec text format.\n   'buff' must be at least 32 chars long. */\nvoid sec_to_min (char *buff, const int seconds)\n{\n\tassert (seconds >= 0);\n\n\tif (seconds < 6000) {\n\n\t\t/* the time is less than 99:59 */\n\t\tint min, sec;\n\n\t\tmin = seconds / 60;\n\t\tsec = seconds % 60;\n\n\t\tsnprintf (buff, 32, \"%02d:%02d\", min, sec);\n\t}\n\telse if (seconds < 10000 * 60)\n\n\t\t/* the time is less than 9999 minutes */\n\t\tsnprintf (buff, 32, \"%4dm\", seconds/60);\n\telse\n\t\tstrcpy (buff, \"!!!!!\");\n}\n\n/* Determine and return the path of the user's home directory. */\nconst char *get_home ()\n{\n\tstatic const char *home = NULL;\n\tstruct passwd *passwd;\n\n\tif (home == NULL) {\n\t\thome = xstrdup (getenv (\"HOME\"));\n\t\tif (home == NULL) {\n\t\t\terrno = 0;\n\t\t\tpasswd = getpwuid (geteuid ());\n\t\t\tif (passwd)\n\t\t\t\thome = xstrdup (passwd->pw_dir);\n\t\t\telse\n\t\t\t\tif (errno != 0) {\n\t\t\t\t\tchar *err = xstrerror (errno);\n\t\t\t\t\tlogit (\"getpwuid(%d): %s\", geteuid (), err);\n\t\t\t\t\tfree (err);\n\t\t\t\t}\n\t\t}\n\t}\n\n\treturn home;\n}\n\nvoid common_cleanup ()\n{\n#if !HAVE_DECL_STRERROR_R\n\tint rc;\n\n\tif (im_server)\n\t\treturn;\n\n\trc = pthread_mutex_destroy (&xstrerror_mtx);\n\tif (rc != 0)\n\t\tlogit (\"Can't destroy xstrerror_mtx: %s\", strerror (rc));\n#endif\n}\n"
  },
  {
    "path": "common.h",
    "content": "/*\n * The purpose of this header is to provide common functions and macros\n * used throughout MOC code.  It also provides (x-prefixed) functions\n * which augment or adapt their respective system functions with error\n * checking and the like.\n */\n\n#ifndef COMMON_H\n#define COMMON_H\n\n#include <stdlib.h>\n#include <stdarg.h>\n#include <stdbool.h>\n#include <limits.h>\n\n#include \"compat.h\"\n\n/* Suppress overly-enthusiastic GNU variadic macro extensions warning. */\n#if defined(__clang__) && HAVE_VARIADIC_MACRO_WARNING\n# pragma clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"\n#endif\n\nstruct timespec;\n\n#ifdef HAVE_FUNC_ATTRIBUTE_FORMAT\n# define ATTR_PRINTF(x,y) __attribute__ ((format (printf, x, y)))\n#else\n# define ATTR_PRINTF(...)\n#endif\n\n#ifdef HAVE_FUNC_ATTRIBUTE_NORETURN\n# define ATTR_NORETURN __attribute__((noreturn))\n#else\n# define ATTR_NORETURN\n#endif\n\n#ifdef HAVE_VAR_ATTRIBUTE_UNUSED\n# define ATTR_UNUSED __attribute__((unused))\n#else\n# define ATTR_UNUSED\n#endif\n\n#ifndef GCC_VERSION\n#define GCC_VERSION (__GNUC__ * 10000 + \\\n                     __GNUC_MINOR__ * 100 + \\\n                     __GNUC_PATCHLEVEL__)\n#endif\n\n/* These macros allow us to use the appropriate method for manipulating\n * GCC's diagnostic pragmas depending on the compiler's version. */\n#if GCC_VERSION >= 40200\n# define GCC_DIAG_STR(s) #s\n# define GCC_DIAG_JOINSTR(x,y) GCC_DIAG_STR(x ## y)\n# define GCC_DIAG_DO_PRAGMA(x) _Pragma (#x)\n# define GCC_DIAG_PRAGMA(x) GCC_DIAG_DO_PRAGMA(GCC diagnostic x)\n# if GCC_VERSION >= 40600\n#  define GCC_DIAG_OFF(x) GCC_DIAG_PRAGMA(push) \\\n                          GCC_DIAG_PRAGMA(ignored GCC_DIAG_JOINSTR(-W,x))\n#  define GCC_DIAG_ON(x)  GCC_DIAG_PRAGMA(pop)\n# else\n#  define GCC_DIAG_OFF(x) GCC_DIAG_PRAGMA(ignored GCC_DIAG_JOINSTR(-W,x))\n#  define GCC_DIAG_ON(x)  GCC_DIAG_PRAGMA(warning GCC_DIAG_JOINSTR(-W,x))\n# endif\n#else\n# define GCC_DIAG_OFF(x)\n# define GCC_DIAG_ON(x)\n#endif\n\n#ifdef HAVE_FORMAT_TRUNCATION_WARNING\n# define SUPPRESS_FORMAT_TRUNCATION_WARNING GCC_DIAG_OFF(format-truncation)\n# define UNSUPPRESS_FORMAT_TRUNCATION_WARNING GCC_DIAG_ON(format-truncation)\n#else\n# define SUPPRESS_FORMAT_TRUNCATION_WARNING\n# define UNSUPPRESS_FORMAT_TRUNCATION_WARNING\n#endif\n\n#define CONFIG_DIR      \".moc\"\n#define LOCK(mutex)     pthread_mutex_lock (&mutex)\n#define UNLOCK(mutex)   pthread_mutex_unlock (&mutex)\n#define ARRAY_SIZE(x)   (sizeof(x)/sizeof(x[0]))\n#define ssizeof(x)      ((ssize_t) sizeof(x))\n\n/* Maximal string length sent/received. */\n#define MAX_SEND_STRING\t4096\n\n/* Exit status on fatal error. */\n#define EXIT_FATAL\t2\n\n#ifndef MIN\n#define MIN(a, b) ((a) < (b) ? (a) : (b))\n#endif\n\n#ifndef MAX\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\n#endif\n\n#ifndef LIMIT\n#define LIMIT(val, lim) ((val) >= 0 && (val) < (lim))\n#endif\n\n#ifndef RANGE\n#define RANGE(min, val, max) ((val) >= (min) && (val) <= (max))\n#endif\n\n#ifndef CLAMP\n#define CLAMP(min, val, max) ((val) < (min) ? (min) : \\\n                              (val) > (max) ? (max) : (val))\n#endif\n\n#ifdef NDEBUG\n#define error(...) \\\n\tinternal_error (NULL, 0, NULL, ## __VA_ARGS__)\n#define fatal(...) \\\n\tinternal_fatal (NULL, 0, NULL, ## __VA_ARGS__)\n#define ASSERT_ONLY ATTR_UNUSED\n#else\n#define error(...) \\\n\tinternal_error (__FILE__, __LINE__, __func__, ## __VA_ARGS__)\n#define fatal(...) \\\n\tinternal_fatal (__FILE__, __LINE__, __func__, ## __VA_ARGS__)\n#define ASSERT_ONLY\n#endif\n\n#ifndef STRERROR_FN\n# define STRERROR_FN xstrerror\n#endif\n\n#define error_errno(format, errnum) \\\n\tdo { \\\n\t\tchar *err##__LINE__ = STRERROR_FN (errnum); \\\n\t\terror (format \": %s\", err##__LINE__); \\\n\t\tfree (err##__LINE__); \\\n\t} while (0)\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid *xmalloc (size_t size);\nvoid *xcalloc (size_t nmemb, size_t size);\nvoid *xrealloc (void *ptr, const size_t size);\nchar *xstrdup (const char *s);\nvoid xsleep (size_t ticks, size_t ticks_per_sec);\nchar *xstrerror (int errnum);\nvoid xsignal (int signum, void (*func)(int));\n\nvoid internal_error (const char *file, int line, const char *function,\n                     const char *format, ...) ATTR_PRINTF(4, 5);\nvoid internal_fatal (const char *file, int line, const char *function,\n                     const char *format, ...) ATTR_NORETURN ATTR_PRINTF(4, 5);\nvoid set_me_server ();\nchar *str_repl (char *target, const char *oldstr, const char *newstr);\nchar *trim (const char *src, size_t len);\nchar *format_msg (const char *format, ...);\nchar *format_msg_va (const char *format, va_list va);\nbool is_valid_symbol (const char *candidate);\nchar *create_file_name (const char *file);\nint get_realtime (struct timespec *ts);\nvoid sec_to_min (char *buff, const int seconds);\nconst char *get_home ();\nvoid common_cleanup ();\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "compat.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n/* Various functions which some systems lack. */\n\n#ifndef HAVE_STRCASESTR\n#include <string.h>\n#include <ctype.h>\n\n/* Case insensitive version of strstr(). */\nchar *strcasestr (const char *haystack, const char *needle)\n{\n\tchar *haystack_i, *needle_i;\n\tchar *c;\n\tchar *res;\n\n\thaystack_i = xstrdup (haystack);\n\tneedle_i = xstrdup (needle);\n\n\tc = haystack_i;\n\twhile (*c) {\n\t\t*c = tolower (*c);\n\t\tc++;\n\t}\n\n\tc = needle_i;\n\twhile (*c) {\n\t\t*c = tolower (*c);\n\t\tc++;\n\t}\n\n\tres = strstr (haystack_i, needle_i);\n\tfree (haystack_i);\n\tfree (needle_i);\n\treturn res ? res - haystack_i + (char *)haystack : NULL;\n}\n#endif\n\n/* This is required to prevent an \"empty translation unit\" warning\n   if neither strcasestr() nor clock_gettime() get defined. */\n#if defined(HAVE_STRCASESTR) && defined(HAVE_CLOCK_GETTIME)\nint compat_is_empty;\n#endif\n"
  },
  {
    "path": "compat.h",
    "content": "/*\n * The purpose of this header is to provide functions and macros which\n * MOC code expects but which are missing or broken on the host system.\n *\n * This header should be included by all code before any other MOC\n * headers (except 'compiler.h').  Therefore, it is included once by\n * 'common.h' which is itself included by all code.\n */\n\n#ifndef COMPAT_H\n#define COMPAT_H\n\n#ifdef HAVE_BYTESWAP_H\n# include <byteswap.h>\n#else\n/* Given an unsigned 16-bit argument X, return the value corresponding to\n   X with reversed byte order.  */\n# define bswap_16(x) ((((x) & 0x00FF) << 8) | \\\n                      (((x) & 0xFF00) >> 8))\n\n/* Given an unsigned 32-bit argument X, return the value corresponding to\n   X with reversed byte order.  */\n# define bswap_32(x) ((((x) & 0x000000FF) << 24) | \\\n                      (((x) & 0x0000FF00) << 8) | \\\n                      (((x) & 0x00FF0000) >> 8) | \\\n                      (((x) & 0xFF000000) >> 24))\n#endif\n\n#ifndef SUN_LEN\n#define SUN_LEN(p) \\\n        ((sizeof *(p)) - sizeof((p)->sun_path) + strlen ((p)->sun_path))\n#endif\n\n/* Maximum path length, we don't consider exceptions like mounted NFS */\n#ifndef PATH_MAX\n# if defined(_POSIX_PATH_MAX)\n#  define PATH_MAX\t_POSIX_PATH_MAX /* Posix */\n# elif defined(MAXPATHLEN)\n#  define PATH_MAX\tMAXPATHLEN      /* Solaris? Also linux...*/\n# else\n#  define PATH_MAX\t4096             /* Suppose, we have 4096 */\n# endif\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if !HAVE_DECL_STRCASESTR && !defined(__cplusplus)\nchar *strcasestr (const char *haystack, const char *needle);\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "compiler.h",
    "content": "/*\n * The purpose of this header is to configure the compiler and system\n * headers the way we want them to be.  It should be included in every\n * source code file *before* any system headers are included, and thus\n * an include for it is automatically appended to the 'config.h' header\n * by 'configure'.\n *\n * It is also included by 'configure' tests to ensure that any system\n * headers *they* include will be configured consistantly and symbols\n * they declare will not be exposed differently in the tests and the\n * code thus causing the configuration macros defined in 'config.h'\n * to be mismatched with the included system headers.\n *\n * Because it is used in both places, it should not include any code\n * which is relevant only to MOC code.\n */\n\n#ifndef COMPILER_H\n#define COMPILER_H\n\n/* _XOPEN_SOURCE is known to break compilation on OpenBSD. */\n#ifndef OPENBSD\n# if defined(_XOPEN_SOURCE) && _XOPEN_SOURCE < 600\n#  undef _XOPEN_SOURCE\n# endif\n# ifndef _XOPEN_SOURCE\n#  define _XOPEN_SOURCE 600\n# endif\n#endif\n\n/* _XOPEN_SOURCE_EXTENDED is known to break compilation on FreeBSD. */\n#ifndef FREEBSD\n# define _XOPEN_SOURCE_EXTENDED 1\n#endif\n\n/* Require POSIX.1-2001 or better. */\n#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE < 200112L\n# undef _POSIX_C_SOURCE\n#endif\n#ifndef _POSIX_C_SOURCE\n# define _POSIX_C_SOURCE 200112L\n#endif\n\n#endif\n"
  },
  {
    "path": "config.example.in",
    "content": "# This is a configuration file for the MOC player.  It should be named\n# 'config' and placed in the ~/.moc directory.  As this file can specify\n# commands which invoke other applications, MOC will refuse to start if it\n# is not owned by either root or the current user, or if it is writable by\n# anyone other than its owner.\n\n# Comments begin with '#'.  All options are given with their default\n# values, and therefore commented.  If you change the value from the\n# default you must uncomment the line to have the new value take effect.\n#\n# You can use quotes and escape ('\\') in parameters.\n#\n# You can have variable values substituted by enclosing the variable name\n# as \"${...}\".  (This only applies to the portion of the option following\n# the '='.)  Variables are substituted first from the environment then,\n# if not found, from the configuration options.  (Note that the value of\n# a configuration option substituted is that which it has at the time the\n# substitution variable is encountered.)  If there is a naming conflict\n# between an environment and configuration variable, you may be able to\n# resolve it by using lowercase as the environment variable matches are\n# case-sensitive whereas the configuration variables are not.\n#\n# You can also use the form \"${...:-...}\" where the value in the second\n# position will be substituted if the variable name given in the first\n# position is unset or null.\n#\n# So, for example:\n#\n#     MusicDir = /music/${USER:-public}\n#     Fastdir1 = ${MusicDir}/mp3/rock\n#     Fastdir2 = ${MusicDir}/mp3/electronic\n#     Fastdir3 = ${MusicDir}/mp3/rap\n#     Fastdir4 = ${MusicDir}/mp3/etc\n#\n# Variable names are limited to those accepted by the BASH shell; that\n# is, those comprising the upper- and lowercase ASCII characters, digits\n# and the underscore.\n#\n# If you need to use the \"${\" sequence for any other purpose, write \"$${\"\n# and it will be replaced by \"${\" and not treated as a substitution.\n#\n# Some options take lists of strings as their values.  The strings are\n# separated by colons.  Additional strings can be appended to the list\n# using \"+=\" in place of a plain \"=\" to assign the value.  For an example,\n# see the XTerms option.\n#\n# You can override any configuration option when you run MOC using the\n# '-O' command line option:\n#\n#     mocp -O AutoNext=no -O messagelingertime=1 -O XTerms+=xxt:xwt\n#\n# This command line option can be repeated as many times as needed and\n# the configuration option name is not case sensitive.  (Note that MOC\n# does not perform variable substitution on the value of such overridden\n# configuration options.)  Most option values are set before the\n# configuration file is processed (which allows the new values to be\n# picked up by substitutions), however list-valued options are overridden\n# afterwards (which gives the choice of whether the configured values are\n# replaced or added to).\n\n# Remember that the client and server are separate processes and the\n# server will retain the configuration values formed from the environment\n# within which it was originally started.\n\n# Show file titles (title, author, album) instead of file names?\n#ReadTags = yes\n\n# In which directory do you store your music files?  If you specify it\n# you will be able to jump straight to this directory with the '-m'\n# parameter or the 'm' command.  This can also point to a playlist.\n#\n# Example:    MusicDir = \"/home/joe/music\"\n#\n#MusicDir =\n\n# Start in the music directory by default?  If set to 'no', start\n# in the directory being viewed when the was client last active or,\n# as a last resort, the directory in which the client is being started.\n# A single directory on the command line takes precedence.\n#StartInMusicDir = no\n\n# The number of lines which are retained in an in-memory circular logging\n# buffer.  A value of zero indicates that lines will be written directly\n# to the log file, otherwise the latest CircularLogSize lines are retained\n# in memory and not written to the log file until the MOC client or server\n# are shutdown.  If the client or server terminates abnormally then the\n# log lines are lost.\n#\n# This option is intended to help identify problems which occur infrequently\n# and for which the amount of disk space consumed by logging would otherwise\n# be a limiting factor.  Obviously the memory footprint will increase in\n# proportion to the value of this option.\n#CircularLogSize = 0\n\n# How to sort?  FileName is the option's only value for now.\n#Sort = FileName\n\n# Show errors in the streams (for example, broken frames in MP3 files)?\n#ShowStreamErrors = no\n\n# Ignore CRC errors in MP3 files?  Most players do that, so the default\n# value is 'yes'.\n#MP3IgnoreCRCErrors = yes\n\n# Set playback toggles.\n#Repeat = no\n#Shuffle = no\n#AutoNext = yes\n\n# Default FormatString:\n#\n#   %n - Track number\n#   %a - Artist\n#   %A - Album\n#   %t - Title\n#   %(X:TRUE:FALSE) - Ternary expression: if X exists, do TRUE,\n#                     otherwise FALSE.  The escape character must\n#                     be doubled (i.e., '\\\\').  (See zshmisc\n#                     documentation for more information.)\n#\n#FormatString = \"%(n:%n :)%(a:%a - :)%(t:%t:)%(A: \\(%A\\):)\"\n\n# Input and output buffer sizes (in kilobytes).\n#InputBuffer = 512                  # Minimum value is 32KB\n#OutputBuffer = 512                 # Minimum value is 128KB\n\n# How much to fill the input buffer before playing (in kilobytes)?\n# This can't be greater than the value of InputBuffer.  While this has\n# a positive effect for network streams, it also causes the broadcast\n# audio to be delayed.\n#Prebuffering = 64\n\n# Use this HTTP proxy server for internet streams.  If not set, the\n# environment variables http_proxy and ALL_PROXY will be used if present.\n#\n# Format: HTTPProxy = PROXY_NAME:PORT\n#\n#HTTPProxy =\n\n# Sound driver - OSS, ALSA, JACK, SNDIO (on OpenBSD) or null (only for\n# debugging).  You can enter more than one driver as a colon-separated\n# list.  The first working driver will be used.\n#SoundDriver = @SOUNDDRIVER@\n\n# Jack output settings.\n#JackClientName = \"moc\"\n#JackStartServer = no\n#JackOutLeft = \"system:playback_1\"\n#JackOutRight = \"system:playback_2\"\n\n# OSS output settings.\n#OSSDevice = /dev/dsp\n#OSSMixerDevice = /dev/mixer\n#OSSMixerChannel1 = pcm             # 'pcm', 'master' or 'speaker'\n#OSSMixerChannel2 = master          # 'pcm', 'master' or 'speaker'\n\n# ALSA output settings.  If you need to dump the audio produced by MOC\n# to a file for diagnostic purposes, the following setting of 'ALSADevice'\n# should do that:\n#\n#    ALSADevice=tee:hw,'/tmp/out.wav',wav\n#\n#ALSADevice = default\n#ALSAMixer1 = PCM\n#ALSAMixer2 = Master\n\n# Under some circumstances on 32-bit systems, audio played continously\n# for long periods of time may begin to stutter.  Setting this option to\n# 'yes' will force MOC to avoid ALSA's dmix resampling and prevent this\n# stutter.  But it also has other implications:\n#\n# - You may experience unacceptably high CPU load.\n# - ALSA's resampler plug-ins will not be used.\n# - The resampling may be of lower quality than ALSA would provide.\n# - You may need to try different \"ResampleMethod\" option settings.\n# - The \"ForceSampleRate\" option may be ineffective.\n# - If libsamplerate is not configured, many audios may be unplayable.\n#\n#ALSAStutterDefeat = no\n\n# Save software mixer state?\n# If enabled, a file 'softmixer' will be created in '~/.moc/' storing the\n# mixersetting set when the server is shut down.\n# Note that there is a \"hidden\" 'Amplification' setting in that file.\n# Amplification (0-200) is used to scale the mixer setting (0-100).  This\n# results in a higher signal amplitude but may also produce clipping.\n#Softmixer_SaveState = yes\n\n# Save equalizer state?\n# If enabled, a file 'equalizer' will be created in '~/.moc/' storing the\n# equalizer settings when the server is shut down.\n# Note that there is a \"hidden\" 'Mixin' setting in that file.\n# Mixin (0.0-1.0) is used to determine how much of the original signal is\n# used after equalizing.  0 means to only use the equalized sound, while 1\n# effectively disabled the mixer.  The default is 0.25.\n#Equalizer_SaveState = yes\n\n# Show files with dot at the beginning?\n#ShowHiddenFiles = no\n\n# Hide file name extensions?\n#HideFileExtension = no\n\n# Show file format in menu?\n#ShowFormat = yes\n\n# Show file time in menu?  Possible values: 'yes', 'no' and 'IfAvailable'\n# (meaning show the time only when it is already known, which often works\n# faster).\n#ShowTime = IfAvailable\n\n# Show time played as a percentage in the time progress bar.\n#ShowTimePercent = no\n\n# Values of the TERM environment variable which are deemed to be managed by\n# screen(1).  If you are setting a specific terminal using screen(1)'s\n# '-T <term>' option, then you will need to add 'screen.<term>' to this list.\n# Note that this is only a partial test; the value of the WINDOW environment\n# variable must also be a number (which screen(1) sets).\n#ScreenTerms = screen:screen-w:vt100\n\n# Values of the TERM environment variable which are deemed to be xterms.  If\n# you are using MOC within screen(1) under an xterm, then add screen(1)'s\n# TERM setting here as well to cause MOC to update the xterm's title.\n#XTerms = xterm\n#XTerms += xterm-colour:xterm-color\n#XTerms += xterm-256colour:xterm-256color\n#XTerms += rxvt:rxvt-unicode\n#XTerms += rxvt-unicode-256colour:rxvt-unicode-256color\n#XTerms += eterm\n\n# Theme file to use.  This can be absolute path or relative to\n# /usr/share/moc/themes/ (depends on installation prefix) or\n# ~/.moc/themes/ .\n#\n# Example:    Theme = laras_theme\n#\n#Theme =\n\n# The theme used when running on an xterm.\n#\n# Example:    XTermTheme = transparent-background\n#\n#XTermTheme =\n\n# Should MOC try to autoload the default lyrics file for an audio?  (The\n# default lyrics file is a text file with the same file name as the audio\n# file name with any trailing \"extension\" removed.)\n#AutoLoadLyrics = yes\n\n# MOC directory (where pid file, socket and state files are stored).\n# You can use ~ at the beginning.\n#MOCDir = ~/.moc\n\n# Use mmap() to read files.  mmap() is much slower on NFS.\n#UseMMap = no\n\n# Use MIME to identify audio files.  This can make for slower loading\n# of playlists but is more accurate than using \"extensions\".\n#UseMimeMagic = no\n\n# Assume this encoding for ID3 version 1/1.1 tags (MP3 files).  Unlike\n# ID3v2, UTF-8 is not used here and MOC can't guess how tags are encoded.\n# Another solution is using librcc (see the next option).  This option is\n# ignored if UseRCC is set to 'yes'.\n#ID3v1TagsEncoding = WINDOWS-1250\n\n# Use librcc to fix ID3 version 1/1.1 tags encoding.\n#UseRCC = yes\n\n# Use librcc to filenames and directory names encoding.\n#UseRCCForFilesystem = yes\n\n# When this option is set the player assumes that if the encoding of\n# ID3v2 is set to ISO-8859-1 then the ID3v1TagsEncoding is actually\n# that and applies appropriate conversion.\n#EnforceTagsEncoding = no\n\n# Enable the conversion of filenames from the local encoding to UTF-8.\n#FileNamesIconv = no\n\n# Enable the conversion of the xterm title from UTF-8 to the local encoding.\n#NonUTFXterm = no\n\n# Should MOC precache files to assist gapless playback?\n#Precache = yes\n\n# Remember the playlist after exit?\n#SavePlaylist = yes\n\n# When using more than one client (interface) at a time, do they share\n# the playlist?\n#SyncPlaylist = yes\n\n# Choose a keymap file (relative to '~/.moc/' or using an absolute path).\n# An annotated example keymap file is included ('keymap.example').\n#\n# Example:    Keymap = my_keymap\n#\n#Keymap =\n\n# Use ASCII rather than graphic characters for drawing lines.  This\n# helps on some terminals.\n#ASCIILines = no\n\n# FastDirs, these allow you to jump directly to a directory, the key\n# bindings are in the keymap file.\n#\n# Examples:   Fastdir1 = /mp3/rock\n#             Fastdir2 = /mp3/electronic\n#             Fastdir3 = /mp3/rap\n#             Fastdir4 = /mp3/etc\n#\n#Fastdir1 =\n#Fastdir2 =\n#Fastdir3 =\n#Fastdir4 =\n#Fastdir5 =\n#Fastdir6 =\n#Fastdir7 =\n#Fastdir8 =\n#Fastdir9 =\n#Fastdir10 =\n\n# How fast to seek (in number of seconds per keystroke).  The first\n# option is for normal seek and the second for silent seek.\n#SeekTime = 1\n#SilentSeekTime = 5\n\n# PreferredDecoders allows you to specify which decoder should be used\n# for any given audio format.  It is a colon-separated list in which\n# each entry is of the general form 'code(decoders)', where 'code'\n# identifies the audio format and 'decoders' is a comma-separated list\n# of decoders in order of preference.\n#\n# The audio format identifier may be either a filename extension or a\n# MIME media type.  If the latter, the format is 'type/subtype' (e.g.,\n# 'audio/flac').  Because different systems may give different MIME\n# media types, any 'x-' prefix of the subtype is ignored both here and\n# in the actual file MIME type (so all combinations of 'audio/flac' and\n# 'audio/x-flac' match each other).\n#\n# For Internet streams the matching is done on MIME media type and on\n# actual content.  For files the matches are made on MIME media type\n# (if the 'UseMimeMagic' option is set) and on filename extension.  The\n# MIME media type of a file is not determined until the first entry for\n# MIME is encountered in the list.\n#\n# The matching is done in the order of appearance in the list with any\n# entries added from the command line being matched before those listed\n# here.  Therefore, if you place all filename extension entries before\n# all MIME entries you will speed up MOC's processing of directories\n# (which could be significant for remote file systems).\n#\n# The decoder list may be empty, in which case no decoders will be used\n# for files (and files with that audio format ignored) while Internet\n# streams will be assessed on the actual content.  Any decoder position\n# may contain an asterisk, in which case any decoder not otherwise listed\n# which can handle the audio format will be used.  It is not an error to\n# list the same decoder twice, but neither does it make sense to do so.\n#\n# If you have a mix of audio and non-audio files in your directories, you\n# may wish to include entries at top of the list which ignore non-audio\n# files by extension.\n#\n# In summary, the PreferredDecoders option provides fine control over the\n# type of matching which is performed (filename extension, MIME media\n# type and streamed media content) and which decoder(s) (if any) are used\n# based on the option's list entries and their ordering.\n#\n# Examples:   aac(aac,ffmpeg)             first try FAAD2 for AACs then FFmpeg\n#             mp3()                       ignore MP3 files\n#             wav(*,sndfile)              use sndfile for WAV as a last resort\n#             ogg(vorbis,*):flac(flac,*)  try Xiph decoders first\n#             ogg():audio/ogg()           ignore OGG files, and\n#                                         force Internet selection by content\n#             gz():html()                 ignore some non-audio files\n#\n# Any unspecified audio formats default to trying all decoders.\n# Any unknown (or misspelt) drivers are ignored.\n# All names are case insensitive.\n# The default setting reflects the historical situation modified by\n# the experience of users.\n#\n#PreferredDecoders  = aac(aac,ffmpeg):m4a(ffmpeg)\n#PreferredDecoders += mpc(musepack,*,ffmpeg):mpc8(musepack,*,ffmpeg)\n#PreferredDecoders += sid(sidplay2):mus(sidplay2)\n#PreferredDecoders += wav(sndfile,*,ffmpeg)\n#PreferredDecoders += wv(wavpack,*,ffmpeg)\n#PreferredDecoders += audio/aac(aac):audio/aacp(aac):audio/m4a(ffmpeg)\n#PreferredDecoders += audio/wav(sndfile,*)\n\n# The following PreferredDecoders attempt to handle the ambiguity surrounding\n# container types such as OGG for files.  The first two entries will force\n# a local file to the correct decoder (assuming the .ogg file contains Vorbis\n# audio), while the MIME media types will cause Internet audio streams to\n# be assessed on content (which may be either Vorbis or Speex).\n#\n#PreferredDecoders += ogg(vorbis,*,ffmpeg):oga(vorbis,*,ffmpeg):ogv(ffmpeg)\n#PreferredDecoders += application/ogg(vorbis):audio/ogg(vorbis)\n#PreferredDecoders += flac(flac,*,ffmpeg)\n#PreferredDecoders += opus(ffmpeg)\n#PreferredDecoders += spx(speex)\n\n# Which resampling method to use.  There are a few methods of resampling\n# sound supported by libresamplerate.  The default is 'Linear') which is\n# also the fastest.  A better description can be found at:\n#\n#    http://www.mega-nerd.com/libsamplerate/api_misc.html#Converters\n#\n# but briefly, the following methods are based on bandlimited interpolation\n# and are higher quality, but also slower:\n#\n#    SincBestQuality   - really slow (I know you probably have an xx GHz\n#                        processor, but it's still not enough to not see\n#                        this in the top output :)  The worst case\n#                        Signal-to-Noise Ratio is 97dB.\n#    SincMediumQuality - much faster.\n#    SincFastest       - the fastest bandlimited interpolation.\n#\n# And these are lower quality, but much faster methods:\n#\n#    ZeroOrderHold - really poor quality, but it's really fast.\n#    Linear - a bit better and a bit slower.\n#\n#ResampleMethod = Linear\n\n# Always use this sample rate (in Hz) when opening the audio device (and\n# resample the sound if necessary).  When set to 0 the device is opened\n# with the file's rate.\n#ForceSampleRate = 0\n\n# By default, even if the sound card reports that it can output 24bit samples\n# MOC converts 24bit PCM to 16bit.  Setting this option to 'yes' allows MOC\n# to use 24bit output.  (The MP3 decoder, for example, uses this format.)\n# This is disabled by default because there were reports that it prevents\n# MP3 files from playing on some soundcards.\n#Allow24bitOutput = no\n\n# Use realtime priority for output buffer thread.  This will prevent gaps\n# while playing even with heavy load.  The user who runs MOC must have\n# permissions to set such a priority.  This could be dangerous, because it\n# is possible that a bug in MOC will freeze your computer.\n#UseRealtimePriority = no\n\n# The number of audio files for which MOC will cache tags.  When this limit\n# is reached, file tags are discarded on a least recently used basis (with\n# one second resolution).  You can disable the cache by giving it a size of\n# zero.  Note that if you decrease the cache size below the number of items\n# currently in the cache, the number will not decrease immediately (if at\n# all).\n#TagsCacheSize = 256\n\n# Number items in the playlist.\n#PlaylistNumbering = yes\n\n# Main window layouts can be configured.  You can change the position and\n# size of the menus (directory and playlist).  You have three layouts and\n# can switch between then using the 'l' key (standard mapping).  By default,\n# only two layouts are configured.\n#\n# The format is as follows:\n#\n#     - Each layout is described as a list of menu entries.\n#     - Each menu entry is of the form:\n#\n#           menu(position_x, position_y, width, height)\n#\n#       where 'menu' is either 'directory' or 'playlist'.\n#     - The parameters define position and size of the menu.  They can\n#       be absolute numbers (like 10) or a percentage of the screen size\n#       (like 45%).\n#     - 'width' and 'height' can have also value of 'FILL' which means\n#        fill the screen from the menu's position to the border.\n#     - Menus may overlap.\n#\n# You must describe at least one menu (default is to fill the whole window).\n# There must be at least one layout (Layout1) defined; others can be empty.\n#\n# Example:    Layout1 = playlist(50%,50%,50%,50%)\n#             Layout2 = \"\"\n#             Layout3 = \"\"\n#\n#             Just one layout, the directory will occupy the whole\n#             screen, the playlist will have 1/4 of the screen size\n#             and be positioned at lower right corner.  (Note that\n#             because the playlist will be hidden by the directory\n#             you will have to use the TAB key to make the playlist\n#             visible.)\n#\n# Example:    Layout1 = playlist(0,0,100%,10):directory(0,10,100%,FILL)\n#\n#             The screen is split into two parts: playlist at the top\n#             and the directory menu at the bottom.  Playlist will\n#             occupy 10 lines and the directory menu the rest.\n#\n#Layout1 = directory(0,0,50%,100%):playlist(50%,0,FILL,100%)\n#Layout2 = directory(0,0,100%,100%):playlist(0,0,100%,100%)\n#Layout3 = \"\"\n\n# When the song changes, should the menu be scrolled so that the currently\n# played file is visible?\n#FollowPlayedFile = yes\n\n# What to do if the interface was started and the server is already playing\n# something from the playlist?  If CanStartInPlaylist is set to 'yes', the\n# interface will switch to the playlist.  When set to 'no' it will start\n# from the last directory.\n#CanStartInPlaylist = yes\n\n# Executing external commands (1 - 10) invoked with key commands (F1 - F10\n# by default).\n#\n# Some arguments are substituted before executing:\n#\n#     %f - file path\n#     %i - title made from tags\n#     %S - start block mark (in seconds)\n#     %E - end block mark (in seconds)\n#\n# Data from tags can also be substituted:\n#\n#     %t - title\n#     %a - album\n#     %r - artist\n#     %n - track\n#     %m - time of the file (in seconds)\n#\n# The parameters above apply to the currently selected file.  If you change\n# them to capital letters, they are taken from the file currently playing.\n#\n# Programs are run using execv(), not a shell, so you can't do things like\n# redirecting the output to a file.  The command string is split using blank\n# characters as separators; the first element is the command to be executed\n# and the rest are its parameters, so if you use \"echo Playing: %I\" we run\n# program 'echo' (from $PATH) with 2 parameters: the string 'Playing:' and\n# the title of the file currently playing.  Even if the title contains\n# spaces, it's still one parameter and it's safe if it contains `rm -rf /`.\n#\n# Examples:   ExecCommand1 = \"cp %f /mnt/usb_drive\"\n#             ExecCommand2 = \"/home/joe/now_playing %I\"\n#\n#ExecCommand1 =\n#ExecCommand2 =\n#ExecCommand3 =\n#ExecCommand4 =\n#ExecCommand5 =\n#ExecCommand6 =\n#ExecCommand7 =\n#ExecCommand8 =\n#ExecCommand9 =\n#ExecCommand10 =\n\n# Display the cursor in the line with the selected file.  Some braille\n# readers (the Handy Tech modular series ZMU 737, for example) use the\n# cursor to focus and can make use of it to present the file line even\n# when other fields are changing.\n#UseCursorSelection = no\n\n# Set the terminal title when running under xterm.\n#SetXtermTitle = yes\n\n# Set the terminal title when running under screen(1).  If MOC can detect\n# that it is running under screen(1), then it will set an appropriate\n# title (see description of ScreenTerms above).  However, if multiple\n# levels of screen management are involved, detection might fail and this\n# could cause a screen upset.  In that situation you can use this option\n# to force screen titles off.\n#SetScreenTitle = yes\n\n# Display full paths instead of just file names in the playlist.\n#PlaylistFullPaths = yes\n\n# The following setting describes how block markers are displayed in\n# the play time progress bar.  Its value is a string of exactly three\n# characters.  The first character is displayed in a position which\n# corresponds to the time marked as the start of a block and the last\n# character to the time marked as the end of the block.  The middle\n# character is displayed instead if both the start and the end of the block\n# would fall in the same position (within the resolution of the interface).\n# You can turn off the displaying of these block marker positions by using\n# three space characters.\n#BlockDecorators = \"`\\\"'\"\n\n# How long (in seconds) to leave a message displayed on the screen.\n# Setting this to a high value allows you to scroll through the messages\n# using the 'hide_message' key.  Setting it to zero means you'll have to\n# be quick to see any message at all.  Any new messages will be queued up\n# and displayed after the current message's linger time expires.\n#MessageLingerTime = 3\n\n# Does MOC display a prefix on delayed messages indicating\n# the number of queued messages still to be displayed?\n#PrefixQueuedMessages = yes\n\n# String to append to the queued message count if any\n# error messages are still waiting to be displayed.\n#ErrorMessagesQueued = \"!\"\n\n# Self-describing ModPlug options (with 'yes' or 'no' values).\n#ModPlug_Oversampling = yes\n#ModPlug_NoiseReduction = yes\n#ModPlug_Reverb = no\n#ModPlug_MegaBass = no\n#ModPlug_Surround = no\n\n# ModPlug resampling mode.\n# Valid values are:\n#\n#     FIR -      8 tap fir filter (extremely high quality)\n#     SPLINE -   Cubic spline interpolation (high quality)\n#     LINEAR -   Linear interpolation (fast, good quality)\n#     NEAREST -  No interpolation (very fast, extremely bad sound quality)\n#\n#ModPlug_ResamplingMode = FIR\n\n# Other self-describing ModPlug audio characteristic options.\n# (Note that the 32 bit sample size seems to be buggy.)\n#ModPlug_Channels = 2               # 1 or 2 channels\n#ModPlug_Bits = 16                  # 8, 16 or 32 bits\n#ModPlug_Frequency = 44100          # 11025, 22050, 44100 or 48000 Hz\n#ModPlug_ReverbDepth = 0            # 0 (quiet) to 100 (loud)\n#ModPlug_ReverbDelay = 0            # Delay in ms (usually 40-200ms)\n#ModPlug_BassAmount = 0             # 0 (quiet) to 100 (loud).\n#ModPlug_BassRange = 10             # Cutoff in Hz (10-100).\n#ModPlug_SurroundDepth = 0          # Surround level 0(quiet)-100(heavy).\n#ModPlug_SurroundDelay = 0          # Surround delay in ms, usually 5-40ms.\n#ModPlug_LoopCount = 0              # 0 (never), n (times) or -1 (forever)\n\n# Self-describing TiMidity audio characteristic options.\n#TiMidity_Rate = 44100              # Between 8000 and 48000\n#TiMidity_Bits = 16                 # 8 or 16\n#TiMidity_Channels = 2              # 1 or 2\n#TiMidity_Volume = 100              # 0 to 800\n\n# You can setup a TiMidity-Config-File here.\n# Leave it unset to use library defaults (/etc/timidity.cfg mostly).\n# Setting it to 'yes' also uses the library defaults.\n# Set it to 'no' if you don't have any configuration file.\n# Otherwise set it to the name of a specific file.\n#TiMidity_Config =\n\n# Self-describing SidPlay2 audio characteristic options.\n#SidPlay2_DefaultSongLength = 180   # If not in database (in seconds)\n#SidPlay2_MinimumSongLength = 0     # Play at least n (in seconds)\n#SidPlay2_Frequency = 44100         # 4000 to 48000\n#SidPlay2_Bits = 16                 # 8 or 16\n#SidPlay2_Optimisation = 0          # 0 (worst quality) to 2 (best quality)\n\n# Set path to a HVSC-compatible database (if not set, database is disabled).\n#SidPlay2_Database =\n\n# SidPlay2 playback Mode:\n#\n#     \"M\": Mono (best for many SIDs)\n#     \"S\": Stereo\n#     \"L\"/\"R\": Left / Right\n#\n#SidPlay2_PlayMode = \"M\"\n\n# Use start-song information from SID ('yes') or start at first song\n# ('no').  Songs before the start-song won't be played.\n#SidPlay2_StartAtStart = yes\n\n# Play sub-tunes.\n#SidPlay2_PlaySubTunes = yes\n\n# Run the OnSongChange command when a new song starts playing.\n# Specify the full path (i.e. no leading '~') of an executable to run.\n# Arguments will be passed, and you can use the following escapes:\n#\n#     %a artist\n#     %r album\n#     %f filename\n#     %t title\n#     %n track\n#     %d file duration in XX:YY form\n#     %D file duration, number of seconds\n#\n# No pipes/redirects can be used directly, but writing a shell script\n# can do the job.\n#\n# Example:    OnSongChange = \"/home/jack/.moc/myscript %a %r\"\n#\n#OnSongChange =\n\n# If RepeatSongChange is 'yes' then MOC will execute the command every time\n# a song starts playing regardless of whether or not it is just repeating.\n# Otherwise the command will only be executed when a different song is\n# started.\n#RepeatSongChange = no\n\n# Run the OnStop command (full path, no arguments) when MOC changes state\n# to stopped (i.e., when user stopped playing or changes a song).\n#\n# Example:    OnStop = \"/home/jack/.moc/myscript_on_stop\"\n#\n#OnStop =\n\n# Run the OnServerStart or OnServerStop commands (full path, no arguments)\n# when MOC server is started or terminated respectively.  The server will\n# not wait for the commands to complete before continuing.\n#OnServerStart =\n#OnServerStop =\n\n# This option determines which song to play after finishing all the songs\n# in the queue.  Setting this to 'yes' causes MOC to play the song which\n# follows the song being played before queue playing started. If set to\n# 'no', MOC will play the song following the last song in the queue if it\n# is in the playlist.  The default is 'yes' because this is the way other\n# players usually behave.  (Note that this option previously took the\n# values 1 and 0; these are now deprecated in favour of 'yes' and 'no'.)\n#QueueNextSongReturn = yes\n"
  },
  {
    "path": "config.rpath",
    "content": "#! /bin/sh\n# Output a system dependent set of variables, describing how to set the\n# run time search path of shared libraries in an executable.\n#\n#   Copyright 1996-2011 Free Software Foundation, Inc.\n#   Taken from GNU libtool, 2001\n#   Originally by Gordon Matzigkeit <gord@gnu.ai.mit.edu>, 1996\n#\n#   This file is free software; the Free Software Foundation gives\n#   unlimited permission to copy and/or distribute it, with or without\n#   modifications, as long as this notice is preserved.\n#\n# The first argument passed to this file is the canonical host specification,\n#    CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM\n# or\n#    CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM\n# The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld\n# should be set by the caller.\n#\n# The set of defined variables is at the end of this script.\n\n# Known limitations:\n# - On IRIX 6.5 with CC=\"cc\", the run time search patch must not be longer\n#   than 256 bytes, otherwise the compiler driver will dump core. The only\n#   known workaround is to choose shorter directory names for the build\n#   directory and/or the installation directory.\n\n# All known linkers require a `.a' archive for static linking (except MSVC,\n# which needs '.lib').\nlibext=a\nshrext=.so\n\nhost=\"$1\"\nhost_cpu=`echo \"$host\" | sed 's/^\\([^-]*\\)-\\([^-]*\\)-\\(.*\\)$/\\1/'`\nhost_vendor=`echo \"$host\" | sed 's/^\\([^-]*\\)-\\([^-]*\\)-\\(.*\\)$/\\2/'`\nhost_os=`echo \"$host\" | sed 's/^\\([^-]*\\)-\\([^-]*\\)-\\(.*\\)$/\\3/'`\n\n# Code taken from libtool.m4's _LT_CC_BASENAME.\n\nfor cc_temp in $CC\"\"; do\n  case $cc_temp in\n    compile | *[\\\\/]compile | ccache | *[\\\\/]ccache ) ;;\n    distcc | *[\\\\/]distcc | purify | *[\\\\/]purify ) ;;\n    \\-*) ;;\n    *) break;;\n  esac\ndone\ncc_basename=`echo \"$cc_temp\" | sed -e 's%^.*/%%'`\n\n# Code taken from libtool.m4's _LT_COMPILER_PIC.\n\nwl=\nif test \"$GCC\" = yes; then\n  wl='-Wl,'\nelse\n  case \"$host_os\" in\n    aix*)\n      wl='-Wl,'\n      ;;\n    darwin*)\n      case $cc_basename in\n        xlc*)\n          wl='-Wl,'\n          ;;\n      esac\n      ;;\n    mingw* | cygwin* | pw32* | os2* | cegcc*)\n      ;;\n    hpux9* | hpux10* | hpux11*)\n      wl='-Wl,'\n      ;;\n    irix5* | irix6* | nonstopux*)\n      wl='-Wl,'\n      ;;\n    newsos6)\n      ;;\n    linux* | k*bsd*-gnu)\n      case $cc_basename in\n        ecc*)\n          wl='-Wl,'\n          ;;\n        icc* | ifort*)\n          wl='-Wl,'\n          ;;\n        lf95*)\n          wl='-Wl,'\n          ;;\n        pgcc | pgf77 | pgf90)\n          wl='-Wl,'\n          ;;\n        ccc*)\n          wl='-Wl,'\n          ;;\n        como)\n          wl='-lopt='\n          ;;\n        *)\n          case `$CC -V 2>&1 | sed 5q` in\n            *Sun\\ C*)\n              wl='-Wl,'\n              ;;\n          esac\n          ;;\n      esac\n      ;;\n    osf3* | osf4* | osf5*)\n      wl='-Wl,'\n      ;;\n    rdos*)\n      ;;\n    solaris*)\n      wl='-Wl,'\n      ;;\n    sunos4*)\n      wl='-Qoption ld '\n      ;;\n    sysv4 | sysv4.2uw2* | sysv4.3*)\n      wl='-Wl,'\n      ;;\n    sysv4*MP*)\n      ;;\n    sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*)\n      wl='-Wl,'\n      ;;\n    unicos*)\n      wl='-Wl,'\n      ;;\n    uts4*)\n      ;;\n  esac\nfi\n\n# Code taken from libtool.m4's _LT_LINKER_SHLIBS.\n\nhardcode_libdir_flag_spec=\nhardcode_libdir_separator=\nhardcode_direct=no\nhardcode_minus_L=no\n\ncase \"$host_os\" in\n  cygwin* | mingw* | pw32* | cegcc*)\n    # FIXME: the MSVC++ port hasn't been tested in a loooong time\n    # When not using gcc, we currently assume that we are using\n    # Microsoft Visual C++.\n    if test \"$GCC\" != yes; then\n      with_gnu_ld=no\n    fi\n    ;;\n  interix*)\n    # we just hope/assume this is gcc and not c89 (= MSVC++)\n    with_gnu_ld=yes\n    ;;\n  openbsd*)\n    with_gnu_ld=no\n    ;;\nesac\n\nld_shlibs=yes\nif test \"$with_gnu_ld\" = yes; then\n  # Set some defaults for GNU ld with shared library support. These\n  # are reset later if shared libraries are not supported. Putting them\n  # here allows them to be overridden if necessary.\n  # Unlike libtool, we use -rpath here, not --rpath, since the documented\n  # option of GNU ld is called -rpath, not --rpath.\n  hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'\n  case \"$host_os\" in\n    aix[3-9]*)\n      # On AIX/PPC, the GNU linker is very broken\n      if test \"$host_cpu\" != ia64; then\n        ld_shlibs=no\n      fi\n      ;;\n    amigaos*)\n      hardcode_libdir_flag_spec='-L$libdir'\n      hardcode_minus_L=yes\n      # Samuel A. Falvo II <kc5tja@dolphin.openprojects.net> reports\n      # that the semantics of dynamic libraries on AmigaOS, at least up\n      # to version 4, is to share data among multiple programs linked\n      # with the same dynamic library.  Since this doesn't match the\n      # behavior of shared libraries on other platforms, we cannot use\n      # them.\n      ld_shlibs=no\n      ;;\n    beos*)\n      if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then\n        :\n      else\n        ld_shlibs=no\n      fi\n      ;;\n    cygwin* | mingw* | pw32* | cegcc*)\n      # hardcode_libdir_flag_spec is actually meaningless, as there is\n      # no search path for DLLs.\n      hardcode_libdir_flag_spec='-L$libdir'\n      if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then\n        :\n      else\n        ld_shlibs=no\n      fi\n      ;;\n    interix[3-9]*)\n      hardcode_direct=no\n      hardcode_libdir_flag_spec='${wl}-rpath,$libdir'\n      ;;\n    gnu* | linux* | k*bsd*-gnu)\n      if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then\n        :\n      else\n        ld_shlibs=no\n      fi\n      ;;\n    netbsd*)\n      ;;\n    solaris*)\n      if $LD -v 2>&1 | grep 'BFD 2\\.8' > /dev/null; then\n        ld_shlibs=no\n      elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then\n        :\n      else\n        ld_shlibs=no\n      fi\n      ;;\n    sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*)\n      case `$LD -v 2>&1` in\n        *\\ [01].* | *\\ 2.[0-9].* | *\\ 2.1[0-5].*)\n          ld_shlibs=no\n          ;;\n        *)\n          if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then\n            hardcode_libdir_flag_spec='`test -z \"$SCOABSPATH\" && echo ${wl}-rpath,$libdir`'\n          else\n            ld_shlibs=no\n          fi\n          ;;\n      esac\n      ;;\n    sunos4*)\n      hardcode_direct=yes\n      ;;\n    *)\n      if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then\n        :\n      else\n        ld_shlibs=no\n      fi\n      ;;\n  esac\n  if test \"$ld_shlibs\" = no; then\n    hardcode_libdir_flag_spec=\n  fi\nelse\n  case \"$host_os\" in\n    aix3*)\n      # Note: this linker hardcodes the directories in LIBPATH if there\n      # are no directories specified by -L.\n      hardcode_minus_L=yes\n      if test \"$GCC\" = yes; then\n        # Neither direct hardcoding nor static linking is supported with a\n        # broken collect2.\n        hardcode_direct=unsupported\n      fi\n      ;;\n    aix[4-9]*)\n      if test \"$host_cpu\" = ia64; then\n        # On IA64, the linker does run time linking by default, so we don't\n        # have to do anything special.\n        aix_use_runtimelinking=no\n      else\n        aix_use_runtimelinking=no\n        # Test if we are trying to use run time linking or normal\n        # AIX style linking. If -brtl is somewhere in LDFLAGS, we\n        # need to do runtime linking.\n        case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*)\n          for ld_flag in $LDFLAGS; do\n            if (test $ld_flag = \"-brtl\" || test $ld_flag = \"-Wl,-brtl\"); then\n              aix_use_runtimelinking=yes\n              break\n            fi\n          done\n          ;;\n        esac\n      fi\n      hardcode_direct=yes\n      hardcode_libdir_separator=':'\n      if test \"$GCC\" = yes; then\n        case $host_os in aix4.[012]|aix4.[012].*)\n          collect2name=`${CC} -print-prog-name=collect2`\n          if test -f \"$collect2name\" && \\\n            strings \"$collect2name\" | grep resolve_lib_name >/dev/null\n          then\n            # We have reworked collect2\n            :\n          else\n            # We have old collect2\n            hardcode_direct=unsupported\n            hardcode_minus_L=yes\n            hardcode_libdir_flag_spec='-L$libdir'\n            hardcode_libdir_separator=\n          fi\n          ;;\n        esac\n      fi\n      # Begin _LT_AC_SYS_LIBPATH_AIX.\n      echo 'int main () { return 0; }' > conftest.c\n      ${CC} ${LDFLAGS} conftest.c -o conftest\n      aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0  *\\(.*\\)$/\\1/; p; }\n}'`\n      if test -z \"$aix_libpath\"; then\n        aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0  *\\(.*\\)$/\\1/; p; }\n}'`\n      fi\n      if test -z \"$aix_libpath\"; then\n        aix_libpath=\"/usr/lib:/lib\"\n      fi\n      rm -f conftest.c conftest\n      # End _LT_AC_SYS_LIBPATH_AIX.\n      if test \"$aix_use_runtimelinking\" = yes; then\n        hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'\"$aix_libpath\"\n      else\n        if test \"$host_cpu\" = ia64; then\n          hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib'\n        else\n          hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'\"$aix_libpath\"\n        fi\n      fi\n      ;;\n    amigaos*)\n      hardcode_libdir_flag_spec='-L$libdir'\n      hardcode_minus_L=yes\n      # see comment about different semantics on the GNU ld section\n      ld_shlibs=no\n      ;;\n    bsdi[45]*)\n      ;;\n    cygwin* | mingw* | pw32* | cegcc*)\n      # When not using gcc, we currently assume that we are using\n      # Microsoft Visual C++.\n      # hardcode_libdir_flag_spec is actually meaningless, as there is\n      # no search path for DLLs.\n      hardcode_libdir_flag_spec=' '\n      libext=lib\n      ;;\n    darwin* | rhapsody*)\n      hardcode_direct=no\n      if test \"$GCC\" = yes ; then\n        :\n      else\n        case $cc_basename in\n          xlc*)\n            ;;\n          *)\n            ld_shlibs=no\n            ;;\n        esac\n      fi\n      ;;\n    dgux*)\n      hardcode_libdir_flag_spec='-L$libdir'\n      ;;\n    freebsd1*)\n      ld_shlibs=no\n      ;;\n    freebsd2.2*)\n      hardcode_libdir_flag_spec='-R$libdir'\n      hardcode_direct=yes\n      ;;\n    freebsd2*)\n      hardcode_direct=yes\n      hardcode_minus_L=yes\n      ;;\n    freebsd* | dragonfly*)\n      hardcode_libdir_flag_spec='-R$libdir'\n      hardcode_direct=yes\n      ;;\n    hpux9*)\n      hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'\n      hardcode_libdir_separator=:\n      hardcode_direct=yes\n      # hardcode_minus_L: Not really in the search PATH,\n      # but as the default location of the library.\n      hardcode_minus_L=yes\n      ;;\n    hpux10*)\n      if test \"$with_gnu_ld\" = no; then\n        hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'\n        hardcode_libdir_separator=:\n        hardcode_direct=yes\n        # hardcode_minus_L: Not really in the search PATH,\n        # but as the default location of the library.\n        hardcode_minus_L=yes\n      fi\n      ;;\n    hpux11*)\n      if test \"$with_gnu_ld\" = no; then\n        hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir'\n        hardcode_libdir_separator=:\n        case $host_cpu in\n          hppa*64*|ia64*)\n            hardcode_direct=no\n            ;;\n          *)\n            hardcode_direct=yes\n            # hardcode_minus_L: Not really in the search PATH,\n            # but as the default location of the library.\n            hardcode_minus_L=yes\n            ;;\n        esac\n      fi\n      ;;\n    irix5* | irix6* | nonstopux*)\n      hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'\n      hardcode_libdir_separator=:\n      ;;\n    netbsd*)\n      hardcode_libdir_flag_spec='-R$libdir'\n      hardcode_direct=yes\n      ;;\n    newsos6)\n      hardcode_direct=yes\n      hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'\n      hardcode_libdir_separator=:\n      ;;\n    openbsd*)\n      if test -f /usr/libexec/ld.so; then\n        hardcode_direct=yes\n        if test -z \"`echo __ELF__ | $CC -E - | grep __ELF__`\" || test \"$host_os-$host_cpu\" = \"openbsd2.8-powerpc\"; then\n          hardcode_libdir_flag_spec='${wl}-rpath,$libdir'\n        else\n          case \"$host_os\" in\n            openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*)\n              hardcode_libdir_flag_spec='-R$libdir'\n              ;;\n            *)\n              hardcode_libdir_flag_spec='${wl}-rpath,$libdir'\n              ;;\n          esac\n        fi\n      else\n        ld_shlibs=no\n      fi\n      ;;\n    os2*)\n      hardcode_libdir_flag_spec='-L$libdir'\n      hardcode_minus_L=yes\n      ;;\n    osf3*)\n      hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'\n      hardcode_libdir_separator=:\n      ;;\n    osf4* | osf5*)\n      if test \"$GCC\" = yes; then\n        hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir'\n      else\n        # Both cc and cxx compiler support -rpath directly\n        hardcode_libdir_flag_spec='-rpath $libdir'\n      fi\n      hardcode_libdir_separator=:\n      ;;\n    solaris*)\n      hardcode_libdir_flag_spec='-R$libdir'\n      ;;\n    sunos4*)\n      hardcode_libdir_flag_spec='-L$libdir'\n      hardcode_direct=yes\n      hardcode_minus_L=yes\n      ;;\n    sysv4)\n      case $host_vendor in\n        sni)\n          hardcode_direct=yes # is this really true???\n          ;;\n        siemens)\n          hardcode_direct=no\n          ;;\n        motorola)\n          hardcode_direct=no #Motorola manual says yes, but my tests say they lie\n          ;;\n      esac\n      ;;\n    sysv4.3*)\n      ;;\n    sysv4*MP*)\n      if test -d /usr/nec; then\n        ld_shlibs=yes\n      fi\n      ;;\n    sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*)\n      ;;\n    sysv5* | sco3.2v5* | sco5v6*)\n      hardcode_libdir_flag_spec='`test -z \"$SCOABSPATH\" && echo ${wl}-R,$libdir`'\n      hardcode_libdir_separator=':'\n      ;;\n    uts4*)\n      hardcode_libdir_flag_spec='-L$libdir'\n      ;;\n    *)\n      ld_shlibs=no\n      ;;\n  esac\nfi\n\n# Check dynamic linker characteristics\n# Code taken from libtool.m4's _LT_SYS_DYNAMIC_LINKER.\n# Unlike libtool.m4, here we don't care about _all_ names of the library, but\n# only about the one the linker finds when passed -lNAME. This is the last\n# element of library_names_spec in libtool.m4, or possibly two of them if the\n# linker has special search rules.\nlibrary_names_spec=      # the last element of library_names_spec in libtool.m4\nlibname_spec='lib$name'\ncase \"$host_os\" in\n  aix3*)\n    library_names_spec='$libname.a'\n    ;;\n  aix[4-9]*)\n    library_names_spec='$libname$shrext'\n    ;;\n  amigaos*)\n    library_names_spec='$libname.a'\n    ;;\n  beos*)\n    library_names_spec='$libname$shrext'\n    ;;\n  bsdi[45]*)\n    library_names_spec='$libname$shrext'\n    ;;\n  cygwin* | mingw* | pw32* | cegcc*)\n    shrext=.dll\n    library_names_spec='$libname.dll.a $libname.lib'\n    ;;\n  darwin* | rhapsody*)\n    shrext=.dylib\n    library_names_spec='$libname$shrext'\n    ;;\n  dgux*)\n    library_names_spec='$libname$shrext'\n    ;;\n  freebsd1*)\n    ;;\n  freebsd* | dragonfly*)\n    case \"$host_os\" in\n      freebsd[123]*)\n        library_names_spec='$libname$shrext$versuffix' ;;\n      *)\n        library_names_spec='$libname$shrext' ;;\n    esac\n    ;;\n  gnu*)\n    library_names_spec='$libname$shrext'\n    ;;\n  hpux9* | hpux10* | hpux11*)\n    case $host_cpu in\n      ia64*)\n        shrext=.so\n        ;;\n      hppa*64*)\n        shrext=.sl\n        ;;\n      *)\n        shrext=.sl\n        ;;\n    esac\n    library_names_spec='$libname$shrext'\n    ;;\n  interix[3-9]*)\n    library_names_spec='$libname$shrext'\n    ;;\n  irix5* | irix6* | nonstopux*)\n    library_names_spec='$libname$shrext'\n    case \"$host_os\" in\n      irix5* | nonstopux*)\n        libsuff= shlibsuff=\n        ;;\n      *)\n        case $LD in\n          *-32|*\"-32 \"|*-melf32bsmip|*\"-melf32bsmip \") libsuff= shlibsuff= ;;\n          *-n32|*\"-n32 \"|*-melf32bmipn32|*\"-melf32bmipn32 \") libsuff=32 shlibsuff=N32 ;;\n          *-64|*\"-64 \"|*-melf64bmip|*\"-melf64bmip \") libsuff=64 shlibsuff=64 ;;\n          *) libsuff= shlibsuff= ;;\n        esac\n        ;;\n    esac\n    ;;\n  linux*oldld* | linux*aout* | linux*coff*)\n    ;;\n  linux* | k*bsd*-gnu)\n    library_names_spec='$libname$shrext'\n    ;;\n  knetbsd*-gnu)\n    library_names_spec='$libname$shrext'\n    ;;\n  netbsd*)\n    library_names_spec='$libname$shrext'\n    ;;\n  newsos6)\n    library_names_spec='$libname$shrext'\n    ;;\n  nto-qnx*)\n    library_names_spec='$libname$shrext'\n    ;;\n  openbsd*)\n    library_names_spec='$libname$shrext$versuffix'\n    ;;\n  os2*)\n    libname_spec='$name'\n    shrext=.dll\n    library_names_spec='$libname.a'\n    ;;\n  osf3* | osf4* | osf5*)\n    library_names_spec='$libname$shrext'\n    ;;\n  rdos*)\n    ;;\n  solaris*)\n    library_names_spec='$libname$shrext'\n    ;;\n  sunos4*)\n    library_names_spec='$libname$shrext$versuffix'\n    ;;\n  sysv4 | sysv4.3*)\n    library_names_spec='$libname$shrext'\n    ;;\n  sysv4*MP*)\n    library_names_spec='$libname$shrext'\n    ;;\n  sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*)\n    library_names_spec='$libname$shrext'\n    ;;\n  uts4*)\n    library_names_spec='$libname$shrext'\n    ;;\nesac\n\nsed_quote_subst='s/\\([\"`$\\\\]\\)/\\\\\\1/g'\nescaped_wl=`echo \"X$wl\" | sed -e 's/^X//' -e \"$sed_quote_subst\"`\nshlibext=`echo \"$shrext\" | sed -e 's,^\\.,,'`\nescaped_libname_spec=`echo \"X$libname_spec\" | sed -e 's/^X//' -e \"$sed_quote_subst\"`\nescaped_library_names_spec=`echo \"X$library_names_spec\" | sed -e 's/^X//' -e \"$sed_quote_subst\"`\nescaped_hardcode_libdir_flag_spec=`echo \"X$hardcode_libdir_flag_spec\" | sed -e 's/^X//' -e \"$sed_quote_subst\"`\n\nLC_ALL=C sed -e 's/^\\([a-zA-Z0-9_]*\\)=/acl_cv_\\1=/' <<EOF\n\n# How to pass a linker flag through the compiler.\nwl=\"$escaped_wl\"\n\n# Static library suffix (normally \"a\").\nlibext=\"$libext\"\n\n# Shared library suffix (normally \"so\").\nshlibext=\"$shlibext\"\n\n# Format of library name prefix.\nlibname_spec=\"$escaped_libname_spec\"\n\n# Library names that the linker finds when passed -lNAME.\nlibrary_names_spec=\"$escaped_library_names_spec\"\n\n# Flag to hardcode \\$libdir into a binary during linking.\n# This must work even if \\$libdir does not exist.\nhardcode_libdir_flag_spec=\"$escaped_hardcode_libdir_flag_spec\"\n\n# Whether we need a single -rpath flag with a separated argument.\nhardcode_libdir_separator=\"$hardcode_libdir_separator\"\n\n# Set to yes if using DIR/libNAME.so during linking hardcodes DIR into the\n# resulting binary.\nhardcode_direct=\"$hardcode_direct\"\n\n# Set to yes if using the -LDIR flag during linking hardcodes DIR into the\n# resulting binary.\nhardcode_minus_L=\"$hardcode_minus_L\"\n\nEOF\n"
  },
  {
    "path": "configure.in",
    "content": "AC_INIT([Music On Console],[2.6-alpha3],[mocmaint@daper.net],[moc],\n                           [http://moc.daper.net/])\nAC_CONFIG_SRCDIR([main.c])\nAC_CONFIG_HEADERS([config.h])\n\nAM_INIT_AUTOMAKE([dist-xz no-dist-gzip])\n\nAC_PREREQ([2.64])\n\nAC_CANONICAL_HOST\nAC_PROG_CC_C99\nAC_PROG_CXX\nAC_PROG_INSTALL\n\nif test \"x$ac_cv_prog_cc_c99\" = \"xno\"\nthen\n\tAC_MSG_ERROR([You need an ISO C99 capable compiler.])\nfi\n\ndnl Test for POSIX.1-2001 or better.\nAC_EGREP_CPP(posix_2001_compatible,\n             [#define _POSIX_C_SOURCE 200112L\n              #include <unistd.h>\n              #ifdef _POSIX_VERSION\n              #if _POSIX_VERSION >= 200112L\n              posix_2001_compatible\n              #endif\n              #endif\n             ],\n             [true],\n             [AC_MSG_ERROR([A POSIX.1-2001 compatible system is required.])])\n\nAC_PROG_AWK\nAC_LIBTOOL_DLOPEN\nAC_DISABLE_STATIC\nAC_ENABLE_SHARED\nAC_PROG_LIBTOOL\nAC_LIB_LTDL\n\nAC_SUBST([EXTRA_OBJS])\n\nplugindir=$libdir/moc\nAC_SUBST([plugindir])\nPLUGIN_LDFLAGS='-module -avoid-version'\nAC_SUBST([PLUGIN_LDFLAGS])\n\ncase \"$host_os\" in\n\tlinux*)\n\t\tAC_DEFINE([LINUX], 1, [Define if your system is GNU/Linux])\n\t\t;;\n\topenbsd*)\n\t\tAC_DEFINE([OPENBSD], 1, [Define if your system is OpenBSD])\n\t\t;;\n\tfreebsd*)\n\t\tAC_DEFINE([FREEBSD], 1, [Define if your system is FreeBSD])\n\t\t;;\nesac\n\ndnl Check that a function pointer is the same size as a data pointer.\nAC_MSG_CHECKING([if function and data pointer sizes match])\nAC_RUN_IFELSE(\n\t[AC_LANG_PROGRAM(\n\t\t[], [[if (sizeof(void *) != sizeof(void (*)())) return 1;]]\n\t)],\n\t[AC_MSG_RESULT([yes])],\n\t[WARN_POINTER_MISMATCH=yes\n\t AC_MSG_RESULT([no])],\n\t[AC_MSG_RESULT([unknown])\n\t AC_MSG_WARN([Cross-compilation means pointer size test couldn't be run])])\n\nAC_DEFINE([_FILE_OFFSET_BITS], 64, [Use 64bit IO])\n\ndnl required X/Open SUS standard headers\nAC_CHECK_HEADERS([strings.h sys/un.h],,\n\t\t AC_MSG_ERROR([Required X/Open SUS header files are not present.]))\n\ndnl required OS provided headers\nAC_CHECK_HEADERS([sys/ioctl.h sys/param.h],,\n\t\t AC_MSG_ERROR([Required OS header files are not present.]))\n\ndnl optional headers\nAC_CHECK_HEADERS([byteswap.h])\n\ndnl langinfo\nAC_CHECK_HEADERS([langinfo.h])\nAC_CHECK_HEADERS([nl_types.h])\nAC_CHECK_FUNCS([nl_langinfo])\n\ndnl CODESET (taken from vim)\nAC_MSG_CHECKING(for nl_langinfo(CODESET))\nAC_TRY_LINK([\n\t     #ifdef HAVE_LANGINFO_H\n\t     # include <langinfo.h>\n\t     #endif\n\t     ], [char *cs = nl_langinfo(CODESET);],\n\t     AC_MSG_RESULT(yes)\n\t     AC_DEFINE([HAVE_NL_LANGINFO_CODESET], 1,\n\t\t       [Define if you have CODESET constant]),\n\t     AC_MSG_RESULT(no))\n\nAC_C_BIGENDIAN\n\nAC_C_CONST\nAC_TYPE_INTPTR_T\nAX_CFLAGS_GCC_OPTION(-Wall)\nAX_CFLAGS_GCC_OPTION(-Wextra)\n\ndnl Overly-enthusiastic warning suppression.\nsave_CFLAGS=\"$CFLAGS\"\nAX_CFLAGS_GCC_OPTION([-Wgnu-zero-variadic-macro-arguments], ,\n                     AC_DEFINE([HAVE_VARIADIC_MACRO_WARNING], 1,\n                               [Define if compiler recognises warning option]))\nAX_CFLAGS_GCC_OPTION([-Werror=unknown-warning-option])\nAX_CFLAGS_GCC_OPTION([-Wformat-truncation], ,\n                     AC_DEFINE([HAVE_FORMAT_TRUNCATION_WARNING], 1,\n                               [Define if compiler recognises warning option]))\nCFLAGS=\"$save_CFLAGS\"\n\nPKG_PROG_PKG_CONFIG([0.20])\n\nif test \"x$PKG_CONFIG\" = \"x\"\nthen\n\tAC_MSG_WARN([No pkg-config utility found or it's too old, I will have trouble finding installed libraries.])\nfi\n\nAC_ARG_ENABLE(cache, AS_HELP_STRING([--enable-cache],\n                                    [Enable tags caching code]))\n\nif test \"x$enable_cache\" != \"xno\"\nthen\n\tsave_CFLAGS=\"$CFLAGS\"\n\tCFLAGS=\n\tAX_PATH_BDB([4.1], [], AC_MSG_ERROR([BerkeleyDB (libdb) not found.]))\n\tCPPFLAGS=\"$CPPFLAGS $BDB_CPPFLAGS\"\n\tCFLAGS=\"$save_CFLAGS\"\n\tLDFLAGS=\"$LDFLAGS $BDB_LDFLAGS\"\n\tEXTRA_LIBS=\"$EXTRA_LIBS $BDB_LIBS\"\n\tAC_CHECK_TYPES([u_int], , , [[#include \"${srcdir}/compiler.h\"]\n\t                             [#include <sys/types.h>]\n\t                             [#include <db.h>]])\nfi\n\nAC_ARG_WITH(oss, AS_HELP_STRING([--without-oss],\n                                [Compile without OSS support]))\n\nif test \"x$with_oss\" != \"xno\"\nthen\n\tOSSLIBDIR=\"$with_oss\"\n\tif test \"x$with_oss\" = \"x\" || test \"x$with_oss\" = \"xyes\"\n\tthen\n\t\tOSSLIBDIR=\"/usr/lib/oss\"\n\t\tif test -f \"/etc/oss.conf\"\n\t\tthen\n\t\t\t. /etc/oss.conf\n\t\tfi\n\tfi\n\n\tif test -d \"$OSSLIBDIR/include\"\n\tthen\n\t\tOSS_CFLAGS=\"-I$OSSLIBDIR/include\"\n\tfi\n\n\tsave_CPPFLAGS=\"$CPPFLAGS\"\n\tCPPFLAGS=\"$CPPFLAGS $OSS_CFLAGS\"\n\tAC_CHECK_HEADERS([sys/soundcard.h soundcard.h])\n\tCPPFLAGS=\"$save_CPPFLAGS\"\n\n\tif test \"$ac_cv_header_sys_soundcard_h\" = \"yes\" -o \\\n\t        \"$ac_cv_header_soundcard_h\" = \"yes\"\n\tthen\n\t\tAC_DEFINE([HAVE_OSS], 1, [Define if you have OSS.])\n\t\tEXTRA_OBJS=\"$EXTRA_OBJS oss.o\"\n\t\tCFLAGS=\"$CFLAGS $OSS_CFLAGS\"\n\t\tSOUND_DRIVERS=\"$SOUND_DRIVERS OSS\"\n\t\tAC_CHECK_LIB([ossaudio], [_oss_ioctl],\n\t\t             [EXTRA_LIBS=\"$EXTRA_LIBS -lossaudio\"])\n\tfi\nfi\n\nAC_ARG_WITH(sndio, AS_HELP_STRING([--without-sndio],\n                                  [Compile without SNDIO support]))\n\nif test \"x$with_sndio\" != \"xno\"\nthen\n\tAC_CHECK_HEADERS([sndio.h])\n\tif test \"$ac_cv_header_sndio_h\" = \"yes\"\n\tthen\n\t\tAC_DEFINE([HAVE_SNDIO], 1, [Define if you have SNDIO.])\n\t\tEXTRA_OBJS=\"$EXTRA_OBJS sndio_out.o\"\n\t\tSOUND_DRIVERS=\"$SOUND_DRIVERS SNDIO\"\n\t\tAC_CHECK_LIB([sndio], [sio_open],\n\t\t             [EXTRA_LIBS=\"$EXTRA_LIBS -lsndio\"])\n\tfi\nfi\n\nAC_ARG_WITH(alsa, AS_HELP_STRING([--without-alsa],\n                                 [Compile without ALSA support]))\n\nCOMPILE_ALSA=\"no\"\nif test \"x$with_alsa\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(ALSA, [alsa >= 1.0.11],\n\t     [SOUND_DRIVERS=\"$SOUND_DRIVERS ALSA\"\n\t      EXTRA_OBJS=\"$EXTRA_OBJS alsa.o\"\n\t      AC_DEFINE([HAVE_ALSA], 1, [Define if you have ALSA.])\n\t      EXTRA_LIBS=\"$EXTRA_LIBS $ALSA_LIBS\"\n\t      COMPILE_ALSA=\"yes\"\n\t      CFLAGS=\"$CFLAGS $ALSA_CFLAGS\"],\n\t     [true])\nfi\n\nAC_ARG_WITH(jack, AS_HELP_STRING([--without-jack],\n                                 [Compile without JACK support]))\n\nif test \"x$with_jack\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(JACK, [jack >= 0.4],\n\t\t\t  [SOUND_DRIVERS=\"$SOUND_DRIVERS JACK\"\n\t\t\t   EXTRA_OBJS=\"$EXTRA_OBJS jack.o\"\n\t\t\t   AC_DEFINE([HAVE_JACK], 1, [Define if you have JACK.])\n\t\t\t   EXTRA_LIBS=\"$EXTRA_LIBS $JACK_LIBS\"\n\t\t\t   CFLAGS=\"$CFLAGS $JACK_CFLAGS\"\n\t\t\t   AC_SEARCH_LIBS(jack_client_open, jack,\n\t\t\t       [AC_DEFINE([HAVE_JACK_CLIENT_OPEN], 1,\n\t\t\t           [Define to 1 if you have the `jack_client_open' function.])])],\n\t\t\t  [true])\nfi\n\nAC_SUBST([SOUNDDRIVER])\ncase \"$host_os\" in\n\topenbsd*) SOUNDDRIVER=\"SNDIO:JACK:OSS\";;\n\t       *) SOUNDDRIVER=\"JACK:ALSA:OSS\";;\nesac\n\nAC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug],\n                                    [Enable debugging code]))\n\nif test \"x$enable_debug\" = \"xno\"\nthen\n\tAC_DEFINE([NDEBUG], 1, [Define if you don't want debugging code])\n\tCOMPILE_DEBUG='no'\nelse\n\tif test \"x$enable_debug\" = \"xgdb\"\n\tthen\n\t\tAX_CFLAGS_GCC_OPTION([-ggdb])\n\t\tAX_CFLAGS_GCC_OPTION([-O0])\n\t\tCOMPILE_DEBUG='gdb'\n\tfi\n\tif test \"x$ac_cv_cflags_gcc_option__ggdb\" = \"x\"\n\tthen\n\t\tAX_CFLAGS_GCC_OPTION([-g])\n\t\tCOMPILE_DEBUG='yes'\n\tfi\n\tEXTRA_OBJS=\"$EXTRA_OBJS null_out.o md5.o\"\nfi\n\nAC_FUNC_MALLOC\n\ndnl required POSIX (TMR/TSF/XSI) functions\nAC_CHECK_FUNCS([localtime_r nanosleep strcasecmp \\\n                strdup strncasecmp wcswidth],,\n\t       AC_MSG_ERROR([Required POSIX functions are not present.]))\n\ndnl optional functions\nAC_FUNC_STRERROR_R\nAC_CHECK_FUNCS([sched_get_priority_max syslog])\n\ndnl OSX / MacOS doesn't provide clock_gettime(3) prior to darwin-16.0.0\ndnl so fall back to gettimeofday(2).\ncase \"$host_os\" in\n[*darwin[1-9].*] | [*darwin1[0-5].*])\n\tHAVE_CLOCK_GETTIME=\"no\"\n\t;;\n*darwin*)\n\tHAVE_CLOCK_GETTIME=\"yes\"\n\t;;\n*)\n\tAC_CHECK_LIB([rt], [clock_gettime],\n\t\t[HAVE_CLOCK_GETTIME=\"yes\"],\n\t\t[HAVE_CLOCK_GETTIME=\"no\"])\n\t;;\nesac\n\nif test \"$HAVE_CLOCK_GETTIME\" = \"yes\"\nthen\n\tAC_DEFINE([HAVE_CLOCK_GETTIME], 1, [Define if you have clock_gettime(3)])\nelse\n\tAC_CHECK_FUNCS([gettimeofday],\n\t\t[AC_MSG_WARN([Using the obsolete gettimeofday(2) function.])],\n\t\t[AC_MSG_ERROR([No suitable current time function found.])])\nfi\n\ndnl strcasestr(3) is a GNU extension\nAC_CHECK_DECLS([strcasestr], , , [[#include \"${srcdir}/compiler.h\"]\n                                  [#include <string.h>]])\nAC_CHECK_FUNCS([strcasestr])\n\ndnl MIME magic\nAC_ARG_WITH(magic, AS_HELP_STRING([--without-magic],\n                                  [Compile without MIME magic support]))\nCOMPILE_MAGIC=\"no\"\nif test \"x$with_magic\" != \"xno\"\nthen\n\tAC_CHECK_LIB(magic, magic_open,\n\t\t[COMPILE_MAGIC=\"yes\"\n\t\t AC_DEFINE([HAVE_LIBMAGIC], 1, [Define if you have libmagic.])\n\t\t EXTRA_LIBS=\"$EXTRA_LIBS -lmagic\"])\nfi\n\nAC_FUNC_MMAP\n\ndnl POSIX threads\nAX_PTHREAD\nif test \"$ax_pthread_ok\" != \"yes\"\nthen\n\tAC_MSG_ERROR([[I don't know how to compile pthreads code on this system.]])\nfi\nCC=\"$PTHREAD_CC\"\nCFLAGS=\"$PTHREAD_CFLAGS $CFLAGS\"\nEXTRA_LIBS=\"$EXTRA_LIBS $PTHREAD_LIBS\"\nAC_CHECK_FUNCS([getrlimit])\nAC_CHECK_LIB([pthread], [pthread_attr_getstacksize],\n\t[AC_DEFINE([HAVE_PTHREAD_ATTR_GETSTACKSIZE], 1,\n\t\t[Define if you have pthread_attr_getstacksize(3).])])\n\ndnl __attribute__\nAX_C___ATTRIBUTE__\nif test \"x$ax_cv___attribute__\" = \"xyes\"\nthen\n\tAX_GCC_FUNC_ATTRIBUTE(format)\n\tAX_GCC_FUNC_ATTRIBUTE(noreturn)\n\tAX_GCC_VAR_ATTRIBUTE(aligned)\n\tAX_GCC_VAR_ATTRIBUTE(unused)\nfi\n\ndnl popt\nAC_SEARCH_LIBS([poptGetContext], [popt], ,\n               AC_MSG_ERROR([POPT (libpopt) not found.]))\n\ndnl ncurses\ndnl This 'unset' circumvents a notified bug in AX_WITH_CURSES when\ndnl _PKG_CONFIG succeeds but a stale 'pkg_failed' value is checked.\ndnl It can be removed once an updated 'ax_with_curses.m4' is committed.\nunset pkg_failed\nAX_WITH_CURSES\nif test \"x$ax_cv_curses\" != \"xyes\"\nthen\n\tAC_MSG_ERROR([You need curses/ncurses library and header files.])\nfi\nEXTRA_LIBS=\"$EXTRA_LIBS $CURSES_LIBS\"\ndnl Remove -D_GNU_SOURCE as it causes problems when defined mid-configure.\nfor flag in $CURSES_CFLAGS\ndo\n\tif test \"x$flag\" != \"x-D_GNU_SOURCE\"\n\tthen\n\t\tCPPFLAGS=\"$CPPFLAGS $flag\"\n\tfi\ndone\nAC_RUN_LOG([: The test below is actually for set_escdelay in curses library.])\nAC_RUN_LOG([: Using 'm' avoids unpacking CURSES_LIBS.])\nAC_CHECK_LIB([m], [set_escdelay],\n\tAC_DEFINE([HAVE_SET_ESCDELAY], 1, [Define if you have set_escdelay.]),\n\t[], [$CURSES_LIBS])\n\ndnl samplerate\nAC_ARG_WITH(samplerate, AS_HELP_STRING([--without-samplerate],\n                                       [Compile without libsamplerate]))\nCOMPILE_SAMPLERATE=\"no\"\nif test \"x$with_samplerate\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(samplerate, samplerate >= 0.1.0,\n\t\t\t  [EXTRA_LIBS=\"$EXTRA_LIBS $samplerate_LIBS\"\n\t\t\t   CFLAGS=\"$CFLAGS $samplerate_CFLAGS\"\n\t\t\t   AC_DEFINE([HAVE_SAMPLERATE], 1,\n\t\t\t\t     [Define if you have libsamplerate])\n\t\t\t   COMPILE_SAMPLERATE=\"yes\"],\n\t\t\t   [true])\nfi\n\ndnl Decoder plugins\nm4_include(decoder_plugins/decoders.m4)\n\ndnl Require with iconv for charset translation.\nAM_ICONV\nif test \"x$am_cv_func_iconv\" != xyes; then\n\tAC_MSG_ERROR([No iconv library found.])\nfi\nEXTRA_LIBS=\"$EXTRA_LIBS $LIBICONV\"\n\ndnl librcc\nCOMPILE_RCC=no\nAC_ARG_WITH(rcc, AS_HELP_STRING([--without-rcc],\n                                [Compile without LIBRCC support]))\nif test \"x$with_rcc\" != \"xno\"\nthen\nAC_CHECK_HEADERS([librcc.h],\n                 [AC_DEFINE([HAVE_RCC], 1, [Define if you have librcc.h])\n                  AC_CHECK_LIB(rcc, rccInit,\n                               [RCC_LIBS=\"-lrcc\"\n                                AC_SUBST([RCC_LIBS])\n                                COMPILE_RCC=yes])\n                 ])\n    if ! $PKG_CONFIG --atleast-version 0.2.10 librcc\n    then\n        UPGRADE_LIBRCC=\"yes\"\n    fi\nfi\n\ndnl curl\nCOMPILE_CURL=\"no\"\nAC_ARG_WITH(curl, AS_HELP_STRING([--without-curl],\n                                 [Compile without Network streams support]))\nif test \"x$with_curl\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(CURL, [libcurl >= 7.15.1],\n\t\t[EXTRA_OBJS=\"$EXTRA_OBJS io_curl.o\"\n\t\t AC_DEFINE([HAVE_CURL], 1, [Define if you have libcurl])\n\t\t EXTRA_LIBS=\"$EXTRA_LIBS $CURL_LIBS\"\n\t\t CFLAGS=\"$CFLAGS $CURL_CFLAGS\"\n\t\t COMPILE_CURL=\"yes\"\n\t\t if ! $PKG_CONFIG --atleast-version 7.15.4 libcurl\n\t\t then\n\t\t\tUPGRADE_LIBCURL=\"yes\"\n\t\t fi],\n\t\t[true])\nfi\n\ndnl Capture configuration options for this build.\nAC_DEFINE_UNQUOTED([CONFIGURATION], [\"$ac_configure_args\"],\n                   [Define to the configuration used to build MOC.])\n\ndnl Capture SVN revision number for this build.\nAC_PATH_PROG(SVNVERSION, [svnversion])\nif test -n \"$SVNVERSION\"\nthen\n\tSVNREVN=`$SVNVERSION -n $srcdir`\n\tSVNREVN=`expr \"$SVNREVN\" : '\\([[^:]]*\\)'`\n\tif test \"x$SVNREVN\" = \"xexported\"\n\tthen\n\t\tunset SVNREVN\n\telse\n\t\techo -n $SVNREVN > REVISION\n\t\tEXTRA_DISTS=\"$EXTRA_DISTS REVISION\"\n\tfi\nfi\nif test -z \"$SVNREVN\" && test -f $srcdir/REVISION\nthen\n\tSVNREVN=`cat $srcdir/REVISION`\n\tEXTRA_DISTS=\"$EXTRA_DISTS REVISION\"\nfi\nif test -n \"$SVNREVN\"\nthen\n\tAC_DEFINE_UNQUOTED([PACKAGE_REVISION], [\"$SVNREVN\"],\n\t                   [The SVN revision of this build.])\nfi\n\nAC_SUBST(EXTRA_LIBS)\nAC_SUBST(EXTRA_DISTS)\nAH_BOTTOM([#include \"compiler.h\"])\n\nAC_OUTPUT([Makefile\n\t  themes/Makefile\n\t  config.example\n\t  ])\n\necho\necho \"-----------------------------------------------------------------------\"\necho \"MOC will be compiled with:\"\necho\nif test ${#DECODER_PLUGINS} -le 50\nthen\n\techo \"Decoder plugins:  $DECODER_PLUGINS\"\nelse\n\tDECODERS_IX=`echo $DECODER_PLUGINS | $AWK '{match(substr($0, 1, 51), /.* /);print(RLENGTH)}'`\n\tDECODERS_1=`echo $DECODER_PLUGINS | $AWK \"{print(substr(\\\\$0, 1, $DECODERS_IX - 1))}\"`\n\tDECODERS_2=`echo $DECODER_PLUGINS | $AWK \"{print(substr(\\\\$0, $DECODERS_IX + 1))}\"`\n\techo \"Decoder plugins:   $DECODERS_1\"\n\techo \"                   $DECODERS_2\"\nfi\necho \"Sound Drivers:    \"$SOUND_DRIVERS\necho \"DEBUG:             \"$COMPILE_DEBUG\necho \"RCC:               \"$COMPILE_RCC\necho \"Network streams:   \"$COMPILE_CURL\necho \"Resampling:        \"$COMPILE_SAMPLERATE\necho \"MIME magic:        \"$COMPILE_MAGIC\necho \"-----------------------------------------------------------------------\"\necho\n\nif test \"x$UPGRADE_TAGLIB\" = \"xyes\"\nthen\n\techo \"WARNING: MOC will soon require TagLib version 1.5 or later;\"\n\techo \"         plan to upgrade your TagLib soon.\"\n\techo\nfi\n\nif test \"x$UPGRADE_MUSEPACK\" = \"xyes\"\nthen\n\techo \"WARNING: MOC will soon require Musepack libmpc (rather than libmpcdec);\"\n\techo \"         plan to upgrade your Musepack soon.\"\n\techo\nfi\n\nif test \"x$UPGRADE_LIBRCC\" = \"xyes\"\nthen\n\techo \"WARNING: MOC will soon require librcc version 0.2.10 or later;\"\n\techo \"         plan to upgrade your RCC library soon.\"\n\techo\nfi\n\nif test \"x$UPGRADE_LIBCURL\" = \"xyes\"\nthen\n\techo \"WARNING: MOC will soon require libcurl version 7.15.4 or later;\"\n\techo \"         plan to upgrade your cURL library soon.\"\n\techo\nfi\n\nif test \"x$DECODER_PLUGINS\" = \"x\"\nthen\n\techo \"WARNING: No decoder plugins are to be compiled;\"\n\techo \"         you will have to provide them separately.\"\n\techo\nfi\n\nif test \"x$SOUND_DRIVERS\" = \"x\"\nthen\n\techo \"WARNING: No sound output methods are to be compiled;\"\n\techo \"         you will not hear any sound!\"\n\techo\nfi\n\nif test \"x$cross_compiling\" = \"xyes\"\nthen\n\techo \"WARNING: Because of cross-compilation, some tests could not be run\"\n\techo \"         and MOC may fail with memory access or illegal instruction\"\n\techo \"         errors when loading plug-ins.\"\n\techo\nfi\n\nif test \"x$WARN_POINTER_MISMATCH\" = \"xyes\"\nthen\n\techo \"WARNING: Because the sizes of data and function pointers are not\"\n\techo \"         the same on this architecture, MOC may fail with memory\"\n\techo \"         access or illegal instruction errors when loading plug-ins.\"\n\techo\nfi\n\nif test \"x$COMPILE_SAMPLERATE\" = \"xno\" -a \"x$COMPILE_ALSA\" = \"xyes\"\nthen\n\techo \"WARNING: Without libsamplerate, the 'ALSAStutterDefeat' option\"\n\techo \"         may severely restrict the number of audios playable.\"\n\techo\nfi\n"
  },
  {
    "path": "decoder.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <string.h>\n#include <strings.h>\n#include <ctype.h>\n#include <stdarg.h>\n#include <errno.h>\n#include <assert.h>\n#include <ltdl.h>\n\n#include \"common.h\"\n#include \"decoder.h\"\n#include \"files.h\"\n#include \"log.h\"\n#include \"io.h\"\n#include \"options.h\"\n\nstatic struct plugin {\n\tchar *name;\n\tlt_dlhandle handle;\n\tstruct decoder *decoder;\n} plugins[16];\n\n#define PLUGINS_NUM\t\t\t(ARRAY_SIZE(plugins))\n\nstatic int plugins_num = 0;\n\nstatic bool have_tremor = false;\n\n/* This structure holds the user's decoder preferences for audio formats. */\nstruct decoder_s_preference {\n\tstruct decoder_s_preference *next;    /* chain pointer */\n#ifdef DEBUG\n\tconst char *source;                   /* entry in PreferredDecoders */\n#endif\n\tint decoders;                         /* number of decoders */\n\tint decoder_list[PLUGINS_NUM];        /* decoder indices */\n\tchar *subtype;                        /* MIME subtype or NULL */\n\tchar type[];                          /* MIME type or filename extn */\n};\ntypedef struct decoder_s_preference decoder_t_preference;\nstatic decoder_t_preference *preferences = NULL;\nstatic int default_decoder_list[PLUGINS_NUM];\n\nstatic char *clean_mime_subtype (char *subtype)\n{\n\tchar *ptr;\n\n\tassert (subtype && subtype[0]);\n\n\tif (!strncasecmp (subtype, \"x-\", 2))\n\t\tsubtype += 2;\n\n\tptr = strchr (subtype, ';');\n\tif (ptr)\n\t\t*ptr = 0x00;\n\n\treturn subtype;\n}\n\n/* Find a preference entry matching the given filename extension and/or\n * MIME media type, or NULL. */\nstatic decoder_t_preference *lookup_preference (const char *extn,\n                                                const char *file,\n                                                char **mime)\n{\n\tchar *type, *subtype;\n\tdecoder_t_preference *result;\n\n\tassert ((extn && extn[0]) || (file && file[0])\n\t                          || (mime && *mime && *mime[0]));\n\n\ttype = NULL;\n\tsubtype = NULL;\n\tfor (result = preferences; result; result = result->next) {\n\t\tif (!result->subtype) {\n\t\t\tif (extn && !strcasecmp (result->type, extn))\n\t\t\t\tbreak;\n\t\t}\n\t\telse {\n\n\t\t\tif (!type) {\n\t\t\t\tif (mime && *mime == NULL && file && file[0]) {\n\t\t\t\t\tif (options_get_bool (\"UseMimeMagic\"))\n\t\t\t\t\t\t*mime = file_mime_type (file);\n\t\t\t\t}\n\t\t\t\tif (mime && *mime && strchr (*mime, '/'))\n\t\t\t\t\ttype = xstrdup (*mime);\n\t\t\t\tif (type) {\n\t\t\t\t\tsubtype = strchr (type, '/');\n\t\t\t\t\t*subtype++ = 0x00;\n\t\t\t\t\tsubtype = clean_mime_subtype (subtype);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (type) {\n\t\t\t\tif (!strcasecmp (result->type, type) &&\n\t\t\t    \t!strcasecmp (result->subtype, subtype))\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t}\n\t}\n\n\tfree (type);\n\treturn result;\n}\n\n/* Return the index of the first decoder able to handle files with the\n * given filename extension, or -1 if none can. */\nstatic int find_extn_decoder (int *decoder_list, int count, const char *extn)\n{\n\tint ix;\n\n\tassert (decoder_list);\n\tassert (RANGE(0, count, plugins_num));\n\tassert (extn && extn[0]);\n\n\tfor (ix = 0; ix < count; ix += 1) {\n\t\tif (plugins[decoder_list[ix]].decoder->our_format_ext &&\n\t\t    plugins[decoder_list[ix]].decoder->our_format_ext (extn))\n\t\t\treturn decoder_list[ix];\n\t}\n\n\treturn -1;\n}\n\n/* Return the index of the first decoder able to handle audio with the\n * given MIME media type, or -1 if none can. */\nstatic int find_mime_decoder (int *decoder_list, int count, const char *mime)\n{\n\tint ix;\n\n\tassert (decoder_list);\n\tassert (RANGE(0, count, plugins_num));\n\tassert (mime && mime[0]);\n\n\tfor (ix = 0; ix < count; ix += 1) {\n\t\tif (plugins[decoder_list[ix]].decoder->our_format_mime &&\n\t\t    plugins[decoder_list[ix]].decoder->our_format_mime (mime))\n\t\t\treturn decoder_list[ix];\n\t}\n\n\treturn -1;\n}\n\n/* Return the index of the first decoder able to handle audio with the\n * given filename extension and/or MIME media type, or -1 if none can. */\nstatic int find_decoder (const char *extn, const char *file, char **mime)\n{\n\tint result;\n\tdecoder_t_preference *pref;\n\n\tassert ((extn && extn[0]) || (file && file[0]) || (mime && *mime));\n\n\tpref = lookup_preference (extn, file, mime);\n\tif (pref) {\n\t\tif (pref->subtype)\n\t\t\treturn find_mime_decoder (pref->decoder_list, pref->decoders, *mime);\n\t\telse\n\t\t\treturn find_extn_decoder (pref->decoder_list, pref->decoders, extn);\n\t}\n\n\tresult = -1;\n\tif (mime && *mime)\n\t\tresult = find_mime_decoder (default_decoder_list, plugins_num, *mime);\n\tif (result == -1 && extn && *extn)\n\t\tresult = find_extn_decoder (default_decoder_list, plugins_num, extn);\n\n\treturn result;\n}\n\n/* Find the index in plugins table for the given file.\n * Return -1 if not found. */\nstatic int find_type (const char *file)\n{\n\tint result = -1;\n\tchar *extn, *mime;\n\n\textn = ext_pos (file);\n\tmime = NULL;\n\n\tresult = find_decoder (extn, file, &mime);\n\n\tfree (mime);\n\treturn result;\n}\n\nint is_sound_file (const char *name)\n{\n\treturn find_type(name) != -1 ? 1 : 0;\n}\n\n/* Return short type name for the given file or NULL if not found.\n * Not thread safe! */\nchar *file_type_name (const char *file)\n{\n\tint i;\n\tstatic char buf[4];\n\n\tif (file_type (file) == F_URL) {\n\t\tstrcpy (buf, \"NET\");\n\t\treturn buf;\n\t}\n\n\ti = find_type (file);\n\tif (i == -1)\n\t\treturn NULL;\n\n\tmemset (buf, 0, sizeof (buf));\n\tif (plugins[i].decoder->get_name)\n\t\tplugins[i].decoder->get_name (file, buf);\n\n\t/* Attempt a default name if we have nothing else. */\n\tif (!buf[0]) {\n\t\tchar *ext;\n\n\t\text = ext_pos (file);\n\t\tif (ext) {\n\t\t\tsize_t len;\n\n\t\t\tlen = strlen (ext);\n\t\t\tfor (size_t ix = 0; ix < len; ix += 1) {\n\t\t\t\tif (ix > 1) {\n\t\t\t\t\tbuf[2] = toupper (ext[len - 1]);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbuf[ix] = toupper (ext[ix]);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!buf[0])\n\t\treturn NULL;\n\n\treturn buf;\n}\n\nstruct decoder *get_decoder (const char *file)\n{\n\tint i;\n\n\ti = find_type (file);\n\tif (i != -1)\n\t\treturn plugins[i].decoder;\n\n\treturn NULL;\n}\n\n/* Given a decoder pointer, return its name. */\nconst char *get_decoder_name (const struct decoder *decoder)\n{\n\tint ix;\n\tconst char *result = NULL;\n\n\tassert (decoder);\n\n\tfor (ix = 0; ix < plugins_num; ix += 1) {\n\t\tif (plugins[ix].decoder == decoder) {\n\t\t\tresult = plugins[ix].name;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tassert (result);\n\n\treturn result;\n}\n\n/* Use the stream's MIME type to return a decoder for it, or NULL if no\n * applicable decoder was found. */\nstatic struct decoder *get_decoder_by_mime_type (struct io_stream *stream)\n{\n\tint i;\n\tchar *mime;\n\tstruct decoder *result;\n\n\tresult = NULL;\n\tmime = xstrdup (io_get_mime_type (stream));\n\tif (mime) {\n\t\ti = find_decoder (NULL, NULL, &mime);\n\t\tif (i != -1) {\n\t\t\tlogit (\"Found decoder for MIME type %s: %s\", mime, plugins[i].name);\n\t\t\tresult = plugins[i].decoder;\n\t\t}\n\t\tfree (mime);\n\t}\n\telse\n\t\tlogit (\"No MIME type.\");\n\n\treturn result;\n}\n\n/* Return the decoder for this stream. */\nstruct decoder *get_decoder_by_content (struct io_stream *stream)\n{\n\tchar buf[8096];\n\tssize_t res;\n\tint i;\n\tstruct decoder *decoder_by_mime_type;\n\n\tassert (stream != NULL);\n\n\t/* Peek at the start of the stream to check if sufficient data is\n\t * available.  If not, there is no sense in trying the decoders as\n\t * each of them would issue an error.  The data is also needed to\n\t * get the MIME type. */\n\tlogit (\"Testing the stream...\");\n\tres = io_peek (stream, buf, sizeof (buf));\n\tif (res < 0) {\n\t\terror (\"Stream error: %s\", io_strerror (stream));\n\t\treturn NULL;\n\t}\n\n\tif (res < 512) {\n\t\tlogit (\"Stream too short\");\n\t\treturn NULL;\n\t}\n\n\tdecoder_by_mime_type = get_decoder_by_mime_type (stream);\n\tif (decoder_by_mime_type)\n\t\treturn decoder_by_mime_type;\n\n\tfor (i = 0; i < plugins_num; i++) {\n\t\tif (plugins[i].decoder->can_decode\n\t\t\t\t&& plugins[i].decoder->can_decode (stream)) {\n\t\t\tlogit (\"Found decoder for stream: %s\", plugins[i].name);\n\t\t\treturn plugins[i].decoder;\n\t\t}\n\t}\n\n\terror (\"Format not supported\");\n\treturn NULL;\n}\n\n/* Extract decoder name from file name. */\nstatic char *extract_decoder_name (const char *filename)\n{\n\tint len;\n\tconst char *ptr;\n\tchar *result;\n\n\tif (!strncmp (filename, \"lib\", 3))\n\t\tfilename += 3;\n\tlen = strlen (filename);\n\tptr = strpbrk (filename, \"_.-\");\n\tif (ptr)\n\t\tlen = ptr - filename;\n\tresult = xmalloc (len + 1);\n\tstrncpy (result, filename, len);\n\tresult[len] = 0x00;\n\n\treturn result;\n}\n\n/* Return the index for a decoder of the given name, or plugins_num if\n * not found. */\nstatic int lookup_decoder_by_name (const char *name)\n{\n\tint result;\n\n\tassert (name && name[0]);\n\n\tresult = 0;\n\twhile (result < plugins_num) {\n\t\tif (!strcasecmp (plugins[result].name, name))\n\t\t\tbreak;\n\t\tresult += 1;\n\t}\n\n\treturn result;\n}\n\n/* Return a string of concatenated driver names. */\nstatic char *list_decoder_names (int *decoder_list, int count)\n{\n\tint ix;\n\tchar *result;\n\tlists_t_strs *names;\n\n\tif (count == 0)\n\t\treturn xstrdup (\"\");\n\n\tnames = lists_strs_new (count);\n\tfor (ix = 0; ix < count; ix += 1)\n\t\tlists_strs_append (names, plugins[decoder_list[ix]].name);\n\n\tif (have_tremor) {\n\t\tix = lists_strs_find (names, \"vorbis\");\n\t\tif (ix < lists_strs_size (names))\n\t\t\tlists_strs_replace (names, ix, \"vorbis(tremor)\");\n\t}\n\n\tix = lists_strs_find (names, \"ffmpeg\");\n\tif (ix < lists_strs_size (names)) {\n#if defined(HAVE_FFMPEG)\n\t\t\tlists_strs_replace (names, ix, \"ffmpeg\");\n#elif defined(HAVE_LIBAV)\n\t\t\tlists_strs_replace (names, ix, \"ffmpeg(libav)\");\n#else\n\t\t\tlists_strs_replace (names, ix, \"ffmpeg/libav\");\n#endif\n\t}\n\n\tresult = lists_strs_fmt (names, \" %s\");\n\tlists_strs_free (names);\n\n\treturn result;\n}\n\n/* Check if this handle is already present in the plugins table.\n * Returns 1 if so. */\nstatic int present_handle (const lt_dlhandle h)\n{\n\tint i;\n\n\tfor (i = 0; i < plugins_num; i++) {\n\t\tif (plugins[i].handle == h)\n\t\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nstatic int lt_load_plugin (const char *file, lt_ptr debug_info_ptr)\n{\n\tint debug_info;\n\tconst char *name;\n\tunion {\n\t\tvoid *data;\n\t\tplugin_init_func *func;\n\t} init;\n\n\tdebug_info = *(int *)debug_info_ptr;\n\tname = strrchr (file, '/');\n\tname = name ? (name + 1) : file;\n\tif (debug_info)\n\t\tprintf (\"Loading plugin %s...\\n\", name);\n\n\tif (plugins_num == PLUGINS_NUM) {\n\t\tfprintf (stderr, \"Can't load plugin, because maximum number \"\n\t\t                                    \"of plugins reached!\\n\");\n\t\treturn 0;\n\t}\n\n\tplugins[plugins_num].handle = lt_dlopenext (file);\n\tif (!plugins[plugins_num].handle) {\n\t\tfprintf (stderr, \"Can't load plugin %s: %s\\n\", name, lt_dlerror ());\n\t\treturn 0;\n\t}\n\n\tif (present_handle (plugins[plugins_num].handle)) {\n\t\tif (debug_info)\n\t\t\tprintf (\"Already loaded\\n\");\n\t\tif (lt_dlclose (plugins[plugins_num].handle))\n\t\t\tfprintf (stderr, \"Error unloading plugin: %s\\n\", lt_dlerror ());\n\t\treturn 0;\n\t}\n\n\tinit.data = lt_dlsym (plugins[plugins_num].handle, \"plugin_init\");\n\tif (!init.data) {\n\t\tfprintf (stderr, \"No init function in the plugin!\\n\");\n\t\tif (lt_dlclose (plugins[plugins_num].handle))\n\t\t\tfprintf (stderr, \"Error unloading plugin: %s\\n\", lt_dlerror ());\n\t\treturn 0;\n\t}\n\n\t/* If this call to init.func() fails with memory access or illegal\n\t * instruction errors then read the commit log message for r2831. */\n\tplugins[plugins_num].decoder = init.func ();\n\tif (!plugins[plugins_num].decoder) {\n\t\tfprintf (stderr, \"NULL decoder!\\n\");\n\t\tif (lt_dlclose (plugins[plugins_num].handle))\n\t\t\tfprintf (stderr, \"Error unloading plugin: %s\\n\", lt_dlerror ());\n\t\treturn 0;\n\t}\n\n\tif (plugins[plugins_num].decoder->api_version != DECODER_API_VERSION) {\n\t\tfprintf (stderr, \"Plugin uses different API version\\n\");\n\t\tif (lt_dlclose (plugins[plugins_num].handle))\n\t\t\tfprintf (stderr, \"Error unloading plugin: %s\\n\", lt_dlerror ());\n\t\treturn 0;\n\t}\n\n\tplugins[plugins_num].name = extract_decoder_name (name);\n\n\t/* Is the Vorbis decoder using Tremor? */\n\tif (!strcmp (plugins[plugins_num].name, \"vorbis\")) {\n\t\tvoid *vorbis_has_tremor;\n\n\t\tvorbis_has_tremor = lt_dlsym (plugins[plugins_num].handle,\n\t\t                              \"vorbis_has_tremor\");\n\t\thave_tremor = vorbis_has_tremor != NULL;\n\t}\n\n\tdebug (\"Loaded %s decoder\", plugins[plugins_num].name);\n\n\tif (plugins[plugins_num].decoder->init)\n\t\tplugins[plugins_num].decoder->init ();\n\tplugins_num += 1;\n\n\tif (debug_info)\n\t\tprintf (\"OK\\n\");\n\n\treturn 0;\n}\n\n/* Create a new preferences entry and initialise it. */\nstatic decoder_t_preference *make_preference (const char *prefix)\n{\n\tdecoder_t_preference *result;\n\n\tassert (prefix && prefix[0]);\n\n\tresult = (decoder_t_preference *)xmalloc (\n\t\toffsetof (decoder_t_preference, type) + strlen (prefix) + 1\n\t);\n\tresult->next = NULL;\n\tresult->decoders = 0;\n\tstrcpy (result->type, prefix);\n\tresult->subtype = strchr (result->type, '/');\n\tif (result->subtype) {\n\t\t*result->subtype++ = 0x00;\n\t\tresult->subtype = clean_mime_subtype (result->subtype);\n\t}\n\n\treturn result;\n}\n\n/* Is the given decoder (by index) already in the decoder list for 'pref'? */\nstatic bool is_listed_decoder (decoder_t_preference *pref, int d)\n{\n\tint ix;\n\tbool result;\n\n\tassert (pref);\n\tassert (d >= 0);\n\n\tresult = false;\n\tfor (ix = 0; ix < pref->decoders; ix += 1) {\n\t\tif (d == pref->decoder_list[ix]) {\n\t\t\tresult = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/* Add the named decoder (if valid) to a preferences decoder list. */\nstatic void load_each_decoder (decoder_t_preference *pref, const char *name)\n{\n\tint d;\n\n\tassert (pref);\n\tassert (name && name[0]);\n\n\td = lookup_decoder_by_name (name);\n\n\t/* Drop unknown decoders. */\n\tif (d == plugins_num)\n\t\treturn;\n\n\t/* Drop duplicate decoders. */\n\tif (is_listed_decoder (pref, d))\n\t\treturn;\n\n\tpref->decoder_list[pref->decoders++] = d;\n\n\treturn;\n}\n\n/* Build a preference's decoder list. */\nstatic void load_decoders (decoder_t_preference *pref, lists_t_strs *tokens)\n{\n\tint ix, dx, asterisk_at;\n\tint decoder[PLUGINS_NUM];\n\tconst char *name;\n\n\tassert (pref);\n\tassert (tokens);\n\n\tasterisk_at = -1;\n\n\t/* Add the index of each known decoder to the decoders list.\n\t * Note the position following the first asterisk. */\n\tfor (ix = 1; ix < lists_strs_size (tokens); ix += 1) {\n\t\tname = lists_strs_at (tokens, ix);\n\t\tif (strcmp (name, \"*\"))\n\t\t\tload_each_decoder (pref, name);\n\t\telse if (asterisk_at == -1)\n\t\t\tasterisk_at = pref->decoders;\n\t}\n\n\tif (asterisk_at == -1)\n\t\treturn;\n\n\tdx = 0;\n\n\t/* Find decoders not already listed. */\n\tfor (ix = 0; ix < plugins_num; ix += 1) {\n\t\tif (!is_listed_decoder (pref, ix))\n\t\t\tdecoder[dx++] = ix;\n\t}\n\n\t/* Splice asterisk decoders into the decoder list. */\n\tfor (ix = 0; ix < dx; ix += 1) {\n\t\tpref->decoder_list[pref->decoders++] =\n\t\t      pref->decoder_list[asterisk_at + ix];\n\t\tpref->decoder_list[asterisk_at + ix] = decoder[ix];\n\t}\n\n\tassert (RANGE(0, pref->decoders, plugins_num));\n}\n\n/* Add a new preference for an audio format. */\nstatic void load_each_preference (const char *preference)\n{\n\tconst char *prefix;\n\tlists_t_strs *tokens;\n\tdecoder_t_preference *pref;\n\n\tassert (preference && preference[0]);\n\n\ttokens = lists_strs_new (4);\n\tlists_strs_split (tokens, preference, \"(,)\");\n\tprefix = lists_strs_at (tokens, 0);\n\tpref = make_preference (prefix);\n#ifdef DEBUG\n\tpref->source = preference;\n#endif\n\tload_decoders (pref, tokens);\n\tpref->next = preferences;\n\tpreferences = pref;\n\tlists_strs_free (tokens);\n}\n\n/* Load all preferences given by the user in PreferredDecoders. */\nstatic void load_preferences ()\n{\n\tint ix;\n\tconst char *preference;\n\tlists_t_strs *list;\n\n\tlist = options_get_list (\"PreferredDecoders\");\n\n\tfor (ix = 0; ix < lists_strs_size (list); ix += 1) {\n\t\tpreference = lists_strs_at (list, ix);\n\t\tload_each_preference (preference);\n\t}\n\n#ifdef DEBUG\n\t{\n\t\tchar *names;\n\t\tdecoder_t_preference *pref;\n\n\t\tfor (pref = preferences; pref; pref = pref->next) {\n\t\t\tnames = list_decoder_names (pref->decoder_list, pref->decoders);\n\t\t\tdebug (\"%s:%s\", pref->source, names);\n\t\t\tfree (names);\n\t\t}\n\t}\n#endif\n}\n\nstatic void load_plugins (int debug_info)\n{\n\tint ix;\n\tchar *names;\n\n\tif (debug_info)\n\t\tprintf (\"Loading plugins from %s...\\n\", PLUGIN_DIR);\n\tif (lt_dlinit ())\n\t\tfatal (\"lt_dlinit() failed: %s\", lt_dlerror ());\n\n\tif (lt_dlforeachfile (PLUGIN_DIR, &lt_load_plugin, &debug_info))\n\t\tfatal (\"Can't load plugins: %s\", lt_dlerror ());\n\n\tif (plugins_num == 0)\n\t\tfatal (\"No decoder plugins have been loaded!\");\n\n\tfor (ix = 0; ix < plugins_num; ix += 1)\n\t\tdefault_decoder_list[ix] = ix;\n\n\tnames = list_decoder_names (default_decoder_list, plugins_num);\n\tlogit (\"Loaded %d decoders:%s\", plugins_num, names);\n\tfree (names);\n}\n\nvoid decoder_init (int debug_info)\n{\n\tload_plugins (debug_info);\n\tload_preferences ();\n}\n\nstatic void cleanup_decoders ()\n{\n\tint ix;\n\n\tfor (ix = 0; ix < plugins_num; ix++) {\n\t\tif (plugins[ix].decoder->destroy)\n\t\t\tplugins[ix].decoder->destroy ();\n\t\tfree (plugins[ix].name);\n\t\tif (plugins[ix].handle)\n\t\t\tlt_dlclose (plugins[ix].handle);\n\t}\n\n\tif (lt_dlexit ())\n\t\tlogit (\"lt_exit() failed: %s\", lt_dlerror ());\n}\n\nstatic void cleanup_preferences ()\n{\n\tdecoder_t_preference *pref, *next;\n\n\tpref = preferences;\n\tfor (pref = preferences; pref; pref = next) {\n\t\tnext = pref->next;\n\t\tfree (pref);\n\t}\n\n\tpreferences = NULL;\n}\n\nvoid decoder_cleanup ()\n{\n\tcleanup_decoders ();\n\tcleanup_preferences ();\n}\n\n/* Fill the error structure with an error of a given type and message.\n * strerror(add_errno) is appended at the end of the message if add_errno != 0.\n * The old error message is free()ed.\n * This is thread safe; use this instead of constructs using strerror(). */\nvoid decoder_error (struct decoder_error *error,\n\t\tconst enum decoder_error_type type, const int add_errno,\n\t\tconst char *format, ...)\n{\n\tchar *err_str;\n\tva_list va;\n\n\tif (error->err)\n\t\tfree (error->err);\n\n\terror->type = type;\n\n\tva_start (va, format);\n\terr_str = format_msg_va (format, va);\n\tva_end (va);\n\n\tif (add_errno) {\n\t\tchar *err_buf;\n\n\t\terr_buf = xstrerror (add_errno);\n\t\terror->err = format_msg (\"%s%s\", err_str, err_buf);\n\t\tfree (err_buf);\n\t}\n\telse\n\t\terror->err = format_msg (\"%s\", err_str);\n\n\tfree (err_str);\n}\n\n/* Initialize the decoder_error structure. */\nvoid decoder_error_init (struct decoder_error *error)\n{\n\terror->type = ERROR_OK;\n\terror->err = NULL;\n}\n\n/* Set the decoder_error structure to contain \"success\" information. */\nvoid decoder_error_clear (struct decoder_error *error)\n{\n\terror->type = ERROR_OK;\n\tif (error->err) {\n\t\tfree (error->err);\n\t\terror->err = NULL;\n\t}\n}\n\nvoid decoder_error_copy (struct decoder_error *dst,\n\t\tconst struct decoder_error *src)\n{\n\tdst->type = src->type;\n\tdst->err = xstrdup (src->err);\n}\n\n/* Return the error text from the decoder_error variable. */\nconst char *decoder_error_text (const struct decoder_error *error)\n{\n\treturn error->err;\n}\n"
  },
  {
    "path": "decoder.h",
    "content": "#ifndef DECODER_H\n#define DECODER_H\n\n#include \"audio.h\"\n#include \"playlist.h\"\n#include \"io.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/** Version of the decoder API.\n *\n * On every change in the decoder API this number will be changed, so\n * MOC will not load plugins compiled with older/newer decoder.h. */\n#define DECODER_API_VERSION\t7\n\n/** Type of the decoder error. */\nenum decoder_error_type\n{\n\tERROR_OK, /*!< There was no error. */\n\tERROR_STREAM, /*!< Recoverable error in the stream. */\n\tERROR_FATAL /*!< Fatal error in the stream - further decoding can't\n\t\t      be performed. */\n};\n\n/** Decoder error.\n *\n * Describes decoder error. Fields don't need to be accessed directly,\n * there are functions to modify/access decoder_error object. */\nstruct decoder_error\n{\n\tenum decoder_error_type type; /*!< Type of the error. */\n\tchar *err;\t/*!< malloc()ed error string or NULL. */\n};\n\n/** @struct decoder\n * Functions provided by the decoder plugin.\n *\n * Describes decoder - contains pointers to decoder's functions. If some\n * field is optional, it may have NULL value. */\nstruct decoder\n{\n\t/** API version used by the plugin.\n\t *\n\t * Set it to DECODER_API_VERSION to recognize if MOC and the plugin can\n\t * communicate. This is the first field in the structure, so even after\n\t * changing other fields it will hopefully be always read properly.\n\t */\n\tint api_version;\n\n\t/** Initialize the plugin.\n\t *\n\t * This function is called once at MOC startup (once for the client and\n\t * once for the server). Optional. */\n\tvoid (*init) ();\n\n\t/** Cleanup the plugin.\n\t *\n\t * This function is called once at exit (once for the client and\n\t * once for the server). Optional. */\n\tvoid (*destroy) ();\n\n\t/** Open the resource.\n\t *\n\t * Open the given resource (file).\n\t *\n\t * \\param uri URL to the resource that can be used as the file parameter\n\t * and return pointer to io_open().\n\t *\n\t * \\return Private decoder data. This pointer will be passed to every\n\t * other function that operates on the stream.\n\t */\n\tvoid *(*open)(const char *uri);\n\n\t/** Open the resource for an already opened stream.\n\t *\n\t * Handle the stream that was already opened, but no data were read.\n\t * You must operate on the stream using io_*() functions. This is used\n\t * for internet streams, so seeking is not possible. This function is\n\t * optional.\n\t *\n\t * \\param stream Opened stream from which the decoder must read.\n\t *\n\t * \\return Private decoder data. This pointer will be passed to every\n\t * other function that operates on the stream.\n\t */\n\tvoid *(*open_stream)(struct io_stream *stream);\n\n\t/** Check if the decoder is able to decode from this stream.\n\t *\n\t * Used to check if the decoder is able to read from an already opened\n\t * stream. This is used to find the proper decoder for an internet\n\t * stream when searching by the MIME type failed. The decoder must not\n\t * read from this stream (io_read()), but can peek data (io_peek()).\n\t * The decoder is expected to peek a few bytes to recognize its format.\n\t * Optional.\n\t *\n\t * \\param stream Opened stream.\n\t *\n\t * \\return 1 if the decoder is able to decode data from this stream.\n\t */\n\tint (*can_decode)(struct io_stream *stream);\n\n\t/** Close the resource and cleanup.\n\t *\n\t * Free all decoder's private data and allocated resources.\n\t *\n\t * \\param data Decoder's private data.\n\t */\n\tvoid (*close)(void *data);\n\n\t/** Decode a piece of input.\n\t *\n\t * Decode a piece of input and write it to the buffer. The buffer size\n\t * is at least 32KB, but don't make any assumptions that it is always\n\t * true. It is preferred that as few bytes as possible be decoded\n\t * without loss of performance to minimise delays.\n\t *\n\t * \\param data Decoder's private data.\n\t * \\param buf Buffer to put data in.\n\t * \\param buf_len Size of the buffer in bytes.\n\t * \\param sound_params Parameters of the decoded sound. This must be\n\t * always filled.\n\t *\n\t * \\return Number of bytes written or 0 on EOF.\n\t */\n\tint (*decode)(void *data, char *buf, int buf_len,\n\t\t\tstruct sound_params *sound_params);\n\n\t/** Seek in the stream.\n\t *\n\t * Seek to the given position.\n\t *\n\t * \\param data Decoder's private data.\n\t * \\param sec Where to seek in seconds (never less than zero).\n\t *\n\t * \\return The position that we actually seek to or -1 on error.\n\t * -1 is not a fatal error and further decoding will be performed.\n\t */\n\tint (*seek)(void *data, int sec);\n\n\t/** Get tags for a file.\n\t *\n\t * Get requested file's tags. If some tags are not available, the\n\t * decoder can just not fill the field. The function can even not\n\t * fill any field.\n\t *\n\t * \\param file File for which to get tags.\n\t * \\param tags Pointer to the tags structure where we must put\n\t * the tags. All strings must be malloc()ed.\n\t * \\param tags_sel OR'ed list of requested tags (values of\n\t * enum tags_select).\n\t */\n\tvoid (*info)(const char *file, struct file_tags *tags,\n\t\t\tconst int tags_sel);\n\n\t/** Get the current bitrate.\n\t *\n\t * Get the bitrate of the last decoded piece of sound.\n\t *\n\t * \\param data Decoder's private data.\n\t *\n\t * \\return Current bitrate in kbps or -1 if not available.\n\t */\n\tint (*get_bitrate)(void *data);\n\n\t/** Get duration of the stream.\n\t *\n\t * Get duration of the stream. It is used as a faster alternative\n\t * for getting duration than using info() if the file is opened.\n\t *\n\t * \\param data Decoder's private data.\n\t *\n\t * \\return Duration in seconds or -1 on error. -1 is not a fatal\n\t * error, further decoding will be performed.\n\t */\n\tint (*get_duration)(void *data);\n\n\t/** Get error for the last decode() invocation.\n\t *\n\t * Get the error for the last decode() invocation. If there was no\n\t * error the type of the error must be ERROR_OK. Don't access the\n\t * error object's fields directly, there are proper functions for\n\t * that.\n\t *\n\t * \\param data Decoder's private data.\n\t * \\param error Pointer to the decoder_error object to fill.\n\t */\n\tvoid (*get_error)(void *data, struct decoder_error *error);\n\n\t/** Check if the file extension is for a file that this decoder\n\t * supports.\n\t *\n\t * \\param ext Extension (chars after the last dot in the file name).\n\t *\n\t * \\return Value other than 0 if the extension if a file with this\n\t * extension is supported.\n\t */\n\tint (*our_format_ext)(const char *ext);\n\n\t/** Check if a stream with the given MIME type is supported by this\n\t * decoder. Optional.\n\t *\n\t * \\param mime_type MIME type.\n\t *\n\t * \\return Value other than 0 if a stream with this MIME type is\n\t * supported.\n\t */\n\tint (*our_format_mime)(const char *mime_type);\n\n\t/** Get a 3-chars format name for a file.\n\t *\n\t * Get an abbreviated format name (up to 3 chars) for a file.\n\t * This function is optional.\n\t *\n\t * \\param file File for which we want the format name.\n\t * \\param buf Buffer where the nul-terminated format name may be put.\n\t */\n\tvoid (*get_name)(const char *file, char buf[4]);\n\n\t/** Get current tags for the stream.\n\t *\n\t * Fill the tags structure with the current tags for the stream. This\n\t * is intended for internet streams and used when the source of the\n\t * stream doesn't provide tags while broadcasting. This function is\n\t * optional.\n\t *\n\t * \\param data Decoder's private data.\n\t *\n\t * \\return 1 if the tags were changed from the last call of this\n\t * function or 0 if not.\n\t */\n\tint (*current_tags)(void *data, struct file_tags *tags);\n\n\t/** Get the IO stream used by the decoder.\n\t *\n\t * Get the pointer to the io_stream object used by the decoder. This\n\t * is used for fast interrupting especially when the stream reads\n\t * from a network. This function is optional.\n\t *\n\t * \\param data Decoder's private data.\n\t *\n\t * \\return Pointer to the used IO stream.\n\t */\n\tstruct io_stream *(*get_stream)(void *data);\n\n\t/** Get the average bitrate.\n\t *\n\t * Get the bitrate of the whole file.\n\t *\n\t * \\param data Decoder's private data.\n\t *\n\t * \\return Average bitrate in kbps or -1 if not available.\n\t */\n\tint (*get_avg_bitrate)(void *data);\n};\n\n/** Initialize decoder plugin.\n *\n * Each decoder plugin must export a function name plugin_init of this\n * type. The function must return a pointer to the struct decoder variable\n * filled with pointers to decoder's functions.\n */\ntypedef struct decoder *plugin_init_func ();\n\nint is_sound_file (const char *name);\nstruct decoder *get_decoder (const char *file);\nstruct decoder *get_decoder_by_content (struct io_stream *stream);\nconst char *get_decoder_name (const struct decoder *decoder);\nvoid decoder_init (int debug_info);\nvoid decoder_cleanup ();\nchar *file_type_name (const char *file);\n\n/** @defgroup decoder_error_funcs Decoder error functions\n *\n * These functions can be used to modify variables of the decoder_error\n * structure.\n */\n/*@{*/\n\n/** Fill decoder_error structure with an error.\n *\n * Fills decoder error variable with an error. It can be used like printf().\n *\n * \\param error Pointer to the decoder_error object to fill.\n * \\param type Type of the error.\n * \\param add_errno If this value is non-zero, a space and a string\n * describing system error for errno equal to the value of add_errno\n * is appended to the error message.\n * \\param format Format, like in the printf() function.\n */\nvoid decoder_error (struct decoder_error *error,\n\t\tconst enum decoder_error_type type, const int add_errno,\n\t\tconst char *format, ...) ATTR_PRINTF(4, 5);\n\n/** Clear decoder_error structure.\n *\n * Clear decoder_error structure. Set the system type to ERROR_OK and\n * the error message to NULL. Frees all memory used by the error's fields.\n *\n * \\param error Pointer to the decoder_error object to be cleared.\n */\nvoid decoder_error_clear (struct decoder_error *error);\n\n/** Copy decoder_error variable.\n *\n * Copies the decoder_error variable to another decoder_error variable.\n *\n * \\param dst Destination.\n * \\param src Source.\n */\nvoid decoder_error_copy (struct decoder_error *dst,\n\t\tconst struct decoder_error *src);\n\n/** Return the error text from the decoder_error variable.\n *\n * Returns the error text from the decoder_error variable.  NULL may be\n * returned if decoder_error() has not been called.\n *\n * \\param error Pointer to the source decoder_error object.\n *\n * \\return The address of the error text or NULL.\n */\nconst char *decoder_error_text (const struct decoder_error *error);\n\n/** Initialize decoder_error variable.\n *\n * Initialize decoder_error variable and set the error to ERROR_OK with no\n * message.\n *\n * \\param error Pointer to the decoder_error object to be initialised.\n */\nvoid decoder_error_init (struct decoder_error *error);\n\n/*@}*/\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "decoder_plugins/Makefile.am",
    "content": "SUBDIRS =\n\nif BUILD_mp3\n    SUBDIRS += mp3\nendif\n\nif BUILD_aac\n    SUBDIRS += aac\nendif\n\nif BUILD_musepack\n    SUBDIRS += musepack\nendif\n\nif BUILD_vorbis\n    SUBDIRS += vorbis\nendif\n\nif BUILD_flac\n    SUBDIRS += flac\nendif\n\nif BUILD_wavpack\n    SUBDIRS += wavpack\nendif\n\nif BUILD_sndfile\n    SUBDIRS += sndfile\nendif\n\nif BUILD_modplug\n    SUBDIRS += modplug\nendif\n\nif BUILD_timidity\n    SUBDIRS += timidity\nendif\n\nif BUILD_sidplay2\n    SUBDIRS += sidplay2\nendif\n\nif BUILD_ffmpeg\n    SUBDIRS += ffmpeg\nendif\n\nif BUILD_speex\n    SUBDIRS += speex\nendif\n"
  },
  {
    "path": "decoder_plugins/aac/Makefile.am",
    "content": "lib_LTLIBRARIES = libaac_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibaac_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibaac_decoder_la_LIBADD = -lid3tag -lz $(FAAD2_LIBS)\nlibaac_decoder_la_CFLAGS = $(FAAD2_CFLAGS) -I$(top_srcdir)\nlibaac_decoder_la_SOURCES = aac.c\n"
  },
  {
    "path": "decoder_plugins/aac/aac.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005, 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This code is based on CMUS aac plugin Copyright 2006 dnk <dnk@bjum.net>\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <stdint.h>\n#include <string.h>\n#include <strings.h>\n#include <assert.h>\n\n#include <neaacdec.h>\n#include <id3tag.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"decoder.h\"\n#include \"io.h\"\n#include \"log.h\"\n#include \"files.h\"\n\n/* FAAD_MIN_STREAMSIZE == 768, 6 == # of channels */\n#define BUFFER_SIZE\t(FAAD_MIN_STREAMSIZE * 6 * 4)\n\nstruct aac_data\n{\n\tstruct io_stream *stream;\n\tchar rbuf[BUFFER_SIZE];\n\tint rbuf_len;\n\tint rbuf_pos;\n\n\tint channels;\n\tint sample_rate;\n\n\tchar *overflow_buf;\n\tint overflow_buf_len;\n\n\tNeAACDecHandle decoder;\t/* typedef void * */\n\n\tint ok; /* was this stream successfully opened? */\n\tstruct decoder_error error;\n\n\tint bitrate;\n\tint avg_bitrate;\n\tint duration;\n};\n\nstatic int buffer_length (const struct aac_data *data)\n{\n\treturn data->rbuf_len - data->rbuf_pos;\n}\n\nstatic void *buffer_data (struct aac_data *data)\n{\n\treturn data->rbuf + data->rbuf_pos;\n}\n\nstatic int buffer_fill (struct aac_data *data)\n{\n\tssize_t n;\n\n\tif (data->rbuf_pos > 0) {\n\t\tdata->rbuf_len = buffer_length (data);\n\t\tmemmove (data->rbuf, data->rbuf + data->rbuf_pos, data->rbuf_len);\n\t\tdata->rbuf_pos = 0;\n\t}\n\n\tif (data->rbuf_len == BUFFER_SIZE)\n\t\treturn 1;\n\n\tn = io_read (data->stream, data->rbuf + data->rbuf_len, BUFFER_SIZE - data->rbuf_len);\n\tif (n == -1)\n\t\treturn -1;\n\tif (n == 0)\n\t\treturn 0;\n\n\tdata->rbuf_len += n;\n\treturn 1;\n}\n\nstatic inline void buffer_flush (struct aac_data *data)\n{\n\tdata->rbuf_len = 0;\n\tdata->rbuf_pos = 0;\n}\n\nstatic inline void buffer_consume (struct aac_data *data, int n)\n{\n\tassert (n <= buffer_length(data));\n\n\tdata->rbuf_pos += n;\n}\n\nstatic int buffer_fill_min (struct aac_data *data, int len)\n{\n\tint rc;\n\n\tassert (len < BUFFER_SIZE);\n\n\twhile (buffer_length(data) < len) {\n\t\trc = buffer_fill (data);\n\t\tif (rc <= 0)\n\t\t\treturn rc;\n\t}\n\n\treturn 1;\n}\n\n/* 'data' must point to at least 6 bytes of data */\nstatic int parse_frame (const unsigned char data[6])\n{\n\tint len;\n\n\t/* http://wiki.multimedia.cx/index.php?title=ADTS */\n\n\t/* first 12 bits must be set */\n\tif (data[0] != 0xFF)\n\t\treturn 0;\n\tif ((data[1] & 0xF0) != 0xF0)\n\t\treturn 0;\n\n\t/* layer is always '00' */\n\tif ((data[1] & 0x06) != 0x00)\n\t\treturn 0;\n\n\t/* frame length is stored in 13 bits */\n\tlen  = data[3] << 11;\t/* ..1100000000000 */\n\tlen |= data[4] << 3;\t/* ..xx11111111xxx */\n\tlen |= data[5] >> 5;\t/* ..xxxxxxxxxx111 */\n\tlen &= 0x1FFF;\t\t/* 13 bits */\n\treturn len;\n}\n\n/* scans forward to the next aac frame and makes sure\n * the entire frame is in the buffer.\n */\nstatic int buffer_fill_frame(struct aac_data *data)\n{\n\tunsigned char *datap;\n\tint rc, n, len;\n\tint max = 32768;\n\n\twhile (1) {\n\t\t/* need at least 6 bytes of data */\n\t\trc = buffer_fill_min(data, 6);\n\t\tif (rc <= 0)\n\t\t\tbreak;\n\n\t\tlen = buffer_length(data);\n\t\tdatap = buffer_data(data);\n\n\t\t/* scan for a frame */\n\t\tfor (n = 0; n < len - 5; n++) {\n\t\t\t/* give up after 32KB */\n\t\t\tif (max-- == 0) {\n\t\t\t\tlogit (\"no frame found!\");\n\t\t\t\t/* FIXME: set errno? */\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t\t/* see if there's a frame at this location */\n\t\t\trc = parse_frame(datap + n);\n\t\t\tif (rc == 0)\n\t\t\t\tcontinue;\n\n\t\t\t/* found a frame, consume all data up to the frame */\n\t\t\tbuffer_consume (data, n);\n\n\t\t\t/* rc == frame length */\n\t\t\trc = buffer_fill_min (data, rc);\n\t\t\tif (rc <= 0)\n\t\t\t\tgoto end;\n\n\t\t\treturn 1;\n\t\t}\n\n\t\t/* consume what we used */\n\t\tbuffer_consume (data, n);\n\t}\n\nend:\n\treturn rc;\n}\n\n/* This should be called with a unique decoder instance as the seeking\n * it does triggers an FAAD bug which results in distorted audio due to\n * retained state being corrupted.  (One suspects NeAACDecPostSeekReset()\n * should resolve the problem but experimentation suggests not and no\n * documentation exists describing its use.) */\nstatic int aac_count_time (struct aac_data *data)\n{\n\tNeAACDecFrameInfo frame_info;\n\tint samples = 0, bytes = 0, frames = 0;\n\toff_t file_size;\n\tint16_t *sample_buf;\n\n\tfile_size = io_file_size (data->stream);\n\tif (file_size == -1)\n\t\treturn -1;\n\n\tif (io_seek(data->stream, file_size / 2, SEEK_SET) == -1)\n\t\treturn -1;\n\tbuffer_flush (data);\n\n\t/* Guess track length by decoding the middle 50 frames which have\n\t * more than 25% of non-zero samples having absolute values greater\n\t * than 16. */\n\twhile (frames < 50) {\n\t\tif (buffer_fill_frame (data) <= 0)\n\t\t\tbreak;\n\n\t\tsample_buf = NeAACDecDecode (data->decoder, &frame_info,\n\t\t                             buffer_data (data), buffer_length (data));\n\n\t\tif (frame_info.error == 0 && frame_info.samples > 0) {\n\t\t\tunsigned int ix, zeroes = 0;\n\n\t\t\tfor (ix = 0; ix < frame_info.samples; ix += 1) {\n\t\t\t\tif (sample_buf[ix] != 0 && RANGE(-16, sample_buf[ix], 16))\n\t\t\t\t\tzeroes += 1;\n\t\t\t}\n\n\t\t\tif (zeroes * 4 < frame_info.samples) {\n\t\t\t\tsamples += frame_info.samples;\n\t\t\t\tbytes += frame_info.bytesconsumed;\n\t\t\t\tframes += 1;\n\t\t\t}\n\t\t}\n\n\t\tif (frame_info.bytesconsumed == 0)\n\t\t\tbreak;\n\n\t\tbuffer_consume (data, frame_info.bytesconsumed);\n\t}\n\n\tif (frames == 0)\n\t\treturn -1;\n\n\tsamples /= frames;\n\tsamples /= data->channels;\n\tbytes /= frames;\n\n\treturn ((file_size / bytes) * samples) / data->sample_rate;\n}\n\nstatic struct aac_data *aac_open_internal (struct io_stream *stream,\n                                           const char *fname, bool timing_only)\n{\n\tstruct aac_data *data;\n\tNeAACDecConfigurationPtr neaac_cfg;\n\tunsigned char channels;\n\tunsigned long sample_rate;\n\tint n;\n\n\t/* init private struct */\n\tdata = xcalloc (1, sizeof *data);\n\tdata->decoder = NeAACDecOpen();\n\n\t/* set decoder config */\n\tneaac_cfg = NeAACDecGetCurrentConfiguration(data->decoder);\n\tneaac_cfg->outputFormat = FAAD_FMT_16BIT;\t/* force 16 bit audio */\n\tneaac_cfg->downMatrix = !timing_only;\t\t/* 5.1 -> stereo */\n\tneaac_cfg->dontUpSampleImplicitSBR = 0;\t\t/* upsample, please! */\n\tNeAACDecSetConfiguration(data->decoder, neaac_cfg);\n\n\tif (stream)\n\t\tdata->stream = stream;\n\telse {\n\t\tdata->stream = io_open (fname, 1);\n\t\tif (!io_ok(data->stream)) {\n\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\t\"Can't open AAC file: %s\", io_strerror(data->stream));\n\t\t\treturn data;\n\t\t}\n\t}\n\n\t/* find a frame */\n\tif (buffer_fill_frame(data) <= 0) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Not a valid (or unsupported) AAC file\");\n\t\treturn data;\n\t}\n\n\t/* in case of a bug, make sure there is at least some data\n\t * in the buffer for NeAACDecInit() to work with.\n\t */\n\tif (buffer_fill_min(data, 256) <= 0) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"AAC file/stream too short\");\n\t\treturn data;\n\t}\n\n\t/* init decoder, returns the length of the header (if any) */\n\tchannels = (unsigned char)data->channels;\n\tsample_rate = data->sample_rate;\n\tn = NeAACDecInit (data->decoder, buffer_data(data), buffer_length(data),\n\t\t&sample_rate, &channels);\n\tdata->channels = channels;\n\tdata->sample_rate = (int)sample_rate;\n\tif (n < 0) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"libfaad can't open this stream\");\n\t\treturn data;\n\t}\n\n\tif (!timing_only) {\n\t\tif (data->channels == 6) {\n\t\t\tlogit (\"sample rate %dHz, channels %d (downmixed to stereo)\",\n\t\t\t        data->sample_rate, data->channels);\n\t\t\tdata->channels = 2;\n\t\t}\n\t\telse\n\t\t\tlogit (\"sample rate %dHz, channels %d\",\n\t\t\t        data->sample_rate, data->channels);\n\t}\n\n\tif (!data->sample_rate || !data->channels) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Invalid AAC sound parameters\");\n\t\treturn data;\n\t}\n\n\t/* skip the header */\n\tlogit (\"skipping header (%d bytes)\", n);\n\tbuffer_consume (data, n);\n\n\t/*NeAACDecInitDRM(data->decoder, data->sample_rate, data->channels);*/\n\n\tdata->ok = 1;\n\treturn data;\n}\n\nstatic void aac_close (void *prv_data)\n{\n\tstruct aac_data *data = (struct aac_data *)prv_data;\n\n\tNeAACDecClose (data->decoder);\n\tio_close (data->stream);\n\tdecoder_error_clear (&data->error);\n\tfree (data);\n}\n\nstatic void *aac_open (const char *file)\n{\n\tstruct aac_data *data;\n\n\tdata = aac_open_internal (NULL, file, true);\n\n\tif (data->ok) {\n\t\tint duration = -1;\n\t\tint avg_bitrate = -1;\n\t\toff_t file_size;\n\n\t\tduration = aac_count_time (data);\n\t\tfile_size = io_file_size (data->stream);\n\t\tif (duration > 0 && file_size != -1)\n\t\t\tavg_bitrate = file_size / duration * 8;\n\t\taac_close (data);\n\n\t\tdata = aac_open_internal (NULL, file, false);\n\t\tdata->duration = duration;\n\t\tdata->avg_bitrate = avg_bitrate;\n\t}\n\n\treturn data;\n}\n\nstatic void *aac_open_stream (struct io_stream *stream)\n{\n\tassert (stream != NULL);\n\n\treturn aac_open_internal (stream, NULL, false);\n}\n\nstatic char *get_tag (struct id3_tag *tag, const char *what)\n{\n\tstruct id3_frame *frame;\n\tunion id3_field *field;\n\tconst id3_ucs4_t *ucs4;\n\tchar *comm = NULL;\n\n\tframe = id3_tag_findframe (tag, what, 0);\n\tif (frame && (field = &frame->fields[1])) {\n\t\tucs4 = id3_field_getstrings (field, 0);\n\t\tif (ucs4)\n\t\t\tcomm = (char *)id3_ucs4_utf8duplicate (ucs4);\n\t}\n\n\treturn comm;\n}\n\n/* Fill info structure with data from aac comments */\nstatic void aac_info (const char *file_name,\n\t\tstruct file_tags *info,\n\t\tconst int tags_sel)\n{\n\tif (tags_sel & TAGS_COMMENTS) {\n\t\tstruct id3_tag *tag;\n\t\tstruct id3_file *id3file;\n\t\tchar *track = NULL;\n\n\t\tid3file = id3_file_open (file_name, ID3_FILE_MODE_READONLY);\n\t\tif (!id3file)\n\t\t\treturn;\n\t\ttag = id3_file_tag (id3file);\n\t\tif (tag) {\n\t\t\tinfo->artist = get_tag (tag, ID3_FRAME_ARTIST);\n\t\t\tinfo->title = get_tag (tag, ID3_FRAME_TITLE);\n\t\t\tinfo->album = get_tag (tag, ID3_FRAME_ALBUM);\n\t\t\ttrack = get_tag (tag, ID3_FRAME_TRACK);\n\n\t\t\tif (track) {\n\t\t\t\tchar *end;\n\n\t\t\t\tinfo->track = strtol (track, &end, 10);\n\t\t\t\tif (end == track)\n\t\t\t\t\tinfo->track = -1;\n\t\t\t\tfree (track);\n\t\t\t}\n\t\t}\n\t\tid3_file_close (id3file);\n\t}\n\n\tif (tags_sel & TAGS_TIME) {\n\t\tstruct aac_data *data;\n\n\t\tdata = aac_open_internal (NULL, file_name, true);\n\n\t\tif (data->ok)\n\t\t\tinfo->time = aac_count_time (data);\n\t\telse\n\t\t\tlogit (\"%s\", decoder_error_text (&data->error));\n\n\t\taac_close (data);\n\t}\n}\n\nstatic int aac_seek (void *unused ATTR_UNUSED, int sec ASSERT_ONLY)\n{\n\tassert (sec >= 0);\n\n\t/* AAC will probably never be able to seek.  There is no way of\n\t * relating the time in the audio to the position in the file\n\t * short of pre-processing the file at open and building a seek\n\t * table.  Even then, seeking in the file causes audio glitches\n\t * (see aac_count_time()). */\n\n\treturn -1;\n}\n\n/* returns -1 on fatal errors\n * returns -2 on non-fatal errors\n * 0 on eof\n * number of bytes put in 'buffer' on success */\nstatic int decode_one_frame (struct aac_data *data, void *buffer, int count)\n{\n\tunsigned char *aac_data;\n\tunsigned int aac_data_size;\n\tNeAACDecFrameInfo frame_info;\n\tchar *sample_buf;\n\tint bytes, rc;\n\n\trc = buffer_fill_frame (data);\n\tif (rc <= 0)\n\t\treturn rc;\n\n\taac_data = buffer_data (data);\n\taac_data_size = buffer_length (data);\n\n\t/* aac data -> raw pcm */\n\tsample_buf = NeAACDecDecode (data->decoder, &frame_info,\n\t                             aac_data, aac_data_size);\n\n\tbuffer_consume (data, frame_info.bytesconsumed);\n\n\tif (!sample_buf || frame_info.bytesconsumed <= 0) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"%s\",\n\t\t               NeAACDecGetErrorMessage (frame_info.error));\n\t\treturn -1;\n\t}\n\n\tif (frame_info.error != 0) {\n\t\tdecoder_error (&data->error, ERROR_STREAM, 0, \"%s\",\n\t\t               NeAACDecGetErrorMessage (frame_info.error));\n\t\treturn -2;\n\t}\n\n\tif (frame_info.samples <= 0)\n\t\treturn -2;\n\n\tif (frame_info.channels != (unsigned char)data->channels ||\n\t    frame_info.samplerate != (unsigned long)data->sample_rate) {\n\t\tdecoder_error (&data->error, ERROR_STREAM, 0, \"%s\",\n\t\t               \"Invalid channel or sample_rate count\");\n\t\treturn -2;\n\t}\n\n\t/* 16-bit samples */\n\tbytes = frame_info.samples * 2;\n\n\tif (bytes > count) {\n\t\t/* decoded too much, keep overflow */\n\t\tdata->overflow_buf = sample_buf + count;\n\t\tdata->overflow_buf_len = bytes - count;\n\t\tmemcpy (buffer, sample_buf, count);\n\t\treturn count;\n\t}\n\n\tmemcpy (buffer, sample_buf, bytes);\n\n\tdata->bitrate = frame_info.bytesconsumed * 8 / (bytes / 2.0 /\n\t\t\tdata->channels / data->sample_rate) / 1000;\n\n\treturn bytes;\n}\n\nstatic int aac_decode (void *prv_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n\tstruct aac_data *data = (struct aac_data *)prv_data;\n\tint rc;\n\n\tdecoder_error_clear (&data->error);\n\n\tsound_params->channels = data->channels;\n\tsound_params->rate = data->sample_rate;\n\tsound_params->fmt = SFMT_S16 | SFMT_NE;\n\n\t/* use overflow from previous call (if any) */\n\tif (data->overflow_buf_len) {\n\t\tint len;\n\n\t\tlen = MIN(data->overflow_buf_len, buf_len);\n\n\t\tmemcpy (buf, data->overflow_buf, len);\n\t\tdata->overflow_buf += len;\n\t\tdata->overflow_buf_len -= len;\n\t\treturn len;\n\t}\n\n\tdo {\n\t\trc = decode_one_frame (data, buf, buf_len);\n\t} while (rc == -2);\n\n\treturn MAX(rc, 0);\n}\n\nstatic int aac_get_bitrate (void *prv_data)\n{\n\tstruct aac_data *data = (struct aac_data *)prv_data;\n\n\treturn data->bitrate;\n}\n\nstatic int aac_get_avg_bitrate (void *prv_data)\n{\n\tstruct aac_data *data = (struct aac_data *)prv_data;\n\n\treturn data->avg_bitrate / 1000;\n}\n\nstatic int aac_get_duration (void *prv_data)\n{\n\tstruct aac_data *data = (struct aac_data *)prv_data;\n\n\treturn data->duration;\n}\n\nstatic void aac_get_name (const char *unused ATTR_UNUSED, char buf[4])\n{\n\tstrcpy (buf, \"AAC\");\n}\n\nstatic int aac_our_format_ext (const char *ext)\n{\n\treturn !strcasecmp (ext, \"aac\");\n}\n\nstatic void aac_get_error (void *prv_data, struct decoder_error *error)\n{\n\tstruct aac_data *data = (struct aac_data *)prv_data;\n\n\tdecoder_error_copy (error, &data->error);\n}\n\nstatic int aac_our_mime (const char *mime)\n{\n\treturn !strcasecmp (mime, \"audio/aac\")\n\t\t|| !strncasecmp (mime, \"audio/aac;\", 10)\n\t\t|| !strcasecmp (mime, \"audio/aacp\")\n\t\t|| !strncasecmp (mime, \"audio/aacp;\", 11);\n}\n\nstatic struct decoder aac_decoder = {\n\tDECODER_API_VERSION,\n\tNULL,\n\tNULL,\n\taac_open,\n\taac_open_stream,\n\tNULL,\n\taac_close,\n\taac_decode,\n\taac_seek,\n\taac_info,\n\taac_get_bitrate,\n\taac_get_duration,\n\taac_get_error,\n\taac_our_format_ext,\n\taac_our_mime,\n\taac_get_name,\n\tNULL,\n\tNULL,\n\taac_get_avg_bitrate\n};\n\nstruct decoder *plugin_init ()\n{\n\treturn &aac_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/aac/aac.m4",
    "content": "dnl libfaad2 (aac)\n\nAC_ARG_WITH(aac, AS_HELP_STRING([--without-aac],\n                                [Compile without AAC support (libfaad2)]))\n\nif test \"x$with_aac\" != \"xno\"\nthen\n\tfaad2_OK=\"no\"\n\tAC_CHECK_LIB(faad, NeAACDecInit, [faad2_OK=\"yes\"])\n\n\tif test \"x$faad2_OK\" = \"xyes\"; then\n\t\tAC_CHECK_HEADER([neaacdec.h], ,\n\t\t\tAC_MSG_ERROR([You need a more recent libfaad2 (libfaad2 devel package).]))\n\tfi\n\n\tif test \"x$faad2_OK\" = \"xyes\" -a \"$HAVE_ID3TAG\" = \"yes\"\n\tthen\n\t\tFAAD2_LIBS=\"-lfaad\"\n\t\tAC_SUBST([FAAD2_CFLAGS])\n\t\tAC_SUBST([FAAD2_LIBS])\n\t\twant_aac=\"yes\"\n\t\tDECODER_PLUGINS=\"$DECODER_PLUGINS aac\"\n\tfi\nfi\n\nAM_CONDITIONAL([BUILD_aac], [test \"$want_aac\"])\nAC_CONFIG_FILES([decoder_plugins/aac/Makefile])\n"
  },
  {
    "path": "decoder_plugins/decoders.m4",
    "content": "DECODER_PLUGIN_DIR=decoder_plugins\nAC_SUBST([DECODER_PLUGIN_DIR])\n\ndnl libid3tag (with zlib)\nAC_CHECK_LIB(z, gzopen, [HAVE_ZLIB=yes],)\nAC_CHECK_LIB(id3tag, id3_file_open, [HAVE_LIBID3TAG=yes],)\nAC_CHECK_HEADER([id3tag.h],[HAVE_LIBID3TAG_H=yes],)\n\nif test \"x$HAVE_ZLIB\" = \"xyes\" -a \"x$HAVE_LIBID3TAG\" = \"xyes\" \\\n\t-a \"x$HAVE_LIBID3TAG_H\" = \"xyes\"\nthen\n\tHAVE_ID3TAG=yes\nelse\n\tHAVE_ID3TAG=no\nfi\n\nm4_include(decoder_plugins/aac/aac.m4)\nm4_include(decoder_plugins/ffmpeg/ffmpeg.m4)\nm4_include(decoder_plugins/flac/flac.m4)\nm4_include(decoder_plugins/modplug/modplug.m4)\nm4_include(decoder_plugins/mp3/mp3.m4)\nm4_include(decoder_plugins/musepack/musepack.m4)\nm4_include(decoder_plugins/sidplay2/sidplay2.m4)\nm4_include(decoder_plugins/sndfile/sndfile.m4)\nm4_include(decoder_plugins/speex/speex.m4)\nm4_include(decoder_plugins/timidity/timidity.m4)\nm4_include(decoder_plugins/vorbis/vorbis.m4)\nm4_include(decoder_plugins/wavpack/wavpack.m4)\n\nAC_CONFIG_FILES([decoder_plugins/Makefile])\n"
  },
  {
    "path": "decoder_plugins/ffmpeg/Makefile.am",
    "content": "lib_LTLIBRARIES = libffmpeg_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibffmpeg_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibffmpeg_decoder_la_LIBADD = $(ffmpeg_LIBS)\nlibffmpeg_decoder_la_CPPFLAGS = $(ffmpeg_CPPFLAGS) -I$(top_srcdir)\nlibffmpeg_decoder_la_CFLAGS = $(ffmpeg_CFLAGS) -I$(top_srcdir)\nlibffmpeg_decoder_la_SOURCES = ffmpeg.c\n"
  },
  {
    "path": "decoder_plugins/ffmpeg/ffmpeg.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005, 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * Based on FFplay Copyright (c) 2003 Fabrice Bellard\n *\n */\n\n/*\n *\t\t\"The main problem is that external projects who want to\n *\t\t support both FFmpeg and LibAV are just fucked, and this\n *\t\t only because LibAV doesn't care a second about their users.\"\n *\n *\t\t-- http://blog.pkh.me/p/13-the-ffmpeg-libav-situation.html\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <pthread.h>\n#include <string.h>\n#include <strings.h>\n#include <assert.h>\n#include <stdint.h>\n#include <errno.h>\n\n#include <libavformat/avformat.h>\n#include <libavutil/mathematics.h>\n#if HAVE_LIBAVUTIL_CHANNEL_LAYOUT_H\n# include <libavutil/channel_layout.h>\n#else\n# include <libavutil/audioconvert.h>\n#endif\n\n/* FFmpeg also likes common names, without that, our common.h and log.h\n * would not be included. */\n#undef COMMON_H\n#undef LOG_H\n\n#define DEBUG\n\n#define STRERROR_FN ffmpeg_strerror\n\n#include \"common.h\"\n#include \"audio.h\"\n#include \"decoder.h\"\n#include \"log.h\"\n#include \"files.h\"\n#include \"lists.h\"\n\n#ifndef AV_CODEC_CAP_DELAY\n# define AV_CODEC_CAP_DELAY CODEC_CAP_DELAY\n# define AV_CODEC_CAP_EXPERIMENTAL CODEC_CAP_EXPERIMENTAL\n# define AV_CODEC_CAP_TRUNCATED CODEC_CAP_TRUNCATED\n#endif\n\n#ifndef AV_CODEC_FLAG_TRUNCATED\n# define AV_CODEC_FLAG_TRUNCATED CODEC_FLAG_TRUNCATED\n#endif\n\n/* Set SEEK_IN_DECODER to 1 if you'd prefer seeking to be delay until\n * the next time ffmpeg_decode() is called.  This will provide seeking\n * in formats for which FFmpeg falsely reports seek errors, but could\n * result erroneous current time values. */\n#define SEEK_IN_DECODER 0\n\nstruct ffmpeg_data\n{\n\tAVFormatContext *ic;\n\tAVIOContext *pb;\n\tAVStream *stream;\n\tAVCodecContext *enc;\n\tAVCodec *codec;\n\n\tchar *remain_buf;\n\tint remain_buf_len;\n\n\tbool delay;             /* FFmpeg may buffer samples */\n\tbool eof;               /* end of file seen */\n\tbool eos;               /* end of sound seen */\n\tbool okay;              /* was this stream successfully opened? */\n\n\tchar *filename;\n\tstruct io_stream *iostream;\n\tstruct decoder_error error;\n\tlong fmt;\n\tint sample_width;\n\tint bitrate;            /* in bits per second */\n\tint avg_bitrate;        /* in bits per second */\n#if SEEK_IN_DECODER\n\tbool seek_req;          /* seek requested */\n\tint seek_sec;           /* second to which to seek */\n#endif\n\tbool seek_broken;       /* FFmpeg seeking is broken */\n\tbool timing_broken;     /* FFmpeg trashes duration and bit_rate */\n#if SEEK_IN_DECODER && defined(DEBUG)\n\tpthread_t thread_id;\n#endif\n};\n\nstruct extn_list {\n\tconst char *extn;\n\tconst char *format;\n};\n\nstatic lists_t_strs *supported_extns = NULL;\n\nstatic void ffmpeg_log_repeats (char *msg LOGIT_ONLY)\n{\n#ifndef NDEBUG\n\tstatic int msg_count = 0;\n\tstatic char *prev_msg = NULL;\n\tstatic pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;\n\n\t/* We need to gate the decoder and precaching threads. */\n\tLOCK (mutex);\n\n\tif (prev_msg && (!msg || strcmp (msg, prev_msg))) {\n\t\tif (msg_count > 1)\n\t\t\tlogit (\"FFmpeg said: Last message repeated %d times\", msg_count);\n\t\tfree (prev_msg);\n\t\tprev_msg = NULL;\n\t\tmsg_count = 0;\n\t}\n\tif (prev_msg && msg) {\n\t\tfree (msg);\n\t\tmsg = NULL;\n\t\tmsg_count += 1;\n\t}\n\tif (!prev_msg && msg) {\n\t\tint count, ix;\n\t\tlists_t_strs *lines;\n\n\t\tlines = lists_strs_new (4);\n\t\tcount = lists_strs_split (lines, msg, \"\\n\");\n\t\tfor (ix = 0; ix < count; ix += 1)\n\t\t\tlogit (\"FFmpeg said: %s\", lists_strs_at (lines, ix));\n\t\tlists_strs_free (lines);\n\n\t\tprev_msg = msg;\n\t\tmsg_count = 1;\n\t}\n\tUNLOCK (mutex);\n#endif\n}\n\n#ifndef NDEBUG\nstatic void ffmpeg_log_cb (void *unused ATTR_UNUSED, int level,\n                           const char *fmt, va_list vl)\n{\n\tint len;\n\tchar *msg;\n\n\tassert (fmt);\n\n\tif (level > av_log_get_level ())\n\t\treturn;\n\n\tmsg = format_msg_va (fmt, vl);\n\n#if defined(HAVE_FFMPEG) && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(56,33,101)\n\t/* Drop this message because it is issued repeatedly and is pointless. */\n\tconst char skipping[] = \"Skipping 0 bytes of junk\";\n\n\tif (!strncmp (skipping, msg, sizeof (skipping) - 1)) {\n\t\tfree (msg);\n\t\treturn;\n\t}\n#endif\n\n\tlen = strlen (msg);\n\tfor (len = strlen (msg); len > 0 && msg[len - 1] == '\\n'; len -= 1)\n\t\tmsg[len - 1] = 0x00;\n\n\tffmpeg_log_repeats (msg);\n}\n#endif\n\n/* FFmpeg-provided error code to description function wrapper. */\nstatic inline char *ffmpeg_strerror (int errnum)\n{\n\tchar *result;\n\n\tffmpeg_log_repeats (NULL);\n\tresult = xmalloc (256);\n\tav_strerror (errnum, result, 256);\n\tresult[255] = 0;\n\n\treturn result;\n}\n\n/* Find the first audio stream and return its index, or nb_streams if\n * none found. */\nstatic unsigned int find_first_audio (AVFormatContext *ic)\n{\n\tunsigned int result;\n\n\tassert (ic);\n\n\tfor (result = 0; result < ic->nb_streams; result += 1) {\n\t\tenum AVMediaType codec_type;\n\n#ifdef HAVE_STRUCT_AVSTREAM_CODECPAR\n\t\tcodec_type = ic->streams[result]->codecpar->codec_type;\n#else\n\t\tcodec_type = ic->streams[result]->codec->codec_type;\n#endif\n\n\t\tif (codec_type == AVMEDIA_TYPE_AUDIO)\n\t\t\tbreak;\n\t}\n\n\treturn result;\n}\n\nstatic void load_audio_extns (lists_t_strs *list)\n{\n\tint ix;\n\n\t/* When adding an entry to this list, tests need to be performed to\n\t * determine whether or not FFmpeg/LibAV handles durations and seeking\n\t * correctly.  If not, then the appropriate additions should be made\n\t * in is_timing_broken() and is_seek_broken(). */\n\tconst struct extn_list audio_extns[] = {\n\t\t{\"aac\", \"aac\"},\n\t\t{\"ac3\", \"ac3\"},\n\t\t{\"ape\", \"ape\"},\n\t\t{\"au\", \"au\"},\n\t\t{\"ay\", \"libgme\"},\n\t\t{\"dff\", \"dsf\"},\n\t\t{\"dsf\", \"dsf\"},\n\t\t{\"dts\", \"dts\"},\n\t\t{\"eac3\", \"eac3\"},\n\t\t{\"fla\", \"flac\"},\n\t\t{\"flac\", \"flac\"},\n\t\t{\"gbs\", \"libgme\"},\n\t\t{\"gym\", \"libgme\"},\n\t\t{\"hes\", \"libgme\"},\n\t\t{\"kss\", \"libgme\"},\n\t\t{\"mka\", \"matroska\"},\n\t\t{\"mp2\", \"mpeg\"},\n\t\t{\"mp3\", \"mp3\"},\n\t\t{\"mpc\", \"mpc\"},\n\t\t{\"mpc8\", \"mpc8\"},\n\t\t{\"m4a\", \"m4a\"},\n\t\t{\"nsf\", \"libgme\"},\n\t\t{\"nsfe\", \"libgme\"},\n\t\t{\"ra\", \"rm\"},\n\t\t{\"sap\", \"libgme\"},\n\t\t{\"spc\", \"libgme\"},\n\t\t{\"tta\", \"tta\"},\n\t\t{\"vgm\", \"libgme\"},\n\t\t{\"vgz\", \"libgme\"},\n\t\t{\"vqf\", \"vqf\"},\n\t\t{\"wav\", \"wav\"},\n\t\t{\"w64\", \"w64\"},\n\t\t{\"wma\", \"asf\"},\n\t\t{\"wv\", \"wv\"},\n\t\t{NULL, NULL}\n\t};\n\n\tfor (ix = 0; audio_extns[ix].extn; ix += 1) {\n\t\tif (av_find_input_format (audio_extns[ix].format))\n\t\t\tlists_strs_append (list, audio_extns[ix].extn);\n\t}\n\n\tif (av_find_input_format (\"ogg\")) {\n\t\tlists_strs_append (list, \"ogg\");\n\t\tif (avcodec_find_decoder (AV_CODEC_ID_VORBIS))\n\t\t\tlists_strs_append (list, \"oga\");\n\t\tif (avcodec_find_decoder (AV_CODEC_ID_OPUS))\n\t\t\tlists_strs_append (list, \"opus\");\n\t\tif (avcodec_find_decoder (AV_CODEC_ID_THEORA))\n\t\t\tlists_strs_append (list, \"ogv\");\n\t}\n\n\t/* In theory, FFmpeg supports Speex if built with libspeex enabled.\n\t * In practice, it breaks badly. */\n#if 0\n\tif (avcodec_find_decoder (AV_CODEC_ID_SPEEX))\n\t\tlists_strs_append (list, \"spx\");\n#endif\n}\n\nstatic void load_video_extns (lists_t_strs *list)\n{\n\tint ix;\n\tconst struct extn_list video_extns[] = {\n\t\t{\"avi\", \"avi\"},\n\t\t{\"flv\", \"flv\"},\n\t\t{\"mkv\", \"matroska\"},\n\t\t{\"mp4\", \"mp4\"},\n\t\t{\"rec\", \"mpegts\"},\n\t\t{\"vob\", \"mpeg\"},\n\t\t{\"webm\", \"matroska\"},\n\t\t{NULL, NULL}\n\t};\n\n\tfor (ix = 0; video_extns[ix].extn; ix += 1) {\n\t\tif (av_find_input_format (video_extns[ix].format))\n\t\t\tlists_strs_append (list, video_extns[ix].extn);\n\t}\n}\n\n/* Handle FFmpeg's locking requirements. */\n#if HAVE_LIBAV || LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,9,100)\nstatic int locking_cb (void **mutex, enum AVLockOp op)\n{\n\tint result;\n\n\tswitch (op) {\n\tcase AV_LOCK_CREATE:\n\t\t*mutex = xmalloc (sizeof (pthread_mutex_t));\n\t\tresult = pthread_mutex_init (*mutex, NULL);\n\t\tbreak;\n\tcase AV_LOCK_OBTAIN:\n\t\tresult = pthread_mutex_lock (*mutex);\n\t\tbreak;\n\tcase AV_LOCK_RELEASE:\n\t\tresult = pthread_mutex_unlock (*mutex);\n\t\tbreak;\n\tcase AV_LOCK_DESTROY:\n\t\tresult = pthread_mutex_destroy (*mutex);\n\t\tfree (*mutex);\n\t\t*mutex = NULL;\n\t\tbreak;\n\tdefault:\n\t\t/* We could return -1 here, but examination of the FFmpeg\n\t\t * code shows that return code testing is erratic, so we'll\n\t\t * take charge and complain loudly if FFmpeg/LibAV's API\n\t\t * changes.  This way we don't end up chasing phantoms. */\n\t\tfatal (\"Unexpected FFmpeg lock request received: %d\", op);\n\t}\n\n\treturn result;\n}\n#endif\n\n/* Here we attempt to determine if FFmpeg/LibAV has trashed the 'duration'\n * and 'bit_rate' fields in AVFormatContext for large files.  Determining\n * whether or not they are likely to be valid is imprecise and will vary\n * depending (at least) on:\n *\n * - The file's size,\n * - The file's codec,\n * - The number and size of tags,\n * - The version of FFmpeg/LibAV, and\n * - Whether it's FFmpeg or LibAV.\n *\n * This function represents a best guess.\n*/\nstatic bool is_timing_broken (AVFormatContext *ic)\n{\n\tif (ic->duration < 0 || ic->bit_rate < 0)\n\t\treturn true;\n\n\t/* If and when FFmpeg uses the right field for its calculation this\n\t * should be self-correcting. */\n\tif (ic->duration < AV_TIME_BASE && !strcmp (ic->iformat->name, \"libgme\"))\n\t\treturn true;\n\n\t/* AAC timing is inaccurate. */\n\tif (!strcmp (ic->iformat->name, \"aac\"))\n\t\treturn true;\n\n\t/* Formats less than 4 GiB should be okay, except those excluded above. */\n\tif (avio_size (ic->pb) < UINT32_MAX)\n\t\treturn false;\n\n\t/* WAV files are limited to 4 GiB but that doesn't stop some encoders. */\n\tif (!strcmp (ic->iformat->name, \"wav\"))\n\t\treturn true;\n\n\tif (!strcmp (ic->iformat->name, \"au\"))\n\t\treturn true;\n\n\treturn false;\n}\n\nstatic void ffmpeg_init ()\n{\n#ifndef NDEBUG\n# ifdef DEBUG\n\tav_log_set_level (AV_LOG_INFO);\n# else\n\tav_log_set_level (AV_LOG_ERROR);\n# endif\n\tav_log_set_callback (ffmpeg_log_cb);\n#endif\n\n#if HAVE_LIBAV || LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,10,100)\n\tavcodec_register_all ();\n\tav_register_all ();\n#endif\n\n\tsupported_extns = lists_strs_new (16);\n\tload_audio_extns (supported_extns);\n\tload_video_extns (supported_extns);\n\n#if HAVE_LIBAV || LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,9,100)\n\tint rc = av_lockmgr_register (locking_cb);\n\tif (rc < 0) {\n\t\tchar buf[128];\n\n\t\tav_strerror (rc, buf, sizeof (buf));\n\t\tfatal (\"Lock manager initialisation failed: %s\", buf);\n\t}\n#endif\n}\n\nstatic void ffmpeg_destroy ()\n{\n#if HAVE_LIBAV || LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,9,100)\n\tav_lockmgr_register (NULL);\n#endif\n\n\tav_log_set_level (AV_LOG_QUIET);\n\tffmpeg_log_repeats (NULL);\n\n\tlists_strs_free (supported_extns);\n}\n\n/* Fill info structure with data from ffmpeg comments. */\nstatic void ffmpeg_info (const char *file_name,\n\t\tstruct file_tags *info,\n\t\tconst int tags_sel)\n{\n\tint err;\n\tAVFormatContext *ic = NULL;\n\tAVDictionaryEntry *entry;\n\tAVDictionary *md;\n\n\terr = avformat_open_input (&ic, file_name, NULL, NULL);\n\tif (err < 0) {\n\t\tlog_errno (\"avformat_open_input() failed\", err);\n\t\treturn;\n\t}\n\n\terr = avformat_find_stream_info (ic, NULL);\n\tif (err < 0) {\n\t\tlog_errno (\"avformat_find_stream_info() failed\", err);\n\t\tgoto end;\n\t}\n\n\tif (!is_timing_broken (ic) && tags_sel & TAGS_TIME) {\n\t\tinfo->time = -1;\n\t\tif (ic->duration != (int64_t)AV_NOPTS_VALUE && ic->duration >= 0)\n\t\t\tinfo->time = ic->duration / AV_TIME_BASE;\n\t}\n\n\tif (!(tags_sel & TAGS_COMMENTS))\n\t\tgoto end;\n\n\tmd = ic->metadata;\n\tif (md == NULL) {\n\t\tunsigned int audio_ix;\n\n\t\taudio_ix = find_first_audio (ic);\n\t\tif (audio_ix < ic->nb_streams)\n\t\t\tmd = ic->streams[audio_ix]->metadata;\n\t}\n\n\tif (md == NULL) {\n\t\tdebug (\"no metadata found\");\n\t\tgoto end;\n\t}\n\n\tentry = av_dict_get (md, \"track\", NULL, 0);\n\tif (entry && entry->value && entry->value[0])\n\t\tinfo->track = atoi (entry->value);\n\tentry = av_dict_get (md, \"title\", NULL, 0);\n\tif (entry && entry->value && entry->value[0])\n\t\tinfo->title = xstrdup (entry->value);\n\tentry = av_dict_get (md, \"artist\", NULL, 0);\n\tif (entry && entry->value && entry->value[0])\n\t\tinfo->artist = xstrdup (entry->value);\n\tentry = av_dict_get (md, \"album\", NULL, 0);\n\tif (entry && entry->value && entry->value[0])\n\t\tinfo->album = xstrdup (entry->value);\n\nend:\n\tavformat_close_input (&ic);\n\tffmpeg_log_repeats (NULL);\n}\n\nstatic long fmt_from_sample_fmt (struct ffmpeg_data *data)\n{\n\tlong result;\n\n\tswitch (data->enc->sample_fmt) {\n\tcase AV_SAMPLE_FMT_U8:\n\tcase AV_SAMPLE_FMT_U8P:\n\t\tresult = SFMT_U8;\n\t\tbreak;\n\tcase AV_SAMPLE_FMT_S16:\n\tcase AV_SAMPLE_FMT_S16P:\n\t\tresult = SFMT_S16;\n\t\tbreak;\n\tcase AV_SAMPLE_FMT_S32:\n\tcase AV_SAMPLE_FMT_S32P:\n\t\tresult = SFMT_S32;\n\t\tbreak;\n\tcase AV_SAMPLE_FMT_FLT:\n\tcase AV_SAMPLE_FMT_FLTP:\n\t\tresult = SFMT_FLOAT;\n\t\tbreak;\n\tdefault:\n\t\tresult = 0;\n\t}\n\n\treturn result;\n}\n\n/* Try to figure out if seeking is broken for this format.\n * The aim here is to try and ensure that seeking either works\n * properly or (because of FFmpeg breakages) is disabled. */\nstatic bool is_seek_broken (struct ffmpeg_data *data)\n{\n#if 0\n\t/* FFmpeg's alternate strategy for formats which don't\n\t * support seeking natively seems to be... unreliable. */\n\tif (!data->ic->iformat->read_seek) {\n\t\tdebug (\"Seek broken by AVInputFormat.read_seek\");\n\t\treturn true;\n\t}\n#endif\n\n\t/* How much do we trust this? */\n\tif (!(data->ic->pb->seekable & AVIO_SEEKABLE_NORMAL)) {\n\t\tdebug (\"Seek broken by AVIOContext.seekable\");\n\t\treturn true;\n\t}\n\n#if !SEEK_IN_DECODER\n\t/* FLV (.flv): av_seek_frame always returns an error (even on success).\n\t *             Seeking from the decoder works for false errors (but\n\t *             probably not for real ones) because the player doesn't\n\t *             get to see them. */\n# ifdef HAVE_FFMPEG\n\tif (avcodec_version () < AV_VERSION_INT(55,8,100))\n# else\n\tif (avcodec_version () < AV_VERSION_INT(55,57,1))\n# endif\n\t{\n\t\tif (!strcmp (data->ic->iformat->name, \"flv\"))\n\t\t\treturn true;\n\t}\n#endif\n\n\treturn false;\n}\n\n/* Downmix multi-channel audios to stereo. */\nstatic void set_downmixing (struct ffmpeg_data *data)\n{\n\tif (av_get_channel_layout_nb_channels (data->enc->channel_layout) <= 2)\n\t\treturn;\n\n\tdata->enc->request_channel_layout = AV_CH_LAYOUT_STEREO;\n}\n\nstatic int ffmpeg_io_read_cb (void *s, uint8_t *buf, int count)\n{\n\tint len;\n\n\tif (!buf || count <= 0)\n\t\treturn AVERROR(EINVAL);\n\n\tlen = io_read ((struct io_stream *)s, buf, (size_t)count);\n\tif (len == 0)\n\t\tlen = AVERROR_EOF;\n\telse if (len < 0)\n\t\tlen = AVERROR(EIO);\n\n\treturn len;\n}\n\nstatic int64_t ffmpeg_io_seek_cb (void *s, int64_t offset, int whence)\n{\n\tint w;\n\tint64_t result = -1;\n\n\t/* Warning: Do not blindly accept the avio.h comments for AVSEEK_FORCE\n\t *          and AVSEEK_SIZE; they are incorrect for later FFmpeg/LibAV\n\t *          versions. */\n\n\tw = whence & ~AVSEEK_FORCE;\n\n\tswitch (w) {\n\tcase SEEK_SET:\n\tcase SEEK_CUR:\n\tcase SEEK_END:\n\t\tresult = io_seek ((struct io_stream *)s, offset, w);\n\t\tbreak;\n\tcase AVSEEK_SIZE:\n\t\tresult = io_file_size ((struct io_stream *)s);\n\t\tbreak;\n\t}\n\n\treturn result;\n}\n\nstatic struct ffmpeg_data *ffmpeg_make_data (void)\n{\n\tstruct ffmpeg_data *data;\n\n\tdata = (struct ffmpeg_data *)xmalloc (sizeof (struct ffmpeg_data));\n\n\tdata->ic = NULL;\n\tdata->pb = NULL;\n\tdata->stream = NULL;\n\tdata->enc = NULL;\n\tdata->codec = NULL;\n\tdata->remain_buf = NULL;\n\tdata->remain_buf_len = 0;\n\tdata->delay = false;\n\tdata->eof = false;\n\tdata->eos = false;\n\tdata->okay = false;\n\tdata->filename = NULL;\n\tdata->iostream = NULL;\n\tdecoder_error_init (&data->error);\n\tdata->fmt = 0;\n\tdata->sample_width = 0;\n\tdata->bitrate = 0;\n\tdata->avg_bitrate = 0;\n#if SEEK_IN_DECODER\n\tdata->seek_req = false;\n\tdata->seek_sec = 0;\n#endif\n\tdata->seek_broken = false;\n\tdata->timing_broken = false;\n#if SEEK_IN_DECODER && defined(DEBUG)\n\tdata->thread_id = 0;\n#endif\n\n\treturn data;\n}\n\nstatic void *ffmpeg_open_internal (struct ffmpeg_data *data)\n{\n\tint err;\n\tconst char *extn = NULL;\n\tunsigned int audio_ix;\n\n\tdata->ic = avformat_alloc_context ();\n\tif (!data->ic)\n\t\tfatal (\"Can't allocate format context!\");\n\n\tdata->ic->pb = avio_alloc_context (NULL, 0, 0, data->iostream,\n\t                                   ffmpeg_io_read_cb, NULL,\n\t                                   ffmpeg_io_seek_cb);\n\tif (!data->ic->pb)\n\t\tfatal (\"Can't allocate avio context!\");\n\n\t/* Save AVIO context pointer so we can workaround an FFmpeg\n\t * memory leak later in ffmpeg_close(). */\n\tdata->pb = data->ic->pb;\n\n\terr = avformat_open_input (&data->ic, NULL, NULL, NULL);\n\tif (err < 0) {\n\t\tchar *buf = ffmpeg_strerror (err);\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"Can't open audio: %s\", buf);\n\t\tfree (buf);\n\t\treturn data;\n\t}\n\n\t/* When FFmpeg and LibAV misidentify a file's codec (and they do)\n\t * then hopefully this will save MOC from wanton destruction. */\n\tif (data->filename) {\n\t\textn = ext_pos (data->filename);\n\t\tif (extn && !strcasecmp (extn, \"wav\")\n\t\t         && strcmp (data->ic->iformat->name, \"wav\")) {\n\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t               \"Format possibly misidentified \"\n\t\t\t               \"as '%s' by FFmpeg/LibAV\",\n\t\t\t               data->ic->iformat->name);\n\t\t\tgoto end;\n\t\t}\n\t}\n\n\terr = avformat_find_stream_info (data->ic, NULL);\n\tif (err < 0) {\n\t\t/* Depending on the particular FFmpeg/LibAV version in use, this\n\t\t * may misreport experimental codecs.  Given we don't know the\n\t\t * codec at this time, we will have to live with it. */\n\t\tchar *buf = ffmpeg_strerror (err);\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Could not find codec parameters: %s\", buf);\n\t\tfree (buf);\n\t\tgoto end;\n\t}\n\n\taudio_ix = find_first_audio (data->ic);\n\tif (audio_ix == data->ic->nb_streams) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"No audio in source\");\n\t\tgoto end;\n\t}\n\n\tdata->stream = data->ic->streams[audio_ix];\n\n\tdata->enc = avcodec_alloc_context3 (NULL);\n\tif (!data->enc) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"Failed to allocate codec context\");\n\t\tgoto end;\n\t}\n\n#ifdef HAVE_STRUCT_AVSTREAM_CODECPAR\n\terr = avcodec_parameters_to_context (data->enc, data->stream->codecpar);\n\tif (err < 0) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"Failed to copy codec parameters\");\n\t\tgoto end;\n\t}\n#else\n\terr = avcodec_copy_context (data->enc, data->stream->codec);\n\tif (err < 0) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"Failed to copy codec context\");\n\t\tgoto end;\n\t}\n#endif\n\n\tdata->codec = avcodec_find_decoder (data->enc->codec_id);\n\tif (!data->codec) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"No codec for this audio\");\n\t\tgoto end;\n\t}\n\n\tif (data->filename) {\n\t\tconst char *fn;\n\n\t\tfn = strrchr (data->filename, '/');\n\t\tfn = fn ? fn + 1 : data->filename;\n\t\tdebug (\"FFmpeg thinks '%s' is format(codec) '%s(%s)'\",\n\t\t        fn, data->ic->iformat->name, data->codec->name);\n\t}\n\telse\n\t\tdebug (\"FFmpeg thinks stream is format(codec) '%s(%s)'\",\n\t\t        data->ic->iformat->name, data->codec->name);\n\n\t/* This may or may not work depending on the particular version of\n\t * FFmpeg/LibAV in use.  For some versions this will be caught in\n\t * *_find_stream_info() above and misreported as an unfound codec\n\t * parameters error. */\n\tif (data->codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"The codec is experimental and may damage MOC: %s\",\n\t\t\t\tdata->codec->name);\n\t\tgoto end;\n\t}\n\n\tset_downmixing (data);\n\tif (data->codec->capabilities & AV_CODEC_CAP_TRUNCATED)\n\t\tdata->enc->flags |= AV_CODEC_FLAG_TRUNCATED;\n\n\tif (avcodec_open2 (data->enc, data->codec, NULL) < 0)\n\t{\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"No codec for this audio\");\n\t\tgoto end;\n\t}\n\n\tdata->fmt = fmt_from_sample_fmt (data);\n\tif (data->fmt == 0) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"Cannot get sample size from unknown sample format: %s\",\n\t\t               av_get_sample_fmt_name (data->enc->sample_fmt));\n\t\tgoto end;\n\t}\n\n\tdata->sample_width = sfmt_Bps (data->fmt);\n\n\tif (data->codec->capabilities & AV_CODEC_CAP_DELAY)\n\t\tdata->delay = true;\n\tdata->seek_broken = is_seek_broken (data);\n\tdata->timing_broken = is_timing_broken (data->ic);\n\n\tif (data->timing_broken && extn && !strcasecmp (extn, \"wav\")) {\n\t\tffmpeg_log_repeats (NULL);\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t                   \"Broken WAV file; use W64!\");\n\t\tgoto end;\n\t}\n\n\tdata->okay = true;\n\n\tif (!data->timing_broken && data->ic->duration >= AV_TIME_BASE)\n\t\tdata->avg_bitrate = (int) (avio_size (data->ic->pb) /\n\t\t                           (data->ic->duration / AV_TIME_BASE) * 8);\n\n\tif (!data->timing_broken && data->ic->bit_rate > 0)\n\t\tdata->bitrate = data->ic->bit_rate;\n\n\treturn data;\n\nend:\n#ifdef HAVE_AVCODEC_FREE_CONTEXT\n\tavcodec_free_context (&data->enc);\n#else\n\tavcodec_close (data->enc);\n\tav_freep (&data->enc);\n#endif\n\tavformat_close_input (&data->ic);\n\tffmpeg_log_repeats (NULL);\n\treturn data;\n}\n\nstatic void *ffmpeg_open (const char *file)\n{\n\tstruct ffmpeg_data *data;\n\n\tdata = ffmpeg_make_data ();\n\n\tdata->filename = xstrdup (file);\n\tdata->iostream = io_open (file, 1);\n\tif (!io_ok (data->iostream)) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"Can't open file: %s\", io_strerror (data->iostream));\n\t\treturn data;\n\t}\n\n\treturn ffmpeg_open_internal (data);\n}\n\nstatic void *ffmpeg_open_stream (struct io_stream *stream)\n{\n\tstruct ffmpeg_data *data;\n\n\tdata = ffmpeg_make_data ();\n\n\tdata->iostream = stream;\n\n\treturn ffmpeg_open_internal (data);\n}\n\nstatic int ffmpeg_can_decode (struct io_stream *stream)\n{\n\tint res;\n\tAVProbeData probe_data;\n\tAVInputFormat *fmt;\n\tchar buf[8096 + AVPROBE_PADDING_SIZE] = {0};\n\n\tres = io_peek (stream, buf, sizeof (buf));\n\tif (res < 0) {\n\t\terror (\"Stream error: %s\", io_strerror (stream));\n\t\treturn 0;\n\t}\n\n\tprobe_data.filename = NULL;\n\tprobe_data.buf = (unsigned char*)buf;\n\tprobe_data.buf_size = sizeof (buf) - AVPROBE_PADDING_SIZE;\n#ifdef HAVE_STRUCT_AVPROBEDATA_MIME_TYPE\n\tprobe_data.mime_type = NULL;\n#endif\n\n\tfmt = av_probe_input_format (&probe_data, 1);\n\n\treturn fmt != NULL;\n}\n\nstatic void put_in_remain_buf (struct ffmpeg_data *data, const char *buf,\n\t\tconst int len)\n{\n\tdebug (\"Remain: %dB\", len);\n\n\tdata->remain_buf_len = len;\n\tdata->remain_buf = (char *)xmalloc (len);\n\tmemcpy (data->remain_buf, buf, len);\n}\n\nstatic void add_to_remain_buf (struct ffmpeg_data *data, const char *buf,\n\t\tconst int len)\n{\n\tdebug (\"Adding %dB to remain_buf\", len);\n\n\tdata->remain_buf = (char *)xrealloc (data->remain_buf,\n\t\t\tdata->remain_buf_len + len);\n\tmemcpy (data->remain_buf + data->remain_buf_len, buf, len);\n\tdata->remain_buf_len += len;\n\n\tdebug (\"remain_buf is %dB long\", data->remain_buf_len);\n}\n\n/* Free the remainder buffer. */\nstatic void free_remain_buf (struct ffmpeg_data *data)\n{\n\tfree (data->remain_buf);\n\tdata->remain_buf = NULL;\n\tdata->remain_buf_len = 0;\n}\n\n/* Satisfy the request from previously decoded samples. */\nstatic int take_from_remain_buf (struct ffmpeg_data *data, char *buf, int buf_len)\n{\n\tint to_copy = MIN (buf_len, data->remain_buf_len);\n\n\tdebug (\"Copying %d bytes from the remain buf\", to_copy);\n\n\tmemcpy (buf, data->remain_buf, to_copy);\n\n\tif (to_copy < data->remain_buf_len) {\n\t\tmemmove (data->remain_buf, data->remain_buf + to_copy,\n\t\t\t\tdata->remain_buf_len - to_copy);\n\t\tdata->remain_buf_len -= to_copy;\n\t}\n\telse {\n\t\tdebug (\"Remain buf is now empty\");\n\t\tfree_remain_buf (data);\n\t}\n\n\treturn to_copy;\n}\n\n/* Copy samples to output or remain buffer. */\nstatic int copy_or_buffer (struct ffmpeg_data *data, char *in, int in_len,\n                                                     char *out, int out_len)\n{\n\tif (in_len == 0)\n\t\treturn 0;\n\n\tif (in_len <= out_len) {\n\t\tmemcpy (out, in, in_len);\n\t\treturn in_len;\n\t}\n\n\tif (out_len == 0) {\n\t\tadd_to_remain_buf (data, in, in_len);\n\t\treturn 0;\n\t}\n\n\tmemcpy (out, in, out_len);\n\tput_in_remain_buf (data, in + out_len, in_len - out_len);\n\treturn out_len;\n}\n\n/* Create a new packet ('cause FFmpeg doesn't provide one). */\nstatic inline AVPacket *new_packet (struct ffmpeg_data *data)\n{\n\tAVPacket *pkt;\n\n\tassert (data->stream);\n\n#if HAVE_AV_PACKET_FNS\n\tpkt = av_packet_alloc ();\n#else\n\tpkt = (AVPacket *)av_malloc (sizeof (AVPacket));\n\tif (!pkt)\n\t\tfatal (\"av_malloc() failed to allocate memory\");\n\tav_init_packet (pkt);\n\tpkt->data = NULL;\n\tpkt->size = 0;\n#endif\n\n\tpkt->stream_index = data->stream->index;\n\n\treturn pkt;\n}\n\nstatic inline void free_packet (AVPacket *pkt)\n{\n#if HAVE_AV_PACKET_FNS\n\tav_packet_free (&pkt);\n#else\n\tav_free_packet (pkt);\n\tav_free (pkt);\n#endif\n}\n\n/* Read a packet from the file or empty packet if flushing delayed\n * samples. */\nstatic AVPacket *get_packet (struct ffmpeg_data *data)\n{\n\tint rc;\n\tAVPacket *pkt;\n\n\tassert (data);\n\tassert (!data->eos);\n\n\tpkt = new_packet (data);\n\n\tif (data->eof)\n\t\treturn pkt;\n\n\trc = av_read_frame (data->ic, pkt);\n\tif (rc >= 0) {\n\t\tdebug (\"Got %dB packet\", pkt->size);\n\t\treturn pkt;\n\t}\n\n\tfree_packet (pkt);\n\n\t/* FFmpeg has (at least) two ways of indicating EOF.  (Awesome!) */\n\tif (rc == AVERROR_EOF)\n\t\tdata->eof = true;\n\tif (data->ic->pb && data->ic->pb->eof_reached)\n\t\tdata->eof = true;\n\n\tif (!data->eof && rc < 0) {\n\t\tchar *buf = ffmpeg_strerror (rc);\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"Error in the stream: %s\", buf);\n\t\tfree (buf);\n\t\treturn NULL;\n\t}\n\n\tif (data->delay)\n\t\treturn new_packet (data);\n\n\tdata->eos = true;\n\treturn NULL;\n}\n\n#ifndef HAVE_AVCODEC_RECEIVE_FRAME\n/* Decode samples from packet data using avcodec_decode_audio4(). */\n# define decode_audio avcodec_decode_audio4\n#endif\n\n#ifdef HAVE_AVCODEC_RECEIVE_FRAME\n/* Decode audio using FFmpeg's send/receive encoding and decoding API. */\nstatic int decode_audio (AVCodecContext *ctx, AVFrame *frame,\n                         int *got_frame_ptr, const AVPacket *pkt)\n{\n\tint rc, result = 0;\n\n\t*got_frame_ptr = 0;\n\n\trc = avcodec_send_packet (ctx, pkt);\n\tswitch (rc) {\n\tcase 0:\n\t\tbreak;\n\tcase AVERROR(EAGAIN):\n\t\tdebug (\"avcodec_send_packet(): AVERROR(EAGAIN)\");\n\t\tbreak;\n\tcase AVERROR_EOF:\n\t\tif (pkt->data)\n\t\t\tdebug (\"avcodec_send_packet(): AVERROR_EOF\");\n\t\tbreak;\n\tcase AVERROR(EINVAL):\n\t\tlogit (\"avcodec_send_packet(): AVERROR(EINVAL)\");\n\t\tresult = rc;\n\t\tbreak;\n\tcase AVERROR(ENOMEM):\n\t\tlogit (\"avcodec_send_packet(): AVERROR(ENOMEM)\");\n\t\tresult = rc;\n\t\tbreak;\n\tdefault:\n\t\tlog_errno (\"avcodec_send_packet()\", rc);\n\t\tresult = rc;\n\t}\n\n\tif (result == 0) {\n\t\tresult = pkt->size;\n\n\t\trc = avcodec_receive_frame (ctx, frame);\n\t\tswitch (rc) {\n\t\tcase 0:\n\t\t\t*got_frame_ptr = 1;\n\t\t\tbreak;\n\t\tcase AVERROR(EAGAIN):\n\t\t\tdebug (\"avcodec_receive_frame(): AVERROR(EAGAIN)\");\n\t\t\tbreak;\n\t\tcase AVERROR_EOF:\n\t\t\tdebug (\"avcodec_receive_frame(): AVERROR_EOF\");\n\t\t\tavcodec_flush_buffers (ctx);\n\t\t\tbreak;\n\t\tcase AVERROR(EINVAL):\n\t\t\tlogit (\"avcodec_receive_frame(): AVERROR(EINVAL)\");\n\t\t\tresult = rc;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tlog_errno (\"avcodec_receive_frame()\", rc);\n\t\t\tresult = rc;\n\t\t}\n\t}\n\n\treturn result;\n}\n#endif\n\n/* Decode samples from packet data. */\nstatic int decode_packet (struct ffmpeg_data *data, AVPacket *pkt,\n                          char *buf, int buf_len)\n{\n\tint filled = 0;\n\tchar *packed;\n\tAVFrame *frame;\n\n#ifdef HAVE_AV_FRAME_FNS\n\tframe = av_frame_alloc ();\n#else\n\tframe = avcodec_alloc_frame ();\n#endif\n\n\tdo {\n\t\tint len, got_frame, is_planar, packed_size, copied;\n\n\t\tlen = decode_audio (data->enc, frame, &got_frame, pkt);\n\n\t\tif (len < 0) {\n\t\t\t/* skip frame */\n\t\t\tdecoder_error (&data->error, ERROR_STREAM, 0,\n\t\t\t               \"Error in the stream!\");\n\t\t\tbreak;\n\t\t}\n\n\t\tdebug (\"Decoded %dB\", len);\n\n\t\tpkt->data += len;\n\t\tpkt->size -= len;\n\n\t\tif (!got_frame) {\n\t\t\tdata->eos = data->eof && (pkt->size == 0);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (frame->nb_samples == 0)\n\t\t\tcontinue;\n\n\t\tis_planar = av_sample_fmt_is_planar (data->enc->sample_fmt);\n\t\tpacked = (char *)frame->extended_data[0];\n\t\tpacked_size = frame->nb_samples * data->sample_width\n\t\t                                * data->enc->channels;\n\n\t\tif (is_planar && data->enc->channels > 1) {\n\t\t\tint sample, ch;\n\n\t\t\tpacked = xmalloc (packed_size);\n\n\t\t\tfor (sample = 0; sample < frame->nb_samples; sample += 1) {\n\t\t\t\tfor (ch = 0; ch < data->enc->channels; ch += 1)\n\t\t\t\t\tmemcpy (packed + (sample * data->enc->channels + ch)\n\t\t\t\t\t                         * data->sample_width,\n\t\t\t\t\t        (char *)frame->extended_data[ch] + sample * data->sample_width,\n\t\t\t\t\t        data->sample_width);\n\t\t\t}\n\t\t}\n\n\t\tcopied = copy_or_buffer (data, packed, packed_size, buf, buf_len);\n\t\tbuf += copied;\n\t\tfilled += copied;\n\t\tbuf_len -= copied;\n\n\t\tdebug (\"Copying %dB (%dB filled)\", packed_size, filled);\n\n\t\tif (packed != (char *)frame->extended_data[0])\n\t\t\tfree (packed);\n\t} while (pkt->size > 0);\n\n#ifdef HAVE_AV_FRAME_FNS\n\tav_frame_free (&frame);\n#else\n\tavcodec_free_frame (&frame);\n#endif\n\n\treturn filled;\n}\n\n#if SEEK_IN_DECODER\nstatic bool seek_in_stream (struct ffmpeg_data *data)\n#else\nstatic bool seek_in_stream (struct ffmpeg_data *data, int sec)\n#endif\n{\n\tint rc, flags = AVSEEK_FLAG_ANY;\n\tint64_t seek_ts;\n\n#if SEEK_IN_DECODER\n\tint sec = data->seek_sec;\n\n#ifdef DEBUG\n\tassert (pthread_equal (data->thread_id, pthread_self ()));\n#endif\n#endif\n\n\t/* FFmpeg can't seek if the file has already reached EOF. */\n\tif (data->eof)\n\t\treturn false;\n\n\tseek_ts = av_rescale (sec, data->stream->time_base.den,\n\t                           data->stream->time_base.num);\n\n\tif (data->stream->start_time != (int64_t)AV_NOPTS_VALUE) {\n\t\tif (seek_ts > INT64_MAX - MAX(0, data->stream->start_time)) {\n\t\t\tlogit (\"Seek value too large\");\n\t\t\treturn false;\n\t\t}\n\t\tseek_ts += data->stream->start_time;\n\t}\n\n\tif (data->stream->cur_dts > seek_ts)\n\t\tflags |= AVSEEK_FLAG_BACKWARD;\n\n\trc = av_seek_frame (data->ic, data->stream->index, seek_ts, flags);\n\tif (rc < 0) {\n\t\tlog_errno (\"Seek error\", rc);\n\t\treturn false;\n\t}\n\n\tavcodec_flush_buffers (data->enc);\n\n\treturn true;\n}\n\nstatic inline int compute_bitrate (struct sound_params *sound_params,\n                                   int bytes_used, int bytes_produced,\n                                   int bitrate)\n{\n\tint64_t bytes_per_frame, bytes_per_second, seconds;\n\n\tbytes_per_frame = sfmt_Bps (sound_params->fmt) * sound_params->channels;\n\tbytes_per_second = bytes_per_frame * (int64_t)sound_params->rate;\n\tseconds = (int64_t)bytes_produced / bytes_per_second;\n\tif (seconds > 0)\n\t\tbitrate = (int)((int64_t)bytes_used * 8 / seconds);\n\n\treturn bitrate;\n}\n\nstatic int ffmpeg_decode (void *prv_data, char *buf, int buf_len,\n                          struct sound_params *sound_params)\n{\n\tstruct ffmpeg_data *data = (struct ffmpeg_data *)prv_data;\n\tint bytes_used = 0, bytes_produced = 0;\n\n\tdecoder_error_clear (&data->error);\n\n\tif (data->eos)\n\t\treturn 0;\n\n\t/* FFmpeg claims to always return native endian. */\n\tsound_params->channels = data->enc->channels;\n\tsound_params->rate = data->enc->sample_rate;\n\tsound_params->fmt = data->fmt | SFMT_NE;\n\n#if SEEK_IN_DECODER\n\tif (data->seek_req) {\n\t\tdata->seek_req = false;\n\t\tif (seek_in_stream (data))\n\t\t\tfree_remain_buf (data);\n\t}\n#endif\n\n\tif (data->remain_buf)\n\t\treturn take_from_remain_buf (data, buf, buf_len);\n\n\tdo {\n\t\tuint8_t *saved_pkt_data_ptr;\n\t\tAVPacket *pkt;\n\n\t\tpkt = get_packet (data);\n\t\tif (!pkt)\n\t\t\tbreak;\n\n\t\tif (pkt->stream_index != data->stream->index) {\n\t\t\tfree_packet (pkt);\n\t\t\tcontinue;\n\t\t}\n\n#ifdef AV_PKT_FLAG_CORRUPT\n\t\tif (pkt->flags & AV_PKT_FLAG_CORRUPT) {\n\t\t\tffmpeg_log_repeats (NULL);\n\t\t\tdebug (\"Dropped corrupt packet.\");\n\t\t\tfree_packet (pkt);\n\t\t\tcontinue;\n\t\t}\n#endif\n\n\t\tsaved_pkt_data_ptr = pkt->data;\n\t\tbytes_used += pkt->size;\n\n\t\tbytes_produced = decode_packet (data, pkt, buf, buf_len);\n\t\tbuf += bytes_produced;\n\t\tbuf_len -= bytes_produced;\n\n\t\t/* FFmpeg will segfault if the data pointer is not restored. */\n\t\tpkt->data = saved_pkt_data_ptr;\n\t\tfree_packet (pkt);\n\t} while (!bytes_produced && !data->eos);\n\n\tif (!data->timing_broken)\n\t\tdata->bitrate = compute_bitrate (sound_params, bytes_used,\n\t\t                                 bytes_produced + data->remain_buf_len,\n\t\t                                 data->bitrate);\n\n\treturn bytes_produced;\n}\n\nstatic int ffmpeg_seek (void *prv_data, int sec)\n{\n\tstruct ffmpeg_data *data = (struct ffmpeg_data *)prv_data;\n\n\tassert (sec >= 0);\n\n\tif (data->seek_broken)\n\t\treturn -1;\n\n#if SEEK_IN_DECODER\n\n\tdata->seek_sec = sec;\n\tdata->seek_req = true;\n#ifdef DEBUG\n\tdata->thread_id = pthread_self ();\n#endif\n\n#else\n\n\tif (!seek_in_stream (data, sec))\n\t\treturn -1;\n\n\tfree_remain_buf (data);\n\n#endif\n\n\treturn sec;\n}\n\nstatic void ffmpeg_close (void *prv_data)\n{\n\tstruct ffmpeg_data *data = (struct ffmpeg_data *)prv_data;\n\n\t/* We need to delve into the AVIOContext struct to free the\n\t * buffer FFmpeg leaked if avformat_open_input() failed.  Do\n\t * not be tempted to call avio_close() here; it will segfault. */\n\tif (data->pb) {\n\t\tav_freep (&data->pb->buffer);\n\t\tav_freep (&data->pb);\n\t}\n\n\tif (data->okay) {\n#ifdef HAVE_AVCODEC_FREE_CONTEXT\n\t\tavcodec_free_context (&data->enc);\n#else\n\t\tavcodec_close (data->enc);\n\t\tav_freep (&data->enc);\n#endif\n\t\tavformat_close_input (&data->ic);\n\t\tfree_remain_buf (data);\n\t}\n\n\tffmpeg_log_repeats (NULL);\n\n\tif (data->iostream) {\n\t\tio_close (data->iostream);\n\t\tdata->iostream = NULL;\n\t}\n\n\tdecoder_error_clear (&data->error);\n\tfree (data->filename);\n\tfree (data);\n}\n\nstatic struct io_stream *ffmpeg_get_iostream (void *prv_data)\n{\n\tstruct ffmpeg_data *data;\n\n\tassert (prv_data);\n\n\tdata = (struct ffmpeg_data *)prv_data;\n\treturn data->iostream;\n}\n\nstatic int ffmpeg_get_bitrate (void *prv_data)\n{\n\tstruct ffmpeg_data *data = (struct ffmpeg_data *)prv_data;\n\n\treturn data->timing_broken ? -1 : data->bitrate / 1000;\n}\n\nstatic int ffmpeg_get_avg_bitrate (void *prv_data)\n{\n\tstruct ffmpeg_data *data = (struct ffmpeg_data *)prv_data;\n\n\treturn data->timing_broken ? -1 : data->avg_bitrate / 1000;\n}\n\nstatic int ffmpeg_get_duration (void *prv_data)\n{\n\tstruct ffmpeg_data *data = (struct ffmpeg_data *)prv_data;\n\n\tif (data->timing_broken)\n\t\treturn -1;\n\n\tif (!data->stream)\n\t\treturn -1;\n\n\tif (data->stream->duration == (int64_t)AV_NOPTS_VALUE)\n\t\treturn -1;\n\n\tif (data->stream->duration < 0)\n\t\treturn -1;\n\n\treturn data->stream->duration * data->stream->time_base.num\n\t                              / data->stream->time_base.den;\n}\n\nstatic int ffmpeg_our_format_ext (const char *ext)\n{\n\treturn (lists_strs_exists (supported_extns, ext)) ? 1 : 0;\n}\n\nstatic int ffmpeg_our_format_mime (const char *mime_type)\n{\n\tAVOutputFormat *fmt;\n\n\tfmt = av_guess_format (NULL, NULL, mime_type);\n\treturn fmt ? 1 : 0;\n}\n\nstatic void ffmpeg_get_error (void *prv_data, struct decoder_error *error)\n{\n\tstruct ffmpeg_data *data = (struct ffmpeg_data *)prv_data;\n\n\tdecoder_error_copy (error, &data->error);\n}\n\nstatic struct decoder ffmpeg_decoder = {\n\tDECODER_API_VERSION,\n\tffmpeg_init,\n\tffmpeg_destroy,\n\tffmpeg_open,\n\tffmpeg_open_stream,\n\tffmpeg_can_decode,\n\tffmpeg_close,\n\tffmpeg_decode,\n\tffmpeg_seek,\n\tffmpeg_info,\n\tffmpeg_get_bitrate,\n\tffmpeg_get_duration,\n\tffmpeg_get_error,\n\tffmpeg_our_format_ext,\n\tffmpeg_our_format_mime,\n\tNULL,\n\tNULL,\n\tffmpeg_get_iostream,\n\tffmpeg_get_avg_bitrate\n};\n\nstruct decoder *plugin_init ()\n{\n\treturn &ffmpeg_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/ffmpeg/ffmpeg.m4",
    "content": "dnl ffmpeg/libav\n\nAC_ARG_WITH(ffmpeg, AS_HELP_STRING([--without-ffmpeg],\n                                   [Compile without ffmpeg/libav]))\n\nif test \"x$with_ffmpeg\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(ffmpeg, libavutil libavcodec libavformat,\n\t\t[ffmpeg_CPPFLAGS=`$PKG_CONFIG --cflags-only-I libavutil libavcodec libavformat`\n\t\t AC_SUBST(ffmpeg_CPPFLAGS)\n\t\t AC_SUBST(ffmpeg_CFLAGS)\n\t\t AC_SUBST(ffmpeg_LIBS)\n\t\t want_ffmpeg=\"yes\"],\n\t\t[true])\n\tif test \"x$want_ffmpeg\" = \"xyes\"\n\tthen\n\t\tif $PKG_CONFIG --max-version 53.47.99 libavcodec\n\t\tthen\n\t\t\tAC_MSG_ERROR([You need FFmpeg/LibAV of at least release 1.0/10.0.])\n\t\tfi\n\t\tif test \"`$PKG_CONFIG --modversion libavcodec | awk -F. '{ print $3; }'`\" -gt 99\n\t\tthen\n\t\t\tif ! $PKG_CONFIG --atleast-version 54.59.100 libavcodec\n\t\t\tthen\n\t\t\t\tAC_MSG_ERROR([You need FFmpeg of at least release 1.0.])\n\t\t\tfi\n\t\t\tDECODER_PLUGINS=\"$DECODER_PLUGINS ffmpeg\"\n\t\t\tAC_DEFINE([HAVE_FFMPEG], 1,\n\t\t\t          [Define to 1 if you know you have FFmpeg.])\n\t\telse\n\t\t\tif ! $PKG_CONFIG --atleast-version 55.34.1 libavcodec\n\t\t\tthen\n\t\t\t\tAC_MSG_ERROR([You need LibAV of at least release 10.0.])\n\t\t\tfi\n\t\t\tDECODER_PLUGINS=\"$DECODER_PLUGINS ffmpeg(libav)\"\n\t\t\tAC_DEFINE([HAVE_LIBAV], 1,\n\t\t\t          [Define to 1 if you know you have LibAV.])\n\t\tfi\n\t\tsave_CPPFLAGS=\"$CPPFLAGS\"\n\t\tCPPFLAGS=\"$CPPFLAGS $ffmpeg_CPPFLAGS\"\n\t\tsave_CFLAGS=\"$CFLAGS\"\n\t\tCFLAGS=\"$CFLAGS $ffmpeg_CFLAGS\"\n\t\tsave_LIBS=\"$LIBS\"\n\t\tLIBS=\"$LIBS $ffmpeg_LIBS\"\n\t\tAC_CHECK_MEMBERS([struct AVProbeData.mime_type], [], [],\n\t                     [#include <libavformat/avformat.h>])\n\t\tAC_CHECK_HEADERS([libavutil/channel_layout.h])\n\t\tAC_SEARCH_LIBS(av_packet_alloc, avcodec,\n\t\t\t[AC_DEFINE([HAVE_AV_PACKET_FNS], 1,\n\t\t\t\t[Define to 1 if you have the `av_packet_*' functions.])])\n\t\tAC_SEARCH_LIBS(av_frame_alloc, avutil,\n\t\t\t[AC_DEFINE([HAVE_AV_FRAME_FNS], 1,\n\t\t\t\t[Define to 1 if you have the `av_frame_*' functions.])])\n\t\tAC_SEARCH_LIBS(avcodec_free_context, avcodec,\n\t\t\t[AC_DEFINE([HAVE_AVCODEC_FREE_CONTEXT], 1,\n\t\t\t\t[Define to 1 if you have the `avcodec_free_context' function.])])\n\t\tAC_SEARCH_LIBS(avcodec_receive_frame, avcodec,\n\t\t\t[AC_DEFINE([HAVE_AVCODEC_RECEIVE_FRAME], 1,\n\t\t\t\t[Define to 1 if you have the `avcodec_receive_frame' function.])])\n\t\tAC_CHECK_MEMBERS([struct AVStream.codecpar], [], [],\n\t                     [#include <libavformat/avformat.h>])\n        CPPFLAGS=\"$save_CPPFLAGS\"\n        CFLAGS=\"$save_CFLAGS\"\n        LIBS=\"$save_LIBS\"\n    fi\nfi\n\nAM_CONDITIONAL([BUILD_ffmpeg], [test \"$want_ffmpeg\"])\nAC_CONFIG_FILES([decoder_plugins/ffmpeg/Makefile])\n"
  },
  {
    "path": "decoder_plugins/flac/Makefile.am",
    "content": "lib_LTLIBRARIES = libflac_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibflac_decoder_la_CFLAGS = $(LIBFLAC_CFLAGS) -I$(top_srcdir)\nlibflac_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibflac_decoder_la_LIBADD = $(LIBFLAC_LIBS)\nlibflac_decoder_la_SOURCES = flac.c\n"
  },
  {
    "path": "decoder_plugins/flac/flac.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* The code is based on libxmms-flac written by Josh Coalson. */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <string.h>\n#include <FLAC/all.h>\n#include <stdlib.h>\n#include <strings.h>\n\n/*#define DEBUG*/\n\n#include \"common.h\"\n#include \"audio.h\"\n#include \"decoder.h\"\n#include \"server.h\"\n#include \"log.h\"\n#include \"io.h\"\n\n#define MAX_SUPPORTED_CHANNELS\t\t2\n\n#define SAMPLES_PER_WRITE\t\t512\n#define SAMPLE_BUFFER_SIZE ((FLAC__MAX_BLOCK_SIZE + SAMPLES_PER_WRITE) * MAX_SUPPORTED_CHANNELS * (32/8))\n\nstruct flac_data\n{\n\tFLAC__StreamDecoder *decoder;\n\tstruct io_stream *stream;\n\tint bitrate;\n\tint avg_bitrate;\n\tint abort; /* abort playing (due to an error) */\n\n\tunsigned int length;\n\tFLAC__uint64 total_samples;\n\n\tFLAC__byte sample_buffer[SAMPLE_BUFFER_SIZE];\n\tunsigned int sample_buffer_fill;\n\n\t/* sound parameters */\n\tunsigned int bits_per_sample;\n\tunsigned int sample_rate;\n\tunsigned int channels;\n\n\tFLAC__uint64 last_decode_position;\n\n\tint ok; /* was this stream successfully opened? */\n\tstruct decoder_error error;\n};\n\n/* Convert FLAC big-endian data into PCM little-endian. */\nstatic size_t pack_pcm_signed (FLAC__byte *data,\n\t\tconst FLAC__int32 * const input[], unsigned int wide_samples,\n\t\tunsigned int channels, unsigned int bps)\n{\n\tFLAC__byte * const start = data;\n\tFLAC__int32 sample;\n\tconst FLAC__int32 *input_;\n\tunsigned int samples, channel;\n\tunsigned int bytes_per_sample;\n\tunsigned int incr;\n\n\tif (bps == 24)\n\t\tbps = 32; /* we encode to 32-bit words */\n\tbytes_per_sample = bps / 8;\n\tincr = bytes_per_sample * channels;\n\n\tfor (channel = 0; channel < channels; channel++) {\n\t\tsamples = wide_samples;\n\t\tdata = start + bytes_per_sample * channel;\n\t\tinput_ = input[channel];\n\n\t\twhile(samples--) {\n\t\t\tsample = *input_++;\n\n\t\t\tswitch(bps) {\n\t\t\t\tcase 8:\n\t\t\t\t\tdata[0] = sample;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 16:\n\t\t\t\t\tdata[1] = (FLAC__byte)(sample >> 8);\n\t\t\t\t\tdata[0] = (FLAC__byte)sample;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 32:\n\t\t\t\t\tdata[3] = (FLAC__byte)(sample >> 16);\n\t\t\t\t\tdata[2] = (FLAC__byte)(sample >> 8);\n\t\t\t\t\tdata[1] = (FLAC__byte)sample;\n\t\t\t\t\tdata[0] = 0;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdata += incr;\n\t\t}\n\t}\n\n\tdebug (\"Converted %u bytes\", wide_samples * channels * bytes_per_sample);\n\n\treturn wide_samples * channels * bytes_per_sample;\n}\n\nstatic FLAC__StreamDecoderWriteStatus write_cb (\n\t\tconst FLAC__StreamDecoder *unused ATTR_UNUSED,\n\t\tconst FLAC__Frame *frame,\n\t\tconst FLAC__int32 * const buffer[], void *client_data)\n{\n\tstruct flac_data *data = (struct flac_data *)client_data;\n\tconst unsigned int wide_samples = frame->header.blocksize;\n\n\tif (data->abort)\n\t\treturn FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;\n\n\tdata->sample_buffer_fill = pack_pcm_signed (\n\t\t\tdata->sample_buffer, buffer, wide_samples,\n\t\t\tdata->channels, data->bits_per_sample);\n\n\treturn FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;\n}\n\nstatic void metadata_cb (\n\t\tconst FLAC__StreamDecoder *unused ATTR_UNUSED,\n\t\tconst FLAC__StreamMetadata *metadata, void *client_data)\n{\n\tstruct flac_data *data = (struct flac_data *)client_data;\n\n\tif (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {\n\t\tdebug (\"Got metadata info\");\n\n\t\tdata->total_samples = metadata->data.stream_info.total_samples;\n\t\tdata->bits_per_sample = metadata->data.stream_info.bits_per_sample;\n\t\tdata->channels = metadata->data.stream_info.channels;\n\t\tdata->sample_rate = metadata->data.stream_info.sample_rate;\n\t\tif (data->total_samples > 0)\n\t\t\tdata->length = data->total_samples / data->sample_rate;\n\t}\n}\n\nstatic void error_cb (\n\t\tconst FLAC__StreamDecoder *unused ATTR_UNUSED,\n\t\tFLAC__StreamDecoderErrorStatus status, void *client_data)\n{\n\tstruct flac_data *data = (struct flac_data *)client_data;\n\n\tif (status != FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC) {\n\t\tdebug (\"Aborting due to error\");\n\t\tdata->abort = 1;\n\t}\n\telse\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"FLAC: lost sync\");\n}\n\nstatic FLAC__StreamDecoderReadStatus read_cb (\n\t\tconst FLAC__StreamDecoder *unused ATTR_UNUSED,\n\t\tFLAC__byte buffer[], size_t *bytes, void *client_data)\n{\n\tstruct flac_data *data = (struct flac_data *)client_data;\n\tssize_t res;\n\n\tres = io_read (data->stream, buffer, *bytes);\n\n\tif (res > 0) {\n\t\t*bytes = res;\n\t\treturn FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;\n\t}\n\n\tif (res == 0) {\n\t\t*bytes = 0;\n\t\t/* not sure why this works, but if it ain't broke... */\n\t\treturn FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;\n\t}\n\n\terror (\"read error: %s\", io_strerror(data->stream));\n\n\treturn FLAC__STREAM_DECODER_READ_STATUS_ABORT;\n}\n\nstatic FLAC__StreamDecoderSeekStatus seek_cb (\n\t\tconst FLAC__StreamDecoder *unused ATTR_UNUSED,\n\t\tFLAC__uint64 absolute_byte_offset, void *client_data)\n{\n\tstruct flac_data *data = (struct flac_data *)client_data;\n\n\treturn io_seek(data->stream, absolute_byte_offset, SEEK_SET) >= 0\n\t\t? FLAC__STREAM_DECODER_SEEK_STATUS_OK\n\t\t: FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;\n}\n\nstatic FLAC__StreamDecoderTellStatus tell_cb (\n\t\tconst FLAC__StreamDecoder *unused ATTR_UNUSED,\n\t\tFLAC__uint64 *absolute_byte_offset, void *client_data)\n{\n\tstruct flac_data *data = (struct flac_data *)client_data;\n\n\t*absolute_byte_offset = io_tell (data->stream);\n\n\treturn FLAC__STREAM_DECODER_TELL_STATUS_OK;\n}\n\nstatic FLAC__StreamDecoderLengthStatus length_cb (\n\t\tconst FLAC__StreamDecoder *unused ATTR_UNUSED,\n\t\tFLAC__uint64 *stream_length, void *client_data)\n{\n\toff_t file_size;\n\tstruct flac_data *data = (struct flac_data *)client_data;\n\n\tfile_size = io_file_size (data->stream);\n\tif (file_size == -1)\n\t\treturn FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR;\n\n\t*stream_length = file_size;\n\n\treturn FLAC__STREAM_DECODER_LENGTH_STATUS_OK;\n}\n\nstatic FLAC__bool eof_cb (\n\t\tconst FLAC__StreamDecoder *unused ATTR_UNUSED,\n\t\tvoid *client_data)\n{\n\tstruct flac_data *data = (struct flac_data *)client_data;\n\n\treturn io_eof (data->stream);\n}\n\nstatic void *flac_open_internal (const char *file, const int buffered)\n{\n\tstruct flac_data *data;\n\n\tdata = (struct flac_data *)xmalloc (sizeof(struct flac_data));\n\tdecoder_error_init (&data->error);\n\n\tdata->decoder = NULL;\n\tdata->bitrate = -1;\n\tdata->avg_bitrate = -1;\n\tdata->abort = 0;\n\tdata->sample_buffer_fill = 0;\n\tdata->last_decode_position = 0;\n\tdata->length = -1;\n\tdata->ok = 0;\n\n\tdata->stream = io_open (file, buffered);\n\tif (!io_ok(data->stream)) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Can't load file: %s\", io_strerror(data->stream));\n\t\treturn data;\n\t}\n\n\tif (!(data->decoder = FLAC__stream_decoder_new())) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"FLAC__stream_decoder_new() failed\");\n\t\treturn data;\n\t}\n\n\tFLAC__stream_decoder_set_md5_checking (data->decoder, false);\n\n\tFLAC__stream_decoder_set_metadata_ignore_all (data->decoder);\n\tFLAC__stream_decoder_set_metadata_respond (data->decoder,\n\t\t\tFLAC__METADATA_TYPE_STREAMINFO);\n\n\tif (FLAC__stream_decoder_init_stream(data->decoder, read_cb, seek_cb, tell_cb, length_cb, eof_cb, write_cb, metadata_cb, error_cb, data)\n\t\t\t!= FLAC__STREAM_DECODER_INIT_STATUS_OK) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"FLAC__stream_decoder_init() failed\");\n\t\treturn data;\n\t}\n\n\tif (!FLAC__stream_decoder_process_until_end_of_metadata(data->decoder)) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"FLAC__stream_decoder_process_until_end_of_metadata() failed.\");\n\t\treturn data;\n\t}\n\n\tdata->ok = 1;\n\n\tif (data->length > 0) {\n\t\toff_t data_size = io_file_size (data->stream);\n\t\tif (data_size > 0) {\n\t\t\tFLAC__uint64 pos;\n\n\t\t\tif (FLAC__stream_decoder_get_decode_position (data->decoder, &pos))\n\t\t\t\tdata_size -= pos;\n\t\t\tdata->avg_bitrate = data_size * 8 / data->length;\n\t\t}\n\t}\n\n\treturn data;\n}\n\nstatic void *flac_open (const char *file)\n{\n\treturn flac_open_internal (file, 1);\n}\n\nstatic void flac_close (void *void_data)\n{\n\tstruct flac_data *data = (struct flac_data *)void_data;\n\n\tif (data->decoder) {\n\t\tFLAC__stream_decoder_finish (data->decoder);\n\t\tFLAC__stream_decoder_delete (data->decoder);\n\t}\n\n\tio_close (data->stream);\n\tdecoder_error_clear (&data->error);\n\tfree (data);\n}\n\nstatic void fill_tag (FLAC__StreamMetadata_VorbisComment_Entry *comm,\n\t\tstruct file_tags *tags)\n{\n\tchar *name, *value;\n\tFLAC__byte *eq;\n\tint value_length;\n\n\teq = memchr (comm->entry, '=', comm->length);\n\tif (!eq)\n\t\treturn;\n\n\tname = (char *)xmalloc (sizeof(char) * (eq - comm->entry + 1));\n\tstrncpy (name, (char *)comm->entry, eq - comm->entry);\n\tname[eq - comm->entry] = 0;\n\tvalue_length = comm->length - (eq - comm->entry + 1);\n\n\tif (value_length == 0) {\n\t\tfree (name);\n\t\treturn;\n\t}\n\n\tvalue = (char *)xmalloc (sizeof(char) * (value_length + 1));\n\tstrncpy (value, (char *)(eq + 1), value_length);\n\tvalue[value_length] = 0;\n\n\tif (!strcasecmp(name, \"title\"))\n\t\ttags->title = value;\n\telse if (!strcasecmp(name, \"artist\"))\n\t\ttags->artist = value;\n\telse if (!strcasecmp(name, \"album\"))\n\t\ttags->album = value;\n\telse if (!strcasecmp(name, \"tracknumber\")\n\t\t\t|| !strcasecmp(name, \"track\")) {\n\t\ttags->track = atoi (value);\n\t\tfree (value);\n\t}\n\telse\n\t\tfree (value);\n\n\tfree (name);\n}\n\nstatic void get_vorbiscomments (const char *filename, struct file_tags *tags)\n{\n\tFLAC__Metadata_SimpleIterator *iterator\n\t\t= FLAC__metadata_simple_iterator_new();\n\tFLAC__bool got_vorbis_comments = false;\n\n\tdebug (\"Reading comments for %s\", filename);\n\n\tif (!iterator) {\n\t\tlogit (\"FLAC__metadata_simple_iterator_new() failed.\");\n\t\treturn;\n\t}\n\n\tif (!FLAC__metadata_simple_iterator_init(iterator, filename, true,\n\t\t\t\ttrue)) {\n\t\tlogit (\"FLAC__metadata_simple_iterator_init failed.\");\n\t\tFLAC__metadata_simple_iterator_delete(iterator);\n\t\treturn;\n\t}\n\n\tdo {\n\t\tif (FLAC__metadata_simple_iterator_get_block_type(iterator)\n\t\t\t\t== FLAC__METADATA_TYPE_VORBIS_COMMENT) {\n\t\t\tFLAC__StreamMetadata *block;\n\n\t\t\tblock = FLAC__metadata_simple_iterator_get_block (\n\t\t\t\t\titerator);\n\t\t\tif (block) {\n\t\t\t\tunsigned int i;\n\t\t\t\tconst FLAC__StreamMetadata_VorbisComment *vc\n\t\t\t\t\t= &block->data.vorbis_comment;\n\n\t\t\t\tfor (i = 0; i < vc->num_comments; i++)\n\t\t\t\t\tfill_tag (&vc->comments[i], tags);\n\n\t\t\t\tFLAC__metadata_object_delete (block);\n\t\t\t\tgot_vorbis_comments = true;\n\t\t\t}\n\t\t}\n\t} while (!got_vorbis_comments\n\t\t\t&& FLAC__metadata_simple_iterator_next(iterator));\n\n\tFLAC__metadata_simple_iterator_delete(iterator);\n}\n\nstatic void flac_info (const char *file_name, struct file_tags *info,\n\t\tconst int tags_sel)\n{\n\tif (tags_sel & TAGS_TIME) {\n\t\tstruct flac_data *data;\n\n\t\tdata = flac_open_internal (file_name, 0);\n\t\tif (data->ok)\n\t\t\tinfo->time = data->length;\n\t\tflac_close (data);\n\t}\n\n\tif (tags_sel & TAGS_COMMENTS)\n\t\tget_vorbiscomments (file_name, info);\n}\n\nstatic int flac_seek (void *void_data, int sec)\n{\n\tstruct flac_data *data = (struct flac_data *)void_data;\n\tFLAC__uint64 target_sample;\n\n\tif ((unsigned int)sec > data->length)\n\t\treturn -1;\n\n\ttarget_sample = (FLAC__uint64)(((double)sec / (double)data->length) *\n\t                               (double)data->total_samples);\n\n\n\tif (FLAC__stream_decoder_seek_absolute(data->decoder, target_sample))\n\t\treturn sec;\n\n\tlogit (\"FLAC__stream_decoder_seek_absolute() failed.\");\n\n\treturn -1;\n}\n\nstatic int flac_decode (void *void_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n\tstruct flac_data *data = (struct flac_data *)void_data;\n\tunsigned int to_copy;\n\tint bytes_per_sample;\n\tFLAC__uint64 decode_position;\n\n\tbytes_per_sample = data->bits_per_sample / 8;\n\n\tswitch (bytes_per_sample) {\n\t\tcase 1:\n\t\t\tsound_params->fmt = SFMT_S8;\n\t\t\tbreak;\n\t\tcase 2:\n\t\t\tsound_params->fmt = SFMT_S16 | SFMT_LE;\n\t\t\tbreak;\n\t\tcase 3:\n\t\t\tsound_params->fmt = SFMT_S32 | SFMT_LE;\n\t\t\tbreak;\n\t}\n\n\tsound_params->rate = data->sample_rate;\n\tsound_params->channels = data->channels;\n\n\tdecoder_error_clear (&data->error);\n\n\tif (!data->sample_buffer_fill) {\n\t\tdebug (\"decoding...\");\n\n\t\tif (FLAC__stream_decoder_get_state(data->decoder) == FLAC__STREAM_DECODER_END_OF_STREAM) {\n\t\t\tlogit (\"EOF\");\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (!FLAC__stream_decoder_process_single(data->decoder)) {\n\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\t\"Read error processing frame.\");\n\t\t\treturn 0;\n\t\t}\n\n\t\t/* Count the bitrate */\n\t\tif(!FLAC__stream_decoder_get_decode_position(data->decoder, &decode_position))\n\t\t\tdecode_position = 0;\n\t\tif (decode_position > data->last_decode_position) {\n\t\t\tint bytes_per_sec = bytes_per_sample * data->sample_rate\n\t\t\t\t* data->channels;\n\n\t\t\tdata->bitrate = (decode_position\n\t\t\t\t- data->last_decode_position) * 8.0\n\t\t\t\t/ (data->sample_buffer_fill\n\t\t\t\t\t\t/ (float)bytes_per_sec)\n\t\t\t\t/ 1000;\n\t\t}\n\n\t\tdata->last_decode_position = decode_position;\n\t}\n\telse\n\t\tdebug (\"Some date remain in the buffer.\");\n\n\tdebug (\"Decoded %d bytes\", data->sample_buffer_fill);\n\n\tto_copy = MIN((unsigned int)buf_len, data->sample_buffer_fill);\n\tmemcpy (buf, data->sample_buffer, to_copy);\n\tmemmove (data->sample_buffer, data->sample_buffer + to_copy,\n\t\t\tdata->sample_buffer_fill - to_copy);\n\tdata->sample_buffer_fill -= to_copy;\n\n\treturn to_copy;\n}\n\nstatic int flac_get_bitrate (void *void_data)\n{\n\tstruct flac_data *data = (struct flac_data *)void_data;\n\n\treturn data->bitrate;\n}\n\nstatic int flac_get_avg_bitrate (void *void_data)\n{\n\tstruct flac_data *data = (struct flac_data *)void_data;\n\n\treturn data->avg_bitrate / 1000;\n}\n\nstatic int flac_get_duration (void *void_data)\n{\n\tint result = -1;\n\tstruct flac_data *data = (struct flac_data *)void_data;\n\n\tif (data->ok)\n\t\tresult = data->length;\n\n\treturn result;\n}\n\nstatic void flac_get_name (const char *unused ATTR_UNUSED, char buf[4])\n{\n\tstrcpy (buf, \"FLC\");\n}\n\nstatic int flac_our_format_ext (const char *ext)\n{\n\treturn !strcasecmp (ext, \"flac\") || !strcasecmp (ext, \"fla\");\n}\n\nstatic int flac_our_format_mime (const char *mime)\n{\n\treturn !strcasecmp (mime, \"audio/flac\") ||\n\t       !strncasecmp (mime, \"audio/flac;\", 11) ||\n\t       !strcasecmp (mime, \"audio/x-flac\") ||\n\t       !strncasecmp (mime, \"audio/x-flac;\", 13);\n}\n\nstatic void flac_get_error (void *prv_data, struct decoder_error *error)\n{\n\tstruct flac_data *data = (struct flac_data *)prv_data;\n\n\tdecoder_error_copy (error, &data->error);\n}\n\nstatic struct decoder flac_decoder = {\n\tDECODER_API_VERSION,\n\tNULL,\n\tNULL,\n\tflac_open,\n\tNULL,\n\tNULL,\n\tflac_close,\n\tflac_decode,\n\tflac_seek,\n\tflac_info,\n\tflac_get_bitrate,\n\tflac_get_duration,\n\tflac_get_error,\n\tflac_our_format_ext,\n\tflac_our_format_mime,\n\tflac_get_name,\n\tNULL,\n\tNULL,\n\tflac_get_avg_bitrate\n};\n\nstruct decoder *plugin_init ()\n{\n\treturn &flac_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/flac/flac.m4",
    "content": "dnl FLAC\n\nAC_ARG_WITH(flac, AS_HELP_STRING([--without-flac],\n                                 [Compile without FLAC support]))\n\nif test \"x$with_flac\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(LIBFLAC, [flac >= 1.1.3],\n\t\t\t  [AC_SUBST(LIBFLAC_LIBS)\n\t\t\t   AC_SUBST(LIBFLAC_CFLAGS)\n\t\t\t   want_flac=\"yes\"\n\t\t\t   DECODER_PLUGINS=\"$DECODER_PLUGINS flac\"],\n\t\t\t  [true])\nfi\n\nAM_CONDITIONAL([BUILD_flac], [test \"$want_flac\"])\nAC_CONFIG_FILES([decoder_plugins/flac/Makefile])\n"
  },
  {
    "path": "decoder_plugins/modplug/Makefile.am",
    "content": "lib_LTLIBRARIES = libmodplug_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibmodplug_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibmodplug_decoder_la_LIBADD = $(modplug_LIBS)\nlibmodplug_decoder_la_CFLAGS = $(modplug_CFLAGS) -I$(top_srcdir)\nlibmodplug_decoder_la_SOURCES = modplug.c\n"
  },
  {
    "path": "decoder_plugins/modplug/modplug.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * libmodplug-plugin Copyright (C) 2006 Hendrik Iben <hiben@tzi.de>\n * Enables MOC to play modules via libmodplug (actually just a wrapper around\n * libmodplug's C-wrapper... :-)).\n *\n * Based on ideas from G\"urkan Seng\"un's modplugplay. A command line\n * interface to the modplugxmms library.\n * Structure of this plugin is an adaption of the libsndfile-plugin from\n * moc.\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <strings.h>\n#include <limits.h>\n#include <assert.h>\n#include <libmodplug/modplug.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"io.h\"\n#include \"decoder.h\"\n#include \"log.h\"\n#include \"files.h\"\n#include \"options.h\"\n\n// Limiting maximum size for loading a module was suggested by Damian.\n// I've never seen such a large module so this should be a safe limit...\n#ifndef MAXMODSIZE\n#define MAXMODSIZE 1024*1024*42\n#endif\n\nModPlug_Settings settings;\n\nstruct modplug_data\n{\n  ModPlugFile *modplugfile;\n  int length;\n  char *filedata;\n  struct decoder_error error;\n};\n\n#if !defined(NDEBUG) && defined(DEBUG)\n// this is needed because debugging in plugin_init gets lost\n// The alternative is to debug settings when opening a file\n// but settings never change so I need a flag to check if it\n// has been done...\nstatic int doDebugSettings=1;\n\nstatic void debugSettings(void)\n{\n  debug(\"\\n\\\nModPlug-Settings:\\n\\\nOversampling : %s\\n\\\nNoiseReduction : %s\\n\\\nReverb : %s\\n\\\nMegaBass : %s\\n\\\nSurround : %s\\n\\\nResamplingMode : %s\\n\\\nChannels : %d\\n\\\nBits : %d\\n\\\nFrequency : %d\\n\\\nReverbDepth : %d\\n\\\nReverbDelay : %d\\n\\\nBassAmount : %d\\n\\\nBassRange : %d\\n\\\nSurroundDepth : %d\\n\\\nSurroundDelay : %d\\n\\\nLoopCount : %d\",\n   (settings.mFlags & MODPLUG_ENABLE_OVERSAMPLING)?\"yes\":\"no\"\n  ,(settings.mFlags & MODPLUG_ENABLE_NOISE_REDUCTION)?\"yes\":\"no\"\n  ,(settings.mFlags & MODPLUG_ENABLE_REVERB)?\"yes\":\"no\"\n  ,(settings.mFlags & MODPLUG_ENABLE_MEGABASS)?\"yes\":\"no\"\n  ,(settings.mFlags & MODPLUG_ENABLE_SURROUND)?\"yes\":\"no\"\n  ,(settings.mResamplingMode == MODPLUG_RESAMPLE_FIR)?\"8-tap fir\":\n    (settings.mResamplingMode == MODPLUG_RESAMPLE_SPLINE)?\"spline\":\n    (settings.mResamplingMode == MODPLUG_RESAMPLE_LINEAR)?\"linear\":\n    (settings.mResamplingMode == MODPLUG_RESAMPLE_NEAREST)?\"nearest\":\"?\"\n  ,settings.mChannels\n  ,settings.mBits\n  ,settings.mFrequency\n  ,settings.mReverbDepth\n  ,settings.mReverbDelay\n  ,settings.mBassAmount\n  ,settings.mBassRange\n  ,settings.mSurroundDepth\n  ,settings.mSurroundDelay\n  ,settings.mLoopCount\n  );\n}\n#endif\n\nstatic struct modplug_data *make_modplug_data(const char *file) {\n  struct modplug_data *data;\n\n  data = (struct modplug_data *)xmalloc (sizeof(struct modplug_data));\n\n  data->modplugfile = NULL;\n  data->filedata = NULL;\n  decoder_error_init (&data->error);\n\n  struct io_stream *s = io_open(file, 0);\n  if(!io_ok(s)) {\n    decoder_error(&data->error, ERROR_FATAL, 0, \"Can't open file: %s\", file);\n    io_close(s);\n    return data;\n  }\n\n  off_t size = io_file_size(s);\n\n  if (!RANGE(1, size, INT_MAX)) {\n    decoder_error(&data->error, ERROR_FATAL, 0,\n                  \"Module size unsuitable for loading: %s\", file);\n    io_close(s);\n    return data;\n  }\n\n//  if(size>MAXMODSIZE) {\n//    io_close(s);\n//    decoder_error(&data->error, ERROR_FATAL, 0, \"Module to big! 42M ain't enough ? (%s)\", file);\n//    return data;\n//  }\n\n  data->filedata = (char *)xmalloc((size_t)size);\n\n  io_read(s, data->filedata, (size_t)size);\n  io_close(s);\n\n  data->modplugfile=ModPlug_Load(data->filedata, (int)size);\n\n  if(data->modplugfile==NULL) {\n    free(data->filedata);\n    decoder_error(&data->error, ERROR_FATAL, 0, \"Can't load module: %s\", file);\n    return data;\n  }\n\n  return data;\n}\n\nstatic void *modplug_open (const char *file)\n{\n// this is not really needed but without it the calls would still be made\n// and thus time gets wasted...\n#if !defined(NDEBUG) && defined(DEBUG)\n  if(doDebugSettings) {\n    doDebugSettings=0;\n    debugSettings();\n  }\n#endif\n  struct modplug_data *data = make_modplug_data(file);\n\n  if(data->modplugfile) {\n    data->length = ModPlug_GetLength(data->modplugfile);\n  }\n\n  if(data->modplugfile) {\n    debug (\"Opened file %s\", file);\n  }\n\n  return data;\n}\n\nstatic void modplug_close (void *void_data)\n{\n  struct modplug_data *data = (struct modplug_data *)void_data;\n\n  if (data->modplugfile) {\n    ModPlug_Unload(data->modplugfile);\n    free(data->filedata);\n  }\n\n  decoder_error_clear (&data->error);\n  free (data);\n}\n\nstatic void modplug_info (const char *file_name, struct file_tags *info,\n\t\tconst int tags_sel)\n{\n  struct modplug_data *data = make_modplug_data(file_name);\n\n  if(data->modplugfile==NULL)\n    return;\n\n  if(tags_sel & TAGS_TIME) {\n    info->time = ModPlug_GetLength(data->modplugfile) / 1000;\n    info->filled |= TAGS_TIME;\n  }\n\n  if(tags_sel & TAGS_COMMENTS) {\n    info->title = xstrdup(ModPlug_GetName(data->modplugfile));\n    info->filled |= TAGS_COMMENTS;\n  }\n\n  modplug_close(data);\n}\n\nstatic int modplug_seek (void *void_data, int sec)\n{\n  struct modplug_data *data = (struct modplug_data *)void_data;\n\n  assert (sec >= 0);\n\n  int ms = sec*1000;\n\n  ms = MIN(ms,data->length);\n\n  ModPlug_Seek(data->modplugfile, ms);\n\n  return ms/1000;\n}\n\nstatic int modplug_decode (void *void_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n  struct modplug_data *data = (struct modplug_data *)void_data;\n\n  sound_params->channels = settings.mChannels;\n  sound_params->rate = settings.mFrequency;\n  sound_params->fmt = ((settings.mBits==16)?SFMT_S16:(settings.mBits==8)?SFMT_S8:SFMT_S32) | SFMT_NE;\n\n  return ModPlug_Read(data->modplugfile, buf, buf_len);\n}\n\nstatic int modplug_get_bitrate (void *unused ATTR_UNUSED)\n{\n  return -1;\n}\n\nstatic int modplug_get_duration (void *void_data)\n{\n  struct modplug_data *data = (struct modplug_data *)void_data;\n  return data->length/1000;\n}\n\nstatic int modplug_our_format_ext(const char *ext)\n{\n  // Do not include non-module formats in this list (even if\n  // ModPlug supports them).  Doing so may cause memory exhaustion\n  // in make_modplug_data().\n  return\n    !strcasecmp (ext, \"NONE\") ||\n    !strcasecmp (ext, \"MOD\") ||\n    !strcasecmp (ext, \"S3M\") ||\n    !strcasecmp (ext, \"XM\") ||\n    !strcasecmp (ext, \"MED\") ||\n    !strcasecmp (ext, \"MTM\") ||\n    !strcasecmp (ext, \"IT\") ||\n    !strcasecmp (ext, \"669\") ||\n    !strcasecmp (ext, \"ULT\") ||\n    !strcasecmp (ext, \"STM\") ||\n    !strcasecmp (ext, \"FAR\") ||\n    !strcasecmp (ext, \"AMF\") ||\n    !strcasecmp (ext, \"AMS\") ||\n    !strcasecmp (ext, \"DSM\") ||\n    !strcasecmp (ext, \"MDL\") ||\n    !strcasecmp (ext, \"OKT\") ||\n    // modplug can do MIDI but not in this form...\n    //!strcasecmp (ext, \"MID\") ||\n    !strcasecmp (ext, \"DMF\") ||\n    !strcasecmp (ext, \"PTM\") ||\n    !strcasecmp (ext, \"DBM\") ||\n    !strcasecmp (ext, \"MT2\") ||\n    !strcasecmp (ext, \"AMF0\") ||\n    !strcasecmp (ext, \"PSM\") ||\n    !strcasecmp (ext, \"J2B\") ||\n    !strcasecmp (ext, \"UMX\");\n}\n\nstatic void modplug_get_error (void *prv_data, struct decoder_error *error)\n{\n  struct modplug_data *data = (struct modplug_data *)prv_data;\n\n  decoder_error_copy (error, &data->error);\n}\n\nstatic struct decoder modplug_decoder =\n{\n  DECODER_API_VERSION,\n  NULL,\n  NULL,\n  modplug_open,\n  NULL,\n  NULL,\n  modplug_close,\n  modplug_decode,\n  modplug_seek,\n  modplug_info,\n  modplug_get_bitrate,\n  modplug_get_duration,\n  modplug_get_error,\n  modplug_our_format_ext,\n  NULL,\n  NULL,\n  NULL,\n  NULL,\n  NULL\n};\n\nstruct decoder *plugin_init ()\n{\n  ModPlug_GetSettings(&settings);\n  settings.mFlags = 0;\n  settings.mFlags |= options_get_bool(\"ModPlug_Oversampling\")\n    ?MODPLUG_ENABLE_OVERSAMPLING:0;\n  settings.mFlags |= options_get_bool(\"ModPlug_NoiseReduction\")\n    ?MODPLUG_ENABLE_NOISE_REDUCTION:0;\n  settings.mFlags |= options_get_bool(\"ModPlug_Reverb\")\n    ?MODPLUG_ENABLE_REVERB:0;\n  settings.mFlags |= options_get_bool(\"ModPlug_MegaBass\")\n    ?MODPLUG_ENABLE_MEGABASS:0;\n  settings.mFlags |= options_get_bool(\"ModPlug_Surround\")\n    ?MODPLUG_ENABLE_SURROUND:0;\n  if(!strcasecmp(options_get_symb(\"ModPlug_ResamplingMode\"), \"FIR\"))\n    settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;\n  if(!strcasecmp(options_get_symb(\"ModPlug_ResamplingMode\"), \"SPLINE\"))\n    settings.mResamplingMode = MODPLUG_RESAMPLE_SPLINE;\n  if(!strcasecmp(options_get_symb(\"ModPlug_ResamplingMode\"), \"LINEAR\"))\n    settings.mResamplingMode = MODPLUG_RESAMPLE_LINEAR;\n  if(!strcasecmp(options_get_symb(\"ModPlug_ResamplingMode\"), \"NEAREST\"))\n    settings.mResamplingMode = MODPLUG_RESAMPLE_NEAREST;\n  settings.mChannels = options_get_int(\"ModPlug_Channels\");\n  settings.mBits = options_get_int(\"ModPlug_Bits\");\n  settings.mFrequency = options_get_int(\"ModPlug_Frequency\");\n  settings.mReverbDepth = options_get_int(\"ModPlug_ReverbDepth\");\n  settings.mReverbDelay = options_get_int(\"ModPlug_ReverbDelay\");\n  settings.mBassAmount = options_get_int(\"ModPlug_BassAmount\");\n  settings.mBassRange = options_get_int(\"ModPlug_BassRange\");\n  settings.mSurroundDepth = options_get_int(\"ModPlug_SurroundDepth\");\n  settings.mSurroundDelay = options_get_int(\"ModPlug_SurroundDelay\");\n  settings.mLoopCount = options_get_int(\"ModPlug_LoopCount\");\n  ModPlug_SetSettings(&settings);\n  return &modplug_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/modplug/modplug.m4",
    "content": "dnl libmodplug\n\nAC_ARG_WITH(modplug, AS_HELP_STRING([--without-modplug],\n                                    [Compile without libmodplug]))\n\nif test \"x$with_modplug\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(modplug, libmodplug >= 0.7,\n\t\t\t   [AC_SUBST(modplug_LIBS)\n\t\t\t   AC_SUBST(modplug_CFLAGS)\n\t\t\t   want_modplug=\"yes\"\n\t\t\t   DECODER_PLUGINS=\"$DECODER_PLUGINS modplug\"],\n\t\t\t   [true])\nfi\n\nAM_CONDITIONAL([BUILD_modplug], [test \"$want_modplug\"])\nAC_CONFIG_FILES([decoder_plugins/modplug/Makefile])\n"
  },
  {
    "path": "decoder_plugins/mp3/Makefile.am",
    "content": "lib_LTLIBRARIES = libmp3_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibmp3_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibmp3_decoder_la_CFLAGS = -I$(top_srcdir)\nlibmp3_decoder_la_LIBADD = -lmad -lid3tag -lz $(RCC_LIBS)\nlibmp3_decoder_la_SOURCES = mp3.c xing.c xing.h\n"
  },
  {
    "path": "decoder_plugins/mp3/mp3.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2002 - 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* This code was based on madlld.c (C) by Bertrand Petit including code\n * from xmms-mad (C) by Sam Clegg and winamp plugin for madlib (C) by\n * Robert Leslie. */\n\n/* FIXME: there can be a bit of silence in mp3 at the end or at the\n * beginning. If you hear gaps between files, it's the file's fault.\n * Can we strip this silence? */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <unistd.h>\n#include <stdlib.h>\n#include <inttypes.h>\n#include <errno.h>\n#include <string.h>\n#include <strings.h>\n#include <mad.h>\n#include <id3tag.h>\n#include <assert.h>\n#ifdef HAVE_ICONV\n# include <iconv.h>\n#endif\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"xing.h\"\n#include \"audio.h\"\n#include \"decoder.h\"\n#include \"io.h\"\n#include \"options.h\"\n#include \"files.h\"\n#include \"utf8.h\"\n#include \"rcc.h\"\n\n#define INPUT_BUFFER\t(32 * 1024)\n\nstatic iconv_t iconv_id3_fix;\n\nstruct mp3_data\n{\n\tstruct io_stream *io_stream;\n\tunsigned long bitrate;\n\tlong avg_bitrate;\n\n\tunsigned int freq;\n\tshort channels;\n\tsigned long duration;\t/* Total time of the file in seconds\n\t                           (used for seeking). */\n\toff_t size;\t\t\t\t/* Size of the file */\n\n\tunsigned char in_buff[INPUT_BUFFER + MAD_BUFFER_GUARD];\n\n\tstruct mad_stream stream;\n\tstruct mad_frame frame;\n\tstruct mad_synth synth;\n\n\tint skip_frames; /* how many frames to skip (after seeking) */\n\n\tint ok; /* was this stream successfully opened? */\n\tstruct decoder_error error;\n};\n\n/* Fill in the mad buffer, return number of bytes read, 0 on eof or error */\nstatic size_t fill_buff (struct mp3_data *data)\n{\n\tsize_t remaining;\n\tssize_t read_size;\n\tunsigned char *read_start;\n\n\tif (data->stream.next_frame != NULL) {\n\t\tremaining = data->stream.bufend - data->stream.next_frame;\n\t\tmemmove (data->in_buff, data->stream.next_frame, remaining);\n\t\tread_start = data->in_buff + remaining;\n\t\tread_size = INPUT_BUFFER - remaining;\n\t}\n\telse {\n\t\tread_start = data->in_buff;\n\t\tread_size = INPUT_BUFFER;\n\t\tremaining = 0;\n\t}\n\n\tread_size = io_read (data->io_stream, read_start, read_size);\n\tif (read_size < 0) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"read error: %s\", io_strerror(data->io_stream));\n\t\treturn 0;\n\t}\n\telse if (read_size == 0)\n\t\treturn 0;\n\n\tif (io_eof (data->io_stream)) {\n\t\tmemset (read_start + read_size, 0, MAD_BUFFER_GUARD);\n\t\tread_size += MAD_BUFFER_GUARD;\n\t}\n\n\tmad_stream_buffer(&data->stream, data->in_buff, read_size + remaining);\n\tdata->stream.error = 0;\n\n\treturn read_size;\n}\n\nstatic char *id3v1_fix (const char *str)\n{\n\tif (iconv_id3_fix != (iconv_t)-1)\n\t\treturn iconv_str (iconv_id3_fix, str);\n\treturn xstrdup (str);\n}\n\nint __unique_frame (struct id3_tag *tag, struct id3_frame *frame)\n{\n    unsigned int i;\n\n    for (i = 0; i < tag->nframes; i++) {\n        if (tag->frames[i] == frame) {\n            break;\n        }\n    }\n\n    for (; i < tag->nframes; i++) {\n        if (strcmp(tag->frames[i]->id, frame->id) == 0) {\n            return 0;\n        }\n    }\n\n    return 1;\n}\n\nstatic char *get_tag (struct id3_tag *tag, const char *what)\n{\n\tstruct id3_frame *frame;\n\tunion id3_field *field;\n\tconst id3_ucs4_t *ucs4;\n\tchar *comm = NULL;\n\n\tframe = id3_tag_findframe (tag, what, 0);\n\tif (frame && (field = &frame->fields[1])) {\n\t\tucs4 = id3_field_getstrings (field, 0);\n\t\tif (ucs4) {\n\t\t\t/* Workaround for ID3 tags v1/v1.1 where the encoding\n\t\t\t * is latin1. */\n\t\t\tunion id3_field *encoding_field = &frame->fields[0];\n\t\t\tif (((id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) &&\n\t\t\t\t\t\t__unique_frame(tag, frame))\n\t\t\t\t\t|| ((options_get_bool (\"EnforceTagsEncoding\") &&\n\t\t\t\t\t\t\t(id3_field_gettextencoding((encoding_field))\n\t\t\t\t\t\t\t == ID3_FIELD_TEXTENCODING_ISO_8859_1))))\n\t\t\t{\n\t\t\t\tchar *t;\n\n\t\t\t\tcomm = (char *)id3_ucs4_latin1duplicate (ucs4);\n\n#ifdef HAVE_RCC\n\t\t\t\tif (options_get_bool(\"UseRCC\"))\n\t\t\t\t\tcomm = rcc_reencode (comm);\n\t\t\t\telse {\n#endif /* HAVE_RCC */\n\t\t\t\t\tt = comm;\n\t\t\t\t\tcomm = id3v1_fix (comm);\n\t\t\t\t\tfree (t);\n#ifdef HAVE_RCC\n\t\t\t\t}\n#endif /* HAVE_RCC */\n\t\t\t}\n\t\t\telse\n\t\t\t\tcomm = (char *)id3_ucs4_utf8duplicate (ucs4);\n\t\t}\n\t}\n\n\treturn comm;\n}\n\nstatic int count_time_internal (struct mp3_data *data)\n{\n\tstruct xing xing;\n\tunsigned long bitrate = 0;\n\tint has_xing = 0;\n\tint is_vbr = 0;\n\tint num_frames = 0;\n\tmad_timer_t duration = mad_timer_zero;\n\tstruct mad_header header;\n\tint good_header = 0; /* Have we decoded any header? */\n\n\tmad_header_init (&header);\n\txing_init (&xing);\n\n\t/* There are three ways of calculating the length of an mp3:\n\t  1) Constant bitrate: One frame can provide the information\n\t\t needed: # of frames and duration. Just see how long it\n\t\t is and do the division.\n\t  2) Variable bitrate: Xing tag. It provides the number of\n\t\t frames. Each frame has the same number of samples, so\n\t\t just use that.\n\t  3) All: Count up the frames and duration of each frame\n\t\t by decoding each one. We do this if we've no other\n\t\t choice, i.e. if it's a VBR file with no Xing tag.\n\t*/\n\n\twhile (1) {\n\n\t\t/* Fill the input buffer if needed */\n\t\tif (data->stream.buffer == NULL ||\n\t\t\tdata->stream.error == MAD_ERROR_BUFLEN) {\n\t\t\tif (!fill_buff(data))\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (mad_header_decode(&header, &data->stream) == -1) {\n\t\t\tif (MAD_RECOVERABLE(data->stream.error))\n\t\t\t\tcontinue;\n\t\t\telse if (data->stream.error == MAD_ERROR_BUFLEN)\n\t\t\t\tcontinue;\n\t\t\telse {\n\t\t\t\tdebug (\"Can't decode header: %s\",\n\t\t\t\t        mad_stream_errorstr(&data->stream));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tgood_header = 1;\n\n\t\t/* Limit xing testing to the first frame header */\n\t\tif (!num_frames++) {\n\t\t\tif (xing_parse(&xing, data->stream.anc_ptr,\n\t\t\t\t\t\tdata->stream.anc_bitlen)\n\t\t\t\t\t!= -1) {\n\t\t\t\tis_vbr = 1;\n\n\t\t\t\tdebug (\"Has XING header\");\n\n\t\t\t\tif (xing.flags & XING_FRAMES) {\n\t\t\t\t\thas_xing = 1;\n\t\t\t\t\tnum_frames = xing.frames;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tdebug (\"XING header doesn't contain number of frames.\");\n\t\t\t}\n\t\t}\n\n\t\t/* Test the first n frames to see if this is a VBR file */\n\t\tif (!is_vbr && !(num_frames > 20)) {\n\t\t\tif (bitrate && header.bitrate != bitrate) {\n\t\t\t\tdebug (\"Detected VBR after %d frames\", num_frames);\n\t\t\t\tis_vbr = 1;\n\t\t\t}\n\t\t\telse\n\t\t\t\tbitrate = header.bitrate;\n\t\t}\n\n\t\t/* We have to assume it's not a VBR file if it hasn't already\n\t\t * been marked as one and we've checked n frames for different\n\t\t * bitrates */\n\t\telse if (!is_vbr) {\n\t\t\tdebug (\"Fixed rate MP3\");\n\t\t\tbreak;\n\t\t}\n\n\t\tmad_timer_add (&duration, header.duration);\n\t}\n\n\tif (!good_header)\n\t\treturn -1;\n\n\tif (data->size == -1) {\n\t\tmad_header_finish(&header);\n\t\treturn -1;\n\t}\n\n\tif (!is_vbr) {\n\t\t/* time in seconds */\n\t\tdouble time = (data->size * 8.0) / (header.bitrate);\n\n\t\tdouble timefrac = (double)time - ((long)(time));\n\n\t\t/* samples per frame */\n\t\tlong nsamples = 32 * MAD_NSBSAMPLES(&header);\n\n\t\t/* samplerate is a constant */\n\t\tnum_frames = (long) (time * header.samplerate / nsamples);\n\n\t\t/* the average bitrate is the constant bitrate */\n\t\tdata->avg_bitrate = bitrate;\n\n\t\tmad_timer_set(&duration, (long)time, (long)(timefrac*100),\n\t\t\t\t100);\n\t}\n\n\telse if (has_xing) {\n\t\tmad_timer_multiply (&header.duration, num_frames);\n\t\tduration = header.duration;\n\t}\n\telse {\n\t\t/* the durations have been added up, and the number of frames\n\t\t   counted. We do nothing here. */\n\t\tdebug (\"Counted duration by counting frames durations in VBR file.\");\n\t}\n\n\tif (data->avg_bitrate == -1\n\t\t\t&& mad_timer_count(duration, MAD_UNITS_SECONDS) > 0) {\n\t\tdata->avg_bitrate = data->size\n\t\t\t\t/ mad_timer_count(duration, MAD_UNITS_SECONDS) * 8;\n\t}\n\n\tmad_header_finish(&header);\n\n\tdebug (\"MP3 time: %ld\", mad_timer_count (duration, MAD_UNITS_SECONDS));\n\n\treturn mad_timer_count (duration, MAD_UNITS_SECONDS);\n}\n\nstatic struct mp3_data *mp3_open_internal (const char *file,\n\t\tconst int buffered)\n{\n\tstruct mp3_data *data;\n\n\tdata = (struct mp3_data *)xmalloc (sizeof(struct mp3_data));\n\tdata->ok = 0;\n\tdecoder_error_init (&data->error);\n\n\t/* Reset information about the file */\n\tdata->freq = 0;\n\tdata->channels = 0;\n\tdata->skip_frames = 0;\n\tdata->bitrate = -1;\n\tdata->avg_bitrate = -1;\n\n\t/* Open the file */\n\tdata->io_stream = io_open (file, buffered);\n\tif (io_ok(data->io_stream)) {\n\t\tdata->ok = 1;\n\n\t\tdata->size = io_file_size (data->io_stream);\n\n\t\tmad_stream_init (&data->stream);\n\t\tmad_frame_init (&data->frame);\n\t\tmad_synth_init (&data->synth);\n\n\t\tif (options_get_bool (\"MP3IgnoreCRCErrors\"))\n\t\t\t\tmad_stream_options (&data->stream,\n\t\t\t\t\tMAD_OPTION_IGNORECRC);\n\n\t\tdata->duration = count_time_internal (data);\n\t\tmad_frame_mute (&data->frame);\n\t\tdata->stream.next_frame = NULL;\n\t\tdata->stream.sync = 0;\n\t\tdata->stream.error = MAD_ERROR_NONE;\n\n\t\tif (io_seek(data->io_stream, 0, SEEK_SET) == -1) {\n\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"seek failed\");\n\t\t\tmad_stream_finish (&data->stream);\n\t\t\tmad_frame_finish (&data->frame);\n\t\t\tmad_synth_finish (&data->synth);\n\t\t\tdata->ok = 0;\n\t\t}\n\n\t\tdata->stream.error = MAD_ERROR_BUFLEN;\n\t}\n\telse {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"Can't open: %s\",\n\t\t\t\tio_strerror(data->io_stream));\n\t}\n\n\treturn data;\n}\n\nstatic void *mp3_open (const char *file)\n{\n\treturn mp3_open_internal (file, 1);\n}\n\nstatic void *mp3_open_stream (struct io_stream *stream)\n{\n\tstruct mp3_data *data;\n\n\tdata = (struct mp3_data *)xmalloc (sizeof(struct mp3_data));\n\tdata->ok = 1;\n\tdecoder_error_init (&data->error);\n\n\t/* Reset information about the file */\n\tdata->freq = 0;\n\tdata->channels = 0;\n\tdata->skip_frames = 0;\n\tdata->bitrate = -1;\n\tdata->io_stream = stream;\n\tdata->duration = -1;\n\tdata->size = -1;\n\n\tmad_stream_init (&data->stream);\n\tmad_frame_init (&data->frame);\n\tmad_synth_init (&data->synth);\n\n\tif (options_get_bool (\"MP3IgnoreCRCErrors\"))\n\t\t\tmad_stream_options (&data->stream,\n\t\t\t\tMAD_OPTION_IGNORECRC);\n\n\treturn data;\n}\n\nstatic void mp3_close (void *void_data)\n{\n\tstruct mp3_data *data = (struct mp3_data *)void_data;\n\n\tif (data->ok) {\n\t\tmad_stream_finish (&data->stream);\n\t\tmad_frame_finish (&data->frame);\n\t\tmad_synth_finish (&data->synth);\n\t}\n\tio_close (data->io_stream);\n\tdecoder_error_clear (&data->error);\n\tfree (data);\n}\n\n/* Get the time for mp3 file, return -1 on error.\n * Adapted from mpg321. */\nstatic int count_time (const char *file)\n{\n\tstruct mp3_data *data;\n\tint time;\n\n\tdebug (\"Processing file %s\", file);\n\n\tdata = mp3_open_internal (file, 0);\n\n\tif (!data->ok)\n\t\ttime = -1;\n\telse\n\t\ttime = data->duration;\n\n\tmp3_close (data);\n\n\treturn time;\n}\n\n/* Fill info structure with data from the id3 tag */\nstatic void mp3_info (const char *file_name, struct file_tags *info,\n\t\tconst int tags_sel)\n{\n\tif (tags_sel & TAGS_COMMENTS) {\n\t\tstruct id3_tag *tag;\n\t\tstruct id3_file *id3file;\n\t\tchar *track = NULL;\n\n\t\tid3file = id3_file_open (file_name, ID3_FILE_MODE_READONLY);\n\t\tif (!id3file)\n\t\t\treturn;\n\t\ttag = id3_file_tag (id3file);\n\t\tif (tag) {\n\t\t\tinfo->artist = get_tag (tag, ID3_FRAME_ARTIST);\n\t\t\tinfo->title = get_tag (tag, ID3_FRAME_TITLE);\n\t\t\tinfo->album = get_tag (tag, ID3_FRAME_ALBUM);\n\t\t\ttrack = get_tag (tag, ID3_FRAME_TRACK);\n\n\t\t\tif (track) {\n\t\t\t\tchar *end;\n\n\t\t\t\tinfo->track = strtol (track, &end, 10);\n\t\t\t\tif (end == track)\n\t\t\t\t\tinfo->track = -1;\n\t\t\t\tfree (track);\n\t\t\t}\n\t\t}\n\t\tid3_file_close (id3file);\n\t}\n\n\tif (tags_sel & TAGS_TIME)\n\t\tinfo->time = count_time (file_name);\n}\n\nstatic inline int32_t round_sample (mad_fixed_t sample)\n{\n\tsample += 1L << (MAD_F_FRACBITS - 24);\n\n\tsample = CLAMP(-MAD_F_ONE, sample, MAD_F_ONE - 1);\n\n\treturn sample >> (MAD_F_FRACBITS + 1 - 24);\n}\n\nstatic int put_output (char *buf, int buf_len, struct mad_pcm *pcm,\n\t\tstruct mad_header *header)\n{\n\tunsigned int nsamples;\n\tmad_fixed_t const *left_ch, *right_ch;\n\tint olen;\n\n\tnsamples = pcm->length;\n\tleft_ch = pcm->samples[0];\n\tright_ch = pcm->samples[1];\n\tolen = nsamples * MAD_NCHANNELS (header) * 4;\n\n\tif (olen > buf_len) {\n\t\tlogit (\"PCM buffer to small!\");\n\t\treturn 0;\n\t}\n\n\twhile (nsamples--) {\n\t\tlong sample0 = round_sample (*left_ch++);\n\n\t\tbuf[0] = 0;\n\t\tbuf[1] = sample0;\n\t\tbuf[2] = sample0 >> 8;\n\t\tbuf[3] = sample0 >> 16;\n\t\tbuf += 4;\n\n\t\tif (MAD_NCHANNELS(header) == 2) {\n\t\t\tlong sample1;\n\n\t\t\tsample1 = round_sample (*right_ch++);\n\n\t\t\tbuf[0] = 0;\n\t\t\tbuf[1] = sample1;\n\t\t\tbuf[2] = sample1 >> 8;\n\t\t\tbuf[3] = sample1 >> 16;\n\n\t\t\tbuf += 4;\n\t\t}\n\t}\n\n\treturn olen;\n}\n\n/* If the current frame in the stream is an ID3 tag, then swallow it. */\nstatic ssize_t flush_id3_tag (struct mp3_data *data)\n{\n\tsize_t remaining;\n\tssize_t tag_size;\n\n\tremaining = data->stream.bufend - data->stream.next_frame;\n\ttag_size = id3_tag_query (data->stream.this_frame, remaining);\n\tif (tag_size > 0) {\n\t\tmad_stream_skip (&data->stream, tag_size);\n\t\tmad_stream_sync (&data->stream);\n\t}\n\n\treturn tag_size;\n}\n\nstatic int mp3_decode (void *void_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n\tstruct mp3_data *data = (struct mp3_data *)void_data;\n\n\tdecoder_error_clear (&data->error);\n\n\twhile (1) {\n\n\t\t/* Fill the input buffer if needed */\n\t\tif (data->stream.buffer == NULL ||\n\t\t\tdata->stream.error == MAD_ERROR_BUFLEN) {\n\t\t\tif (!fill_buff(data))\n\t\t\t\treturn 0;\n\t\t}\n\n\t\tif (mad_frame_decode (&data->frame, &data->stream)) {\n\t\t\tif (flush_id3_tag (data))\n\t\t\t\tcontinue;\n\t\t\telse if (MAD_RECOVERABLE(data->stream.error)) {\n\n\t\t\t\t/* Ignore LOSTSYNC */\n\t\t\t\tif (data->stream.error == MAD_ERROR_LOSTSYNC)\n\t\t\t\t\tcontinue;\n\n\t\t\t\tif (!data->skip_frames)\n\t\t\t\t\tdecoder_error (&data->error, ERROR_STREAM, 0,\n\t\t\t\t\t\t\t\"Broken frame: %s\",\n\t\t\t\t\t\t\tmad_stream_errorstr(&data->stream));\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\telse if (data->stream.error == MAD_ERROR_BUFLEN)\n\t\t\t\tcontinue;\n\t\t\telse {\n\t\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\t\t\"Broken frame: %s\",\n\t\t\t\t\t\tmad_stream_errorstr(&data->stream));\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\tif (data->skip_frames) {\n\t\t\tdata->skip_frames--;\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Sound parameters. */\n\t\tif (!(sound_params->rate = data->frame.header.samplerate)) {\n\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\t\"Broken file: information about the\"\n\t\t\t\t\t\" frequency couldn't be read.\");\n\t\t\treturn 0;\n\t\t}\n\n\t\tsound_params->channels = MAD_NCHANNELS(&data->frame.header);\n\t\tsound_params->fmt = SFMT_S32 | SFMT_LE;\n\n\t\t/* Change of the bitrate? */\n\t\tif (data->frame.header.bitrate != data->bitrate) {\n\t\t\tif ((data->bitrate = data->frame.header.bitrate) == 0) {\n\t\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\t\t\"Broken file: information about the\"\n\t\t\t\t\t\t\" bitrate couldn't be read.\");\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\tmad_synth_frame (&data->synth, &data->frame);\n\t\tmad_stream_sync (&data->stream);\n\n\t\treturn put_output (buf, buf_len, &data->synth.pcm,\n\t\t\t\t&data->frame.header);\n\t}\n}\n\nstatic int mp3_seek (void *void_data, int sec)\n{\n\tstruct mp3_data *data = (struct mp3_data *)void_data;\n\toff_t new_position;\n\n\tassert (sec >= 0);\n\n\tif (data->size == -1)\n\t\treturn -1;\n\n\tif (sec >= data->duration)\n\t\treturn -1;\n\n\tnew_position = ((double) sec /\n\t\t\t(double) data->duration) * data->size;\n\n\tdebug (\"Seeking to %d (byte %\"PRId64\")\", sec, new_position);\n\n\tif (new_position < 0)\n\t\tnew_position = 0;\n\telse if (new_position >= data->size)\n\t\treturn -1;\n\n\tif (io_seek(data->io_stream, new_position, SEEK_SET) == -1) {\n\t\tlogit (\"seek to %\"PRId64\" failed\", new_position);\n\t\treturn -1;\n\t}\n\n\tdata->stream.error = MAD_ERROR_BUFLEN;\n\n\tmad_frame_mute (&data->frame);\n\tmad_synth_mute (&data->synth);\n\n\tdata->stream.sync = 0;\n\tdata->stream.next_frame = NULL;\n\n\tdata->skip_frames = 2;\n\n\treturn sec;\n}\n\nstatic int mp3_get_bitrate (void *void_data)\n{\n\tstruct mp3_data *data = (struct mp3_data *)void_data;\n\n\treturn data->bitrate / 1000;\n}\n\nstatic int mp3_get_avg_bitrate (void *void_data)\n{\n\tstruct mp3_data *data = (struct mp3_data *)void_data;\n\n\treturn data->avg_bitrate / 1000;\n}\n\nstatic int mp3_get_duration (void *void_data)\n{\n\tstruct mp3_data *data = (struct mp3_data *)void_data;\n\n\treturn data->duration;\n}\n\nstatic void mp3_get_name (const char *file, char buf[4])\n{\n\tchar *ext;\n\n\tstrcpy (buf, \"MPx\");\n\n\text = ext_pos (file);\n\tif (ext) {\n\t\tif (!strcasecmp (ext, \"mp3\"))\n\t\t\tstrcpy (buf, \"MP3\");\n\t\telse if (!strcasecmp (ext, \"mp2\"))\n\t\t\tstrcpy (buf, \"MP2\");\n\t\telse if (!strcasecmp (ext, \"mp1\"))\n\t\t\tstrcpy (buf, \"MP1\");\n\t\telse if (!strcasecmp (ext, \"mpga\"))\n\t\t\tstrcpy (buf, \"MPG\");\n\t}\n}\n\nstatic int mp3_our_format_ext (const char *ext)\n{\n\treturn !strcasecmp (ext, \"mp3\")\n\t\t|| !strcasecmp (ext, \"mpga\")\n\t\t|| !strcasecmp (ext, \"mp2\")\n\t\t|| !strcasecmp (ext, \"mp1\");\n}\n\nstatic void mp3_get_error (void *prv_data, struct decoder_error *error)\n{\n\tstruct mp3_data *data = (struct mp3_data *)prv_data;\n\n\tdecoder_error_copy (error, &data->error);\n}\n\nstatic struct io_stream *mp3_get_stream (void *prv_data)\n{\n\tstruct mp3_data *data = (struct mp3_data *)prv_data;\n\n\treturn data->io_stream;\n}\n\nstatic int mp3_our_mime (const char *mime)\n{\n\treturn !strcasecmp (mime, \"audio/mpeg\")\n\t\t|| !strncasecmp (mime, \"audio/mpeg;\", 11);\n}\n\nstatic int mp3_can_decode (struct io_stream *stream)\n{\n\tunsigned char buf[16 * 1024];\n\n\t/* We must use such a sophisticated test, because there are Shoutcast\n\t * servers that can start broadcasting in the middle of a frame, so we\n\t * can't use any fewer bytes for magic values. */\n\tif (io_peek(stream, buf, sizeof(buf)) == sizeof(buf)) {\n\t\tstruct mad_stream stream;\n\t\tstruct mad_header header;\n\t\tint dec_res;\n\n\t\tmad_stream_init (&stream);\n\t\tmad_header_init (&header);\n\n\t\tmad_stream_buffer (&stream, buf, sizeof(buf));\n\t\tstream.error = 0;\n\n\t\twhile ((dec_res = mad_header_decode(&header, &stream)) == -1\n\t\t\t\t&& MAD_RECOVERABLE(stream.error))\n\t\t\t;\n\n\t\treturn dec_res != -1 ? 1 : 0;\n\t}\n\n\treturn 0;\n}\n\nstatic void mp3_init ()\n{\n\ticonv_id3_fix = iconv_open (\"UTF-8\",\n\t\t\toptions_get_str(\"ID3v1TagsEncoding\"));\n\tif (iconv_id3_fix == (iconv_t)(-1))\n\t\tlog_errno (\"iconv_open() failed\", errno);\n}\n\nstatic void mp3_destroy ()\n{\n\tif (iconv_close(iconv_id3_fix) == -1)\n\t\tlog_errno (\"iconv_close() failed\", errno);\n}\n\nstatic struct decoder mp3_decoder = {\n\tDECODER_API_VERSION,\n\tmp3_init,\n\tmp3_destroy,\n\tmp3_open,\n\tmp3_open_stream,\n\tmp3_can_decode,\n\tmp3_close,\n\tmp3_decode,\n\tmp3_seek,\n\tmp3_info,\n\tmp3_get_bitrate,\n\tmp3_get_duration,\n\tmp3_get_error,\n\tmp3_our_format_ext,\n\tmp3_our_mime,\n\tmp3_get_name,\n\tNULL,\n\tmp3_get_stream,\n\tmp3_get_avg_bitrate\n};\n\nstruct decoder *plugin_init ()\n{\n\treturn &mp3_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/mp3/mp3.m4",
    "content": "dnl libmad (mp3)\n\nAC_ARG_WITH(mp3, AS_HELP_STRING([--without-mp3],\n                                [Compile without mp3 support (libmad)]))\n\nif test \"x$with_mp3\" != \"xno\"\nthen\n\tAC_CHECK_LIB(mad, mad_stream_init, [\n\t\tAC_CHECK_HEADER([mad.h], ,)])\n\n\tif test \"$ac_cv_lib_mad_mad_stream_init\" = \"yes\" -a \"$HAVE_ID3TAG\" = \"yes\"\n\tthen\n\t\twant_mp3=\"yes\"\n\t\tDECODER_PLUGINS=\"$DECODER_PLUGINS mp3\"\n\tfi\nfi\n\nAM_CONDITIONAL([BUILD_mp3], [test \"$want_mp3\"])\nAC_CONFIG_FILES([decoder_plugins/mp3/Makefile])\n"
  },
  {
    "path": "decoder_plugins/mp3/xing.c",
    "content": "/*\n * mad - MPEG audio decoder\n * Copyright (C) 2000-2001 Robert Leslie\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n *\n * $Id: xing.c,v 1.5 2002/11/18 16:32:21 daper Exp $\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <mad.h>\n\n#include \"xing.h\"\n\n#define XING_MAGIC\t(('X' << 24) | ('i' << 16) | ('n' << 8) | 'g')\n\n/*\n * NAME:\txing->init()\n * DESCRIPTION:\tinitialize Xing structure\n */\nvoid xing_init(struct xing *xing)\n{\n  xing->flags = 0;\n}\n\n/*\n * NAME:\txing->parse()\n * DESCRIPTION:\tparse a Xing VBR header\n */\nint xing_parse(struct xing *xing, struct mad_bitptr ptr, unsigned int bitlen)\n{\n  if (bitlen < 64 || mad_bit_read(&ptr, 32) != XING_MAGIC)\n    goto fail;\n\n  xing->flags = mad_bit_read(&ptr, 32);\n  bitlen -= 64;\n\n  if (xing->flags & XING_FRAMES) {\n    if (bitlen < 32)\n      goto fail;\n\n    xing->frames = mad_bit_read(&ptr, 32);\n    bitlen -= 32;\n  }\n\n  if (xing->flags & XING_BYTES) {\n    if (bitlen < 32)\n      goto fail;\n\n    xing->bytes = mad_bit_read(&ptr, 32);\n    bitlen -= 32;\n  }\n\n  if (xing->flags & XING_TOC) {\n    int i;\n\n    if (bitlen < 800)\n      goto fail;\n\n    for (i = 0; i < 100; ++i)\n      xing->toc[i] = mad_bit_read(&ptr, 8);\n\n    bitlen -= 800;\n  }\n\n  if (xing->flags & XING_SCALE) {\n    if (bitlen < 32)\n      goto fail;\n\n    xing->scale = mad_bit_read(&ptr, 32);\n    bitlen -= 32;\n  }\n\n  return 0;\n\nfail:\n  xing->flags = 0;\n  return -1;\n}\n"
  },
  {
    "path": "decoder_plugins/mp3/xing.h",
    "content": "/*\n * mad - MPEG audio decoder\n * Copyright (C) 2000-2001 Robert Leslie\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program; if not, write to the Free Software\n * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA\n *\n * $Id: xing.h,v 1.3 2002/10/04 13:38:09 daper Exp $\n */\n#ifndef XING_H\n#define XING_H\n\n#include \"mad.h\"\n\nstruct xing\n{\n  long flags;\t\t\t/* valid fields (see below) */\n  unsigned long frames;\t\t/* total number of frames */\n  unsigned long bytes;\t\t/* total number of bytes */\n  unsigned char toc[100];\t/* 100-point seek table */\n  long scale;\t\t\t/* ?? */\n};\n\nenum\n{\n  XING_FRAMES = 0x00000001L,\n  XING_BYTES  = 0x00000002L,\n  XING_TOC    = 0x00000004L,\n  XING_SCALE  = 0x00000008L\n};\n\nvoid xing_init (struct xing *);\n\n#define xing_finish(xing)\t/* nothing */\n\nint xing_parse (struct xing *, struct mad_bitptr, unsigned int);\n\n#endif\n"
  },
  {
    "path": "decoder_plugins/musepack/Makefile.am",
    "content": "lib_LTLIBRARIES = libmusepack_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibmusepack_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibmusepack_decoder_la_LIBADD = $(MUSEPACK_LIBS) $(TAGLIB_LIBS)\nlibmusepack_decoder_la_CFLAGS = $(MUSEPACK_CFLAGS) $(TAGLIB_CFLAGS) \\\n\t\t\t\t-I$(top_srcdir)\nlibmusepack_decoder_la_SOURCES = musepack.c\n"
  },
  {
    "path": "decoder_plugins/musepack/musepack.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* FIXME: mpc_decoder_decode() can give fixed point values, do we have to\n * handle this case? */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <inttypes.h>\n#include <string.h>\n#include <strings.h>\n#include <stdio.h>\n#include <errno.h>\n#include <assert.h>\n\n#ifdef MPC_IS_OLD_API\n# include <mpcdec/mpcdec.h>\n#else\n# include <mpc/mpcdec.h>\n#endif\n\n#include <tag_c.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"decoder.h\"\n#include \"io.h\"\n#include \"audio.h\"\n\nstruct musepack_data\n{\n\tstruct io_stream *stream;\n#ifdef MPC_IS_OLD_API\n\tmpc_decoder decoder;\n#else\n\tmpc_demux *demux;\n#endif\n\tmpc_reader reader;\n\tmpc_streaminfo info;\n\tint avg_bitrate;\n\tint bitrate;\n\tstruct decoder_error error;\n\tint ok; /* was this stream successfully opened? */\n\tfloat *remain_buf;\n\tsize_t remain_buf_len; /* in samples (sizeof(float)) */\n};\n\n#ifdef MPC_IS_OLD_API\nstatic mpc_int32_t read_cb (void *t, void *buf, mpc_int32_t size)\n#else\nstatic mpc_int32_t read_cb (mpc_reader *t, void *buf, mpc_int32_t size)\n#endif\n{\n#ifdef MPC_IS_OLD_API\n\tstruct musepack_data *data = (struct musepack_data *)t;\n#else\n\tstruct musepack_data *data = t->data;\n#endif\n\tssize_t res;\n\n\tres = io_read (data->stream, buf, size);\n\tif (res < 0) {\n\t\tlogit (\"Read error\");\n\t\tres = 0;\n\t}\n\n\treturn res;\n}\n\n#ifdef MPC_IS_OLD_API\nstatic mpc_bool_t seek_cb (void *t, mpc_int32_t offset)\n#else\nstatic mpc_bool_t seek_cb (mpc_reader *t, mpc_int32_t offset)\n#endif\n{\n#ifdef MPC_IS_OLD_API\n\tstruct musepack_data *data = (struct musepack_data *)t;\n#else\n\tstruct musepack_data *data = t->data;\n#endif\n\n\tdebug (\"Seek request to %\"PRId32, offset);\n\n\treturn io_seek(data->stream, offset, SEEK_SET) >= 0 ? 1 : 0;\n}\n\n#ifdef MPC_IS_OLD_API\nstatic mpc_int32_t tell_cb (void *t)\n#else\nstatic mpc_int32_t tell_cb (mpc_reader *t)\n#endif\n{\n#ifdef MPC_IS_OLD_API\n\tstruct musepack_data *data = (struct musepack_data *)t;\n#else\n\tstruct musepack_data *data = t->data;\n#endif\n\n\tdebug (\"tell callback\");\n\n\treturn (mpc_int32_t)io_tell (data->stream);\n}\n\n#ifdef MPC_IS_OLD_API\nstatic mpc_int32_t get_size_cb (void *t)\n#else\nstatic mpc_int32_t get_size_cb (mpc_reader *t)\n#endif\n{\n#ifdef MPC_IS_OLD_API\n\tstruct musepack_data *data = (struct musepack_data *)t;\n#else\n\tstruct musepack_data *data = t->data;\n#endif\n\n\tdebug (\"size callback\");\n\n\treturn (mpc_int32_t)io_file_size (data->stream);\n}\n\n#ifdef MPC_IS_OLD_API\nstatic mpc_bool_t canseek_cb (void *t)\n#else\nstatic mpc_bool_t canseek_cb (mpc_reader *t)\n#endif\n{\n#ifdef MPC_IS_OLD_API\n\tstruct musepack_data *data = (struct musepack_data *)t;\n#else\n\tstruct musepack_data *data = t->data;\n#endif\n\n\treturn io_seekable (data->stream);\n}\n\nstatic void musepack_open_stream_internal (struct musepack_data *data)\n{\n\tdata->reader.read = read_cb;\n\tdata->reader.seek = seek_cb;\n\tdata->reader.tell = tell_cb;\n\tdata->reader.get_size = get_size_cb;\n\tdata->reader.canseek = canseek_cb;\n\tdata->reader.data = data;\n\n#ifdef MPC_IS_OLD_API\n\tmpc_streaminfo_init (&data->info);\n\n\tif (mpc_streaminfo_read(&data->info, &data->reader) != ERROR_CODE_OK) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"Not a valid MPC file.\");\n\t\treturn;\n\t}\n\n\tmpc_decoder_setup (&data->decoder, &data->reader);\n\n\tif (!mpc_decoder_initialize(&data->decoder, &data->info)) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Can't initialize mpc decoder.\");\n\t\treturn;\n\t}\n#else\n\tdata->demux = mpc_demux_init (&data->reader);\n\tif (!data->demux) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"Not a valid MPC file.\");\n\t\treturn;\n\t}\n\n\tmpc_demux_get_info (data->demux, &data->info);\n#endif\n\n\tdata->avg_bitrate = (int) (data->info.average_bitrate / 1000);\n\tdebug (\"Avg bitrate: %d\", data->avg_bitrate);\n\n\tdata->remain_buf = NULL;\n\tdata->remain_buf_len = 0;\n\tdata->bitrate = 0;\n\tdata->ok = 1;\n}\n\nstatic void *musepack_open (const char *file)\n{\n\tstruct musepack_data *data;\n\n\tdata = (struct musepack_data *)xmalloc (sizeof(struct musepack_data));\n\tdata->ok = 0;\n\tdecoder_error_init (&data->error);\n\n\tdata->stream = io_open (file, 1);\n\tif (!io_ok(data->stream)) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Can't open file: %s\", io_strerror(data->stream));\n\t\treturn data;\n\t}\n\n\t/* This a restriction placed on us by the Musepack API. */\n\tif (io_file_size (data->stream) > INT32_MAX) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"File too large!\");\n\t\treturn data;\n\t}\n\n\tmusepack_open_stream_internal (data);\n\n\treturn data;\n}\n\nstatic void *musepack_open_stream (struct io_stream *stream)\n{\n\tstruct musepack_data *data;\n\n\tdata = (struct musepack_data *)xmalloc (sizeof(struct musepack_data));\n\tdata->ok = 0;\n\n\tdecoder_error_init (&data->error);\n\tdata->stream = stream;\n\tmusepack_open_stream_internal (data);\n\n\treturn data;\n}\n\nstatic void musepack_close (void *prv_data)\n{\n\tstruct musepack_data *data = (struct musepack_data *)prv_data;\n\n\tif (data->ok) {\n#ifndef MPC_IS_OLD_API\n\t\tmpc_demux_exit (data->demux);\n#endif\n\t\tif (data->remain_buf)\n\t\t\tfree (data->remain_buf);\n\t}\n\n\tio_close (data->stream);\n\tdecoder_error_clear (&data->error);\n\tfree (data);\n}\n\nstatic char *tag_str (const char *str)\n{\n\treturn str && str[0] ? xstrdup(str) : NULL;\n}\n\n/* Fill info structure with data from musepack comments */\nstatic void musepack_info (const char *file_name, struct file_tags *info,\n\t\tconst int tags_sel)\n{\n\tif (tags_sel & TAGS_COMMENTS) {\n\t\tTagLib_File *tf;\n\n\t\ttf = taglib_file_new_type (file_name, TagLib_File_MPC);\n\t\tif (tf) {\n\t\t\tTagLib_Tag *tt;\n\n\t\t\ttt = taglib_file_tag (tf);\n\n\t\t\tif (tt) {\n\t\t\t\tinfo->title = tag_str (taglib_tag_title(tt));\n\t\t\t\tinfo->artist = tag_str (taglib_tag_artist(tt));\n\t\t\t\tinfo->album = tag_str (taglib_tag_album(tt));\n\t\t\t\tinfo->track = taglib_tag_track(tt);\n\n\t\t\t\tif (info->track == 0)\n\t\t\t\t\tinfo->track = -1;\n\t\t\t}\n\n\t\t\ttaglib_file_free (tf);\n\t\t\ttaglib_tag_free_strings ();\n\t\t}\n\t\telse\n\t\t\tlogit (\"taglib_file_new_type() failed.\");\n\t}\n\n\tif (tags_sel & TAGS_TIME) {\n\t\tstruct musepack_data *data = musepack_open (file_name);\n\n\t\tif (data->error.type == ERROR_OK)\n\t\t\tinfo->time = mpc_streaminfo_get_length (&data->info);\n\n\t\tmusepack_close (data);\n\t}\n}\n\nstatic int musepack_seek (void *prv_data, int sec)\n{\n\tstruct musepack_data *data = (struct musepack_data *)prv_data;\n\tint res;\n\n\tassert (sec >= 0);\n\n#ifdef MPC_IS_OLD_API\n\tres = mpc_decoder_seek_seconds (&data->decoder, sec) ? sec : -1;\n#else\n\tmpc_status status;\n\tstatus = mpc_demux_seek_second (data->demux, sec);\n\tif (status == MPC_STATUS_OK)\n\t\tres = sec;\n\telse\n\t\tres = -1;\n#endif\n\n\tif (res != -1 && data->remain_buf) {\n\t\tfree (data->remain_buf);\n\t\tdata->remain_buf = NULL;\n\t\tdata->remain_buf_len = 0;\n\t}\n\n\treturn res;\n}\n\nstatic int musepack_decode (void *prv_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n\tstruct musepack_data *data = (struct musepack_data *)prv_data;\n\tint decoded;\n\tint bytes_from_decoder;\n#ifndef MPC_IS_OLD_API\n\tmpc_frame_info frame;\n\tmpc_status err;\n#else\n\tint ret;\n\tmpc_uint32_t vbrAcc = 0;\n\tmpc_uint32_t vbrUpd = 0;\n#endif\n\tfloat decode_buf[MPC_DECODER_BUFFER_LENGTH];\n\tif (data->remain_buf) {\n\t\tsize_t to_copy = MIN((unsigned int)buf_len,\n\t\t\t\tdata->remain_buf_len * sizeof(float));\n\n\t\tdebug (\"Copying %zu bytes from the remain buf\", to_copy);\n\n\t\tmemcpy (buf, data->remain_buf, to_copy);\n\t\tif (to_copy / sizeof(float) < data->remain_buf_len) {\n\t\t\tmemmove (data->remain_buf, data->remain_buf + to_copy,\n\t\t\t\t\tdata->remain_buf_len * sizeof(float)\n\t\t\t\t\t- to_copy);\n\t\t\tdata->remain_buf_len -= to_copy / sizeof(float);\n\t\t}\n\t\telse {\n\t\t\tdebug (\"Remain buf is now empty\");\n\t\t\tfree (data->remain_buf);\n\t\t\tdata->remain_buf = NULL;\n\t\t\tdata->remain_buf_len = 0;\n\t\t}\n\n\t\treturn to_copy;\n\t}\n\n#ifdef MPC_IS_OLD_API\n\tret = mpc_decoder_decode (&data->decoder, decode_buf, &vbrAcc, &vbrUpd);\n\tif (ret == 0) {\n\t\tdebug (\"EOF\");\n\t\treturn 0;\n\t}\n\n\tif (ret < 0) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"Error in the stream!\");\n\t\treturn 0;\n\t}\n\n\tbytes_from_decoder = ret * sizeof(float) * 2; /* stereo */\n\tdata->bitrate = vbrUpd * sound_params->rate / 1152 / 1000;\n#else\n\tdo {\n\t\tframe.buffer = decode_buf;\n\t\terr = mpc_demux_decode (data->demux, &frame);\n\n\t\tif (err == MPC_STATUS_OK && frame.bits == -1) {\n\t\t\tdebug (\"EOF\");\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (err == MPC_STATUS_OK)\n\t\t\tcontinue;\n\n\t\tif (frame.bits == -1) {\n\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t               \"Error in the stream!\");\n\t\t\treturn 0;\n\t\t}\n\n\t\tdecoder_error (&data->error, ERROR_STREAM, 0, \"Broken frame.\");\n\t} while (err != MPC_STATUS_OK || frame.samples == 0);\n\n\tmpc_demux_get_info (data->demux, &data->info);\n\tbytes_from_decoder = frame.samples * sizeof(MPC_SAMPLE_FORMAT) * data->info.channels;\n\tdata->bitrate = data->info.bitrate;\n#endif\n\n\tdecoder_error_clear (&data->error);\n\tsound_params->channels = data->info.channels;\n\tsound_params->rate = data->info.sample_freq;\n\tsound_params->fmt = SFMT_FLOAT;\n\n\tif (bytes_from_decoder >= buf_len) {\n\t\tsize_t to_copy = MIN (buf_len, bytes_from_decoder);\n\n\t\tdebug (\"Copying %zu bytes\", to_copy);\n\n\t\tmemcpy (buf, decode_buf, to_copy);\n\t\tdata->remain_buf_len = (bytes_from_decoder - to_copy)\n\t\t\t/ sizeof(float);\n\t\tdata->remain_buf = (float *)xmalloc (data->remain_buf_len *\n\t\t\t\tsizeof(float));\n\t\tmemcpy (data->remain_buf, decode_buf + to_copy,\n\t\t\t\tdata->remain_buf_len * sizeof(float));\n\t\tdecoded = to_copy;\n\t}\n\telse {\n\t\tdebug (\"Copying whole decoded sound (%d bytes)\", bytes_from_decoder);\n\t\tmemcpy (buf, decode_buf, bytes_from_decoder);\n\t\tdecoded = bytes_from_decoder;\n\t}\n\n\treturn decoded;\n}\n\nstatic int musepack_get_bitrate (void *prv_data)\n{\n\tstruct musepack_data *data = (struct musepack_data *)prv_data;\n\n\treturn data->bitrate;\n}\n\nstatic int musepack_get_avg_bitrate (void *prv_data)\n{\n\tstruct musepack_data *data = (struct musepack_data *)prv_data;\n\n\treturn data->avg_bitrate;\n}\n\nstatic int musepack_get_duration (void *prv_data)\n{\n\tstruct musepack_data *data = (struct musepack_data *)prv_data;\n\n\treturn mpc_streaminfo_get_length (&data->info);\n}\n\nstatic struct io_stream *musepack_get_stream (void *prv_data)\n{\n\tstruct musepack_data *data = (struct musepack_data *)prv_data;\n\n\treturn data->stream;\n}\n\nstatic void musepack_get_name (const char *unused ATTR_UNUSED, char buf[4])\n{\n\tstrcpy (buf, \"MPC\");\n}\n\nstatic int musepack_our_format_ext (const char *ext)\n{\n\treturn !strcasecmp (ext, \"mpc\");\n}\n\nstatic void musepack_get_error (void *prv_data, struct decoder_error *error)\n{\n\tstruct musepack_data *data = (struct musepack_data *)prv_data;\n\n\tdecoder_error_copy (error, &data->error);\n}\n\nstatic struct decoder musepack_decoder = {\n\tDECODER_API_VERSION,\n\tNULL,\n\tNULL,\n\tmusepack_open,\n\tmusepack_open_stream,\n\tNULL /* musepack_can_decode */,\n\tmusepack_close,\n\tmusepack_decode,\n\tmusepack_seek,\n\tmusepack_info,\n\tmusepack_get_bitrate,\n\tmusepack_get_duration,\n\tmusepack_get_error,\n\tmusepack_our_format_ext,\n\tNULL /*musepack_our_mime*/,\n\tmusepack_get_name,\n\tNULL /* musepack_current_tags */,\n\tmusepack_get_stream,\n\tmusepack_get_avg_bitrate\n};\n\nstruct decoder *plugin_init ()\n{\n\treturn &musepack_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/musepack/musepack.m4",
    "content": "dnl libmpcdec\n\nAC_ARG_WITH(musepack, AS_HELP_STRING([--without-musepack],\n                                     [Compile without musepack (mpc) support]))\n\nif test \"x$with_musepack\" != \"xno\"\nthen\n\tdnl taken from gstreamer\n\tAC_CHECK_HEADER([mpc/mpcdec.h],\n\t\t[have_musepack=\"yes\"],\n\t\t[AC_CHECK_HEADER([mpcdec/mpcdec.h],\n\t\t\t[have_musepack=\"yes\"\n\t\t\t UPGRADE_MUSEPACK=\"yes\"\n\t\t\t AC_DEFINE(MPC_IS_OLD_API, 1, [Define if the old MusePack API is used])],\n\t\t\t[have_musepack=\"no\"])])\n\n\tif test \"x$have_musepack\" = \"xyes\"\n\tthen\n\n\t\tMUSEPACK_LIBS=\"-lmpcdec\"\n\t\tAC_SUBST([MUSEPACK_LIBS])\n\n\t\tdnl taglib\n\t\tAC_CHECK_PROG([TAGLIB_CONFIG], [taglib-config], [yes])\n\t\tif test \"x$TAGLIB_CONFIG\" = \"xyes\"\n\t\tthen\n\t\t\tAC_MSG_CHECKING([taglib version])\n\t\t\ttaglib_ver=`taglib-config --version`\n\t\t\tAX_COMPARE_VERSION($taglib_ver, [ge], [1.3.1])\n\t\t\tif test \"x$ax_compare_version\" = \"xtrue\"\n\t\t\tthen\n\t\t\t\tAC_MSG_RESULT([$taglib_ver, OK])\n\n\t\t\t\tTAGLIB_CFLAGS=\"`taglib-config --cflags`\"\n\t\t\t\tdnl TAGLIB_LIBS=\"`taglib-config --libs`\"\n\t\t\t\tTAGLIB_LIBS=\"-ltag_c\"\n\t\t\t\tAC_SUBST([TAGLIB_CFLAGS])\n\t\t\t\tAC_SUBST([TAGLIB_LIBS])\n\n\t\t\t\tdnl check for tag_c.h\n\t\t\t\told_cflags=\"$CFLAGS\"\n\t\t\t\told_cppflags=\"$CPPFLAGS\"\n\t\t\t\tCFLAGS=\"$CFLAGS $TAGLIB_CFLAGS\"\n\t\t\t\tCPPFLAGS=\"$CPPFLAGS $TAGLIB_CFLAGS\"\n\t\t\t\tAC_CHECK_HEADER([tag_c.h], [\n\t\t\t\t\t\t want_musepack=\"yes\"\n\t\t\t\t\t\t DECODER_PLUGINS=\"$DECODER_PLUGINS musepack\"\n\t\t\t\t\t\t ])\n\t\t\t\tCFLAGS=\"$old_cflags\"\n\t\t\t\tCPPFLAGS=\"$old_cppflags\"\n\n\t\t\t\tAX_COMPARE_VERSION($taglib_ver, [lt], [1.5])\n\t\t\t\tif test \"x$ax_compare_version\" = \"xtrue\"\n\t\t\t\tthen\n\t\t\t\t\tUPGRADE_TAGLIB=\"yes\"\n\t\t\t\tfi\n\t\t\telse\n\t\t\t\tAC_MSG_RESULT([$taglib_ver, but minimum is 1.3.1 - required for musepack])\n\t\t\tfi\n\t\tfi\n\tfi\nfi\n\nAM_CONDITIONAL([BUILD_musepack], [test \"$want_musepack\"])\nAC_CONFIG_FILES([decoder_plugins/musepack/Makefile])\n"
  },
  {
    "path": "decoder_plugins/sidplay2/Makefile.am",
    "content": "lib_LTLIBRARIES = libsidplay2_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibsidplay2_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@ $(sidplay2_LDFLAGS)\nlibsidplay2_decoder_la_LIBADD = $(sidplay2_LIBS) $(sidutils_LIBS)\nlibsidplay2_decoder_la_CFLAGS = $(sidplay2_CFLAGS) $(sidutils_CFLAGS) -I$(top_srcdir)\nlibsidplay2_decoder_la_CXXFLAGS = $(sidplay2_CFLAGS) $(sidutils_CFLAGS) -I$(top_srcdir)\nlibsidplay2_decoder_la_SOURCES = sidplay2.cc sidplay2.h\n"
  },
  {
    "path": "decoder_plugins/sidplay2/sidplay2.cc",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * libsidplay2-plugin Copyright (C) 2007 Hendrik Iben <hiben@tzi.de>\n * Enables MOC to play sids via libsidplay2/libsidutils.\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <pthread.h>\n#include <assert.h>\n#include <string.h>\n#include <strings.h>\n\n#include \"common.h\"\n#include \"sidplay2.h\"\n#include \"log.h\"\n#include \"options.h\"\n\nstatic SID_EXTERN::sidplay2 *players [POOL_SIZE];\n\nstatic ReSIDBuilder *builders [POOL_SIZE];\n\nstatic int playerIndex;\n\nstatic SID_EXTERN::SidDatabase *database;\n\nstatic int init_db;\n\nstatic pthread_mutex_t db_mtx, player_select_mtx;\n\nstatic int defaultLength;\n\nstatic int minLength;\n\nstatic bool startAtStart;\n\nstatic bool playSubTunes;\n\nstatic sidplay2_data * make_data()\n{\n  pthread_mutex_lock(&player_select_mtx);\n\n  playerIndex = (playerIndex+1)%POOL_SIZE;\n\n  struct sidplay2_data *s2d = (struct sidplay2_data *)xmalloc(sizeof(sidplay2_data));\n\n  if(players[playerIndex]==NULL)\n    players[playerIndex] = new SID_EXTERN::sidplay2();\n\n  s2d->player = players[playerIndex];\n\n  s2d->cfg = s2d->player->config();\n\n  s2d->cfg.frequency = options_get_int(OPT_FREQ);\n\n  s2d->cfg.precision = options_get_int(OPT_PREC);\n\n  s2d->cfg.optimisation = options_get_int(OPT_OPTI);\n\n  switch(options_get_symb(OPT_PMODE)[0])\n  {\n    case 'M':\n      s2d->cfg.playback = sid2_mono;\n      break;\n    case 'S':\n      s2d->cfg.playback = sid2_stereo;\n      break;\n    case 'L':\n      s2d->cfg.playback = sid2_left;\n      break;\n    case 'R':\n      s2d->cfg.playback = sid2_right;\n      break;\n    default :\n      s2d->cfg.playback = sid2_mono;\n  }\n\n  s2d->player->config(s2d->cfg);\n\n  s2d->cfg = s2d->player->config();\n\n  if(builders[playerIndex]==NULL)\n    builders[playerIndex] = new ReSIDBuilder( RESID_ID );\n\n  s2d->builder = builders[playerIndex];\n\n  pthread_mutex_unlock(&player_select_mtx);\n\n  if(!(*s2d->builder))\n    fatal(\"sidplay2: Cannot create ReSID-Builder!\");\n\n  s2d->builder->create(s2d->player->info().maxsids);\n\n  s2d->builder->sampling(s2d->cfg.frequency);\n\n  s2d->cfg.sidEmulation = s2d->builder;\n\n  s2d->player->config(s2d->cfg);\n\n  s2d->cfg = s2d->player->config();\n\n  s2d->channels = s2d->player->info().channels;\n\n  s2d->frequency = s2d->cfg.frequency;\n\n#ifdef WORDS_BIGENDIAN\n  s2d->cfg.sampleFormat = SID2_BIG_SIGNED;\n#else\n  s2d->cfg.sampleFormat = SID2_LITTLE_SIGNED;\n#endif\n\n  s2d->player->config(s2d->cfg);\n\n  s2d->cfg = s2d->player->config();\n\n  switch(s2d->cfg.sampleFormat)\n  {\n    case SID2_LITTLE_SIGNED:\n      switch(s2d->cfg.precision)\n      {\n        case 8:\n          s2d->sample_format = SFMT_S8 | SFMT_LE;\n          break;\n        case 16:\n          s2d->sample_format = SFMT_S16 | SFMT_LE;\n          break;\n        case 32:\n          s2d->sample_format = SFMT_S32 | SFMT_LE;\n          break;\n        default:\n          fatal(\"sidplay2: Unsupported precision: %i\", s2d->cfg.precision);\n      }\n      break;\n    case SID2_LITTLE_UNSIGNED:\n      switch(s2d->cfg.precision)\n      {\n        case 8:\n          s2d->sample_format = SFMT_U8 | SFMT_LE;\n          break;\n        case 16:\n          s2d->sample_format = SFMT_U16 | SFMT_LE;\n          break;\n        case 32:\n          s2d->sample_format = SFMT_U32 | SFMT_LE;\n          break;\n        default:\n          fatal(\"sidplay2: Unsupported precision: %i\", s2d->cfg.precision);\n      }\n      break;\n    case SID2_BIG_SIGNED:\n      switch(s2d->cfg.precision)\n      {\n        case 8:\n          s2d->sample_format = SFMT_S8 | SFMT_BE;\n          break;\n        case 16:\n          s2d->sample_format = SFMT_S16 | SFMT_BE;\n          break;\n        case 32:\n          s2d->sample_format = SFMT_S32 | SFMT_BE;\n          break;\n        default:\n          fatal(\"sidplay2: Unsupported precision: %i\", s2d->cfg.precision);\n      }\n      break;\n    case SID2_BIG_UNSIGNED:\n      switch(s2d->cfg.precision)\n      {\n        case 8:\n          s2d->sample_format = SFMT_U8 | SFMT_BE;\n          break;\n        case 16:\n          s2d->sample_format = SFMT_U16 | SFMT_BE;\n          break;\n        case 32:\n          s2d->sample_format = SFMT_U32 | SFMT_BE;\n          break;\n        default:\n          fatal(\"sidplay2: Unsupported precision: %i\", s2d->cfg.precision);\n      }\n      break;\n    default:\n      fatal(\"sidplay2: Unknown Audio-Format!\");\n  }\n\n  return s2d;\n}\n\nstatic void init_database()\n{\n  int cancel = 0;\n\n  pthread_mutex_lock(&db_mtx);\n\n  if(init_db==0)\n    cancel = 1;\n\n  init_db = 0;\n\n  pthread_mutex_unlock(&db_mtx);\n\n  if(cancel)\n    return;\n\n  char * dbfile = options_get_str(OPT_DATABASE);\n\n  if(dbfile!=NULL && dbfile[0]!='\\0')\n  {\n    database = new SidDatabase();\n\n    if(database->open(dbfile)<0)\n    {\n      logit(\"Unable to open SidDatabase %s\", dbfile);\n      database = NULL;\n    }\n  }\n}\n\nextern \"C\" void *sidplay2_open(const char *file)\n{\n  if(init_db)\n    init_database();\n\n  struct sidplay2_data *s2d = make_data();\n\n  decoder_error_init(&s2d->error);\n  s2d->tune=NULL;\n  s2d->sublengths = NULL;\n  s2d->length = 0;\n\n  SidTuneMod *st = new SidTuneMod(file);\n\n  if(!(*st))\n  {\n    decoder_error(&s2d->error, ERROR_FATAL, 0, \"Unable to open %s...\", file);\n    delete st;\n    return s2d;\n  }\n\n  s2d->songs = st->getInfo().songs;\n\n  s2d->sublengths = new int [s2d->songs];\n\n  s2d->startSong = st->getInfo().startSong;\n\n  s2d->timeStart = 1;\n\n  s2d->timeEnd = s2d->songs;\n\n  if(startAtStart)\n    s2d->timeStart = s2d->startSong;\n\n  if(!playSubTunes)\n    s2d->timeEnd = s2d->timeStart;\n\n  for(int s=s2d->timeStart; s <= s2d->timeEnd; s++)\n  {\n    st->selectSong(s);\n\n    if(!(*st))\n    {\n      decoder_error(&s2d->error, ERROR_FATAL, 0,\n                                 \"Error determining length of %s\", file);\n      delete st;\n      return s2d;\n    }\n\n    if(database!=NULL)\n    {\n      int dl = database->length(*st);\n\n      if(dl<1)\n        dl = defaultLength;\n\n      if(dl<minLength)\n        dl = minLength;\n\n      s2d->length += dl;\n      s2d->sublengths[s-1] = dl;\n    }\n    else\n    {\n      s2d->length += defaultLength;\n      s2d->sublengths[s-1] = defaultLength;\n    }\n  }\n\n  // this should not happen normally...\n  if(s2d->length==0)\n    s2d->length = defaultLength;\n\n  s2d->currentSong = s2d->timeStart;\n\n  st->selectSong(s2d->currentSong);\n\n  if(!(*st))\n  {\n    decoder_error(&s2d->error, ERROR_FATAL, 0,\n                  \"Cannot select first song in %s\", file);\n    delete st;\n    return s2d;\n  }\n\n  s2d->tune = st;\n\n  s2d->player->load(st);\n\n  if(!(*s2d->player))\n  {\n    decoder_error(&s2d->error, ERROR_FATAL, 0, \"%s\", s2d->player->error());\n  }\n\n  s2d->player->fastForward(100);\n\n  return ((void *)s2d);\n}\n\nextern \"C\" void sidplay2_close(void *void_data)\n{\n  struct sidplay2_data *data = (struct sidplay2_data *)void_data;\n\n  if(data->player!=NULL)\n     data->player->load(NULL);\n\n  if(data->tune!=NULL)\n    delete data->tune;\n\n  if(data->sublengths!=NULL)\n    delete data->sublengths;\n\n  decoder_error_clear (&data->error);\n  free(data);\n}\n\nextern \"C\" void sidplay2_get_error (void *prv_data, struct decoder_error *error)\n{\n  struct sidplay2_data *data = (struct sidplay2_data *)prv_data;\n\n  decoder_error_copy (error, &data->error);\n}\n\nextern \"C\" void sidplay2_info (const char *file_name, struct file_tags *info,\n\t\tconst int)\n{\n  if(init_db)\n    init_database();\n\n  SidTuneMod *st = new SidTuneMod(file_name);\n\n  if(!(*st))\n  {\n    delete st;\n    return;\n  }\n\n  const SidTuneInfo sti = st->getInfo();\n\n  if\n  (\n    sti.numberOfInfoStrings>0\n    && sti.infoString[STITLE]!=NULL\n    && strlen(sti.infoString[STITLE])>0\n  )\n  {\n    info->title = trim(sti.infoString[STITLE],strlen(sti.infoString[STITLE]));\n    if (info->title)\n      info->filled |= TAGS_COMMENTS;\n  }\n\n  if\n  (\n    sti.numberOfInfoStrings>1\n    && sti.infoString[SAUTHOR]!=NULL\n    && strlen(sti.infoString[SAUTHOR])>0\n  )\n  {\n    info->artist = trim(sti.infoString[SAUTHOR],strlen(sti.infoString[SAUTHOR]));\n    if (info->artist)\n      info->filled |= TAGS_COMMENTS;\n  }\n\n  // Not really album - but close...\n  if\n  (\n    sti.numberOfInfoStrings>2\n    && sti.infoString[SCOPY]!=NULL\n    && strlen(sti.infoString[SCOPY])>0\n  )\n  {\n    info->album = trim(sti.infoString[SCOPY],strlen(sti.infoString[SCOPY]));\n    if (info->album)\n      info->filled |= TAGS_COMMENTS;\n  }\n\n  info->time = 0;\n\n  int songs = st->getInfo().songs;\n\n  int countStart = 1;\n\n  int countEnd = songs;\n\n  if(startAtStart)\n    countStart = st->getInfo().startSong;\n\n  if(!playSubTunes)\n    countEnd = countStart;\n\n  for(int s=countStart; s <= countEnd; s++)\n  {\n    st->selectSong(s);\n\n    if(database!=NULL)\n    {\n      int dl = database->length(*st);\n\n      if(dl<1)\n        dl = defaultLength;\n\n      if(dl<minLength)\n        dl = minLength;\n\n      info->time += dl;\n    }\n    else\n    {\n     info->time += defaultLength;\n    }\n  }\n\n  info->filled |= TAGS_TIME;\n\n  delete st;\n}\n\n/* Seeking is not reliable because I don't know how to keep track of the\n * difference between the time which MOC is currently playing and how much\n * time has been precached... :-|\n *\n * Generic seeking can't be done because the whole audio would have to be\n * replayed until the position is reached (which would introduce a delay).\n * */\nextern \"C\" int sidplay2_seek (void *, int)\n{\n  return -1;\n}\n\nextern \"C\" int sidplay2_decode (void *void_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n  struct sidplay2_data *data = (struct sidplay2_data *)void_data;\n\n  int seconds = data->player->time() / data->player->timebase();\n\n  int currentLength = data->sublengths[data->currentSong-1];\n\n  if(seconds >= currentLength)\n  {\n    if(data->currentSong >= data->timeEnd)\n      return 0;\n\n    data->player->stop();\n    data->currentSong++;\n    data->tune->selectSong(data->currentSong);\n    data->player->load(data->tune);\n\n    currentLength = data->sublengths[data->currentSong-1];\n    seconds = 0;\n  }\n\n  sound_params->channels = data->channels;\n  sound_params->rate = data->frequency;\n  sound_params->fmt = data->sample_format;\n\n  return data->player->play((void *)buf, buf_len);\n}\n\nextern \"C\" int sidplay2_get_bitrate (void *)\n{\n  return -1;\n}\n\nextern \"C\" int sidplay2_get_duration (void *void_data)\n{\n  struct sidplay2_data *data = (struct sidplay2_data *)void_data;\n\n  return data->length;\n}\n\nextern \"C\" int sidplay2_our_format_ext(const char *ext)\n{\n  return\n    !strcasecmp (ext, \"SID\") ||\n    !strcasecmp (ext, \"MUS\");\n}\n\nextern \"C\" void init()\n{\n  defaultLength = options_get_int(OPT_DEFLEN);\n\n  minLength = options_get_int(OPT_MINLEN);\n\n  startAtStart = options_get_bool(OPT_START);\n\n  playSubTunes = options_get_bool(OPT_SUBTUNES);\n\n  database = NULL;\n  init_db = 1;\n\n  playerIndex = POOL_SIZE-1; /* turns to 0 at first use */\n}\n\nextern \"C\" void destroy()\n{\n  pthread_mutex_destroy(&db_mtx);\n\n  pthread_mutex_destroy(&player_select_mtx);\n\n  if(database!=NULL)\n    delete database;\n\n  for(int i=0; i < POOL_SIZE; i++)\n  {\n    if(players[i]!=NULL)\n      delete players[i];\n    if(builders[i]!=NULL)\n      delete builders[i];\n  }\n}\n\nstatic struct decoder sidplay2_decoder =\n{\n  DECODER_API_VERSION,\n  init,\n  destroy,\n  sidplay2_open,\n  NULL,\n  NULL,\n  sidplay2_close,\n  sidplay2_decode,\n  sidplay2_seek,\n  sidplay2_info,\n  sidplay2_get_bitrate,\n  sidplay2_get_duration,\n  sidplay2_get_error,\n  sidplay2_our_format_ext,\n  NULL,\n  NULL,\n  NULL,\n  NULL,\n  NULL\n};\n\nextern \"C\" struct decoder *plugin_init ()\n{\n  pthread_mutex_init(&db_mtx, NULL);\n  pthread_mutex_init(&player_select_mtx, NULL);\n  return &sidplay2_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/sidplay2/sidplay2.h",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * libsidplay2-plugin Copyright (C) 2007 Hendrik Iben <hiben@tzi.de>\n * Enables MOC to play sids via libsidplay2/libsidutils.\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"decoder.h\"\n\n#ifdef __cplusplus\n}\n#endif\n\n#ifdef __cplusplus\n\n// debug and error are used by this library too...\n#undef debug\n#undef error\n\n#include <sidplay/sidplay2.h>\n#include <sidplay/SidTune.h>\n#include <sidplay/builders/resid.h>\n#include <sidplay/utils/SidDatabase.h>\n\n#define RESID_ID      \"ReSID\"\n#define OPT_DEFLEN    \"SidPlay2_DefaultSongLength\"\n#define OPT_MINLEN    \"SidPlay2_MinimumSongLength\"\n#define OPT_DATABASE  \"SidPlay2_Database\"\n#define OPT_FREQ      \"SidPlay2_Frequency\"\n#define OPT_PREC      \"SidPlay2_Bits\"\n#define OPT_PMODE     \"SidPlay2_PlayMode\"\n#define OPT_OPTI      \"SidPlay2_Optimisation\"\n#define OPT_START     \"SidPlay2_StartAtStart\"\n#define OPT_SUBTUNES  \"SidPlay2_PlaySubTunes\"\n\n#define STITLE 0\n#define SAUTHOR 1\n#define SCOPY 2\n\n#define POOL_SIZE 2\n\nstruct sidplay2_data\n{\n  SidTuneMod * tune;\n  SID_EXTERN::sidplay2 *player;\n  sid2_config_t cfg;\n  ReSIDBuilder *builder;\n  int length;\n  int *sublengths;\n  int songs;\n  int startSong;\n  int currentSong;\n  int timeStart;\n  int timeEnd;\n  struct decoder_error error;\n  int sample_format, frequency, channels;\n};\n\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid *sidplay2_open (const char *file);\nvoid sidplay2_close (void *void_data);\nvoid sidplay2_get_error (void *prv_data, struct decoder_error *error);\nvoid sidplay2_info (const char *file_name, struct file_tags *info,\n\t\tconst int tags_sel);\nint sidplay2_seek (void *void_data, int sec);\nint sidplay2_decode (void *void_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params);\nint sidplay2_get_bitrate (void *void_data);\nint sidplay2_get_duration (void *void_data);\nvoid sidplay2_get_name (const char *file, char buf[4]);\nint sidplay2_our_format_ext (const char *ext);\nvoid destroy ();\nvoid init ();\ndecoder *plugin_init ();\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "decoder_plugins/sidplay2/sidplay2.m4",
    "content": "dnl libsidplay2\n\nAC_ARG_WITH(sidplay2, AS_HELP_STRING([--without-sidplay2],\n                                     [Compile without libsidplay2]))\n\nif test \"x$with_sidplay2\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(sidplay2, libsidplay2 >= 2.1.1,\n\t\t\t   [sidplay2_OK=\"yes\"],\n\t\t\t   [true])\n\n\tPKG_CHECK_MODULES(sidutils, libsidutils >= 1.0.4,\n\t\t\t   [sidutils_OK=\"yes\"],\n\t\t\t   [true])\ndnl This is a rather ugly hack to find the builder\ndnl as libsidplay2 works fine without it but the\ndnl decoder uses it...\n\tif test \"x$sidplay2_OK\" = \"xyes\"; then\n\t\tif test \"x$sidutils_OK\" = \"xyes\"; then\n\t\t\ts2lib=`$PKG_CONFIG --variable=builders libsidplay2 2>/dev/null`\n\t\t\tif test \"x$s2lib\" != \"x\"; then\n\t\t\t\tAC_LANG_PUSH([C++])\n\t\t\t\tsave_CXXFLAGS=\"$CXXFLAGS\"\n\t\t\t\tCXXFLAGS=\"$CXXFLAGS $sidplay2_CFLAGS\"\n\t\t\t\tAC_CHECK_HEADER([sidplay/builders/resid.h], [resid_OK=\"yes\"])\n\t\t\t\tCXXFLAGS=\"$save_CXXFLAGS\"\n\t\t\t\tAC_LANG_POP([C++])\n\t\t\t\tif test \"x$resid_OK\" = \"xyes\"; then\n\t\t\t\t\tsidplay2_LDFLAGS=\"-L$s2lib -lresid-builder\"\n\t\t\t\t\tAC_SUBST(sidplay2_LDFLAGS)\n\t\t\t\t\tAC_SUBST(sidplay2_LIBS)\n\t\t\t\t\tAC_SUBST(sidplay2_CFLAGS)\n\t\t\t\t\tAC_SUBST(sidutils_LIBS)\n\t\t\t\t\tAC_SUBST(sidutils_CFLAGS)\n\t\t\t\t\twant_sidplay2=\"yes\"\n\t\t\t\t\tDECODER_PLUGINS=\"$DECODER_PLUGINS sidplay2\"\n\t\t\t\tfi\n\t\t\tfi\n\t\tfi\n\tfi\nfi\n\nAM_CONDITIONAL([BUILD_sidplay2], [test \"$want_sidplay2\"])\nAC_CONFIG_FILES([decoder_plugins/sidplay2/Makefile])\n"
  },
  {
    "path": "decoder_plugins/sndfile/Makefile.am",
    "content": "lib_LTLIBRARIES = libsndfile_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibsndfile_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibsndfile_decoder_la_LIBADD = $(sndfile_LIBS)\nlibsndfile_decoder_la_CFLAGS = $(sndfile_CFLAGS) -I$(top_srcdir)\nlibsndfile_decoder_la_SOURCES = sndfile.c\n"
  },
  {
    "path": "decoder_plugins/sndfile/sndfile.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#include <unistd.h>\n#include <stdint.h>\n#include <errno.h>\n#include <string.h>\n#include <strings.h>\n#include <assert.h>\n#include <sndfile.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"decoder.h\"\n#include \"server.h\"\n#include \"log.h\"\n#include \"files.h\"\n#include \"lists.h\"\n\n/* TODO:\n * - sndfile is not thread-safe: use a mutex?\n * - some tags can be read.\n */\n\nstruct sndfile_data\n{\n\tSNDFILE *sndfile;\n\tSF_INFO snd_info;\n\tstruct decoder_error error;\n\tbool timing_broken;\n};\n\nstatic lists_t_strs *supported_extns = NULL;\n\nstatic void load_extn_list ()\n{\n\tconst int counts[] = {SFC_GET_SIMPLE_FORMAT_COUNT,\n\t                      SFC_GET_FORMAT_MAJOR_COUNT};\n\tconst int formats[] = {SFC_GET_SIMPLE_FORMAT,\n\t                       SFC_GET_FORMAT_MAJOR};\n\n\tsupported_extns = lists_strs_new (16);\n\n\tfor (size_t ix = 0; ix < ARRAY_SIZE(counts); ix += 1) {\n\t\tint limit;\n\t\tSF_FORMAT_INFO format_info;\n\n\t\tsf_command (NULL, counts[ix], &limit, sizeof (limit));\n\t\tfor (int iy = 0 ; iy < limit ; iy += 1) {\n\t\t\tformat_info.format = iy ;\n\t\t\tsf_command (NULL, formats[ix], &format_info, sizeof (format_info));\n\t\t\tif (!lists_strs_exists (supported_extns, format_info.extension))\n\t\t\t\tlists_strs_append (supported_extns, format_info.extension);\n\t\t}\n\t}\n\n\t/* These are synonyms of supported extensions. */\n\tif (lists_strs_exists (supported_extns, \"aiff\"))\n\t\tlists_strs_append (supported_extns, \"aif\");\n\tif (lists_strs_exists (supported_extns, \"au\"))\n\t\tlists_strs_append (supported_extns, \"snd\");\n\tif (lists_strs_exists (supported_extns, \"wav\")) {\n\t\tlists_strs_append (supported_extns, \"nist\");\n\t\tlists_strs_append (supported_extns, \"sph\");\n\t}\n\tif (lists_strs_exists (supported_extns, \"iff\"))\n\t\tlists_strs_append (supported_extns, \"svx\");\n\tif (lists_strs_exists (supported_extns, \"oga\"))\n\t\tlists_strs_append (supported_extns, \"ogg\");\n\tif (lists_strs_exists (supported_extns, \"sf\"))\n\t\tlists_strs_append (supported_extns, \"ircam\");\n\tif (lists_strs_exists (supported_extns, \"mat\")) {\n\t\tlists_strs_append (supported_extns, \"mat4\");\n\t\tlists_strs_append (supported_extns, \"mat5\");\n\t}\n}\n\nstatic void sndfile_init ()\n{\n\tload_extn_list ();\n}\n\nstatic void sndfile_destroy ()\n{\n\tlists_strs_free (supported_extns);\n}\n\n/* Return true iff libsndfile's frame count is unknown or miscalculated. */\nstatic bool is_timing_broken (int fd, struct sndfile_data *data)\n{\n\tint rc;\n\tstruct stat buf;\n\tSF_INFO *info = &data->snd_info;\n\n\tif (info->frames == SF_COUNT_MAX)\n\t\t\treturn true;\n\n\tif (info->frames / info->samplerate > INT32_MAX)\n\t\t\treturn true;\n\n\t/* The libsndfile code warns of miscalculation for huge files of\n\t * specific formats, but it's unclear if others are known to work\n\t * or the test is just omitted for them.  We'll assume they work\n\t * until it's shown otherwise. */\n\tswitch (info->format & SF_FORMAT_TYPEMASK) {\n\tcase SF_FORMAT_AIFF:\n\tcase SF_FORMAT_AU:\n\tcase SF_FORMAT_SVX:\n\tcase SF_FORMAT_WAV:\n\t\trc = fstat (fd, &buf);\n\t\tif (rc == -1) {\n\t\t\tlog_errno (\"Can't stat file\", errno);\n\t\t\t/* We really need to return \"unknown\" here. */\n\t\t\treturn false;\n\t\t}\n\n\t\tif (buf.st_size > UINT32_MAX)\n\t\t\treturn true;\n\t}\n\n\treturn false;\n}\n\nstatic void *sndfile_open (const char *file)\n{\n\tint fd;\n\tstruct sndfile_data *data;\n\n\tdata = (struct sndfile_data *)xmalloc (sizeof(struct sndfile_data));\n\n\tdecoder_error_init (&data->error);\n\tmemset (&data->snd_info, 0, sizeof(data->snd_info));\n\tdata->sndfile = NULL;\n\tdata->timing_broken = false;\n\n\tfd = open (file, O_RDONLY);\n\tif (fd == -1) {\n\t\tchar *err = xstrerror (errno);\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"Can't open file: %s\", err);\n\t\tfree (err);\n\t\treturn data;\n\t}\n\n\t/* sf_open_fd() close()s 'fd' on error and in sf_close(). */\n\tdata->sndfile = sf_open_fd (fd, SFM_READ, &data->snd_info, SF_TRUE);\n\tif (!data->sndfile) {\n\t\t/* FIXME: sf_strerror is not thread safe with NULL argument */\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Can't open file: %s\", sf_strerror(NULL));\n\t\treturn data;\n\t}\n\n\t/* If the timing is broken, sndfile only decodes up to the broken value. */\n\tdata->timing_broken = is_timing_broken (fd, data);\n\tif (data->timing_broken) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"File too large for audio format!\");\n\t\treturn data;\n\t}\n\n\tdebug (\"Opened file %s\", file);\n\tdebug (\"Channels: %d\", data->snd_info.channels);\n\tdebug (\"Format: %08X\", data->snd_info.format);\n\tdebug (\"Sample rate: %d\", data->snd_info.samplerate);\n\n\treturn data;\n}\n\nstatic void sndfile_close (void *void_data)\n{\n\tstruct sndfile_data *data = (struct sndfile_data *)void_data;\n\n\tif (data->sndfile)\n\t\tsf_close (data->sndfile);\n\n\tdecoder_error_clear (&data->error);\n\tfree (data);\n}\n\nstatic void sndfile_info (const char *file_name, struct file_tags *info,\n\t\tconst int tags_sel)\n{\n\tif (tags_sel & TAGS_TIME) {\n\t\tstruct sndfile_data *data;\n\n\t\tdata = sndfile_open (file_name);\n\t\tif (data->sndfile && !data->timing_broken)\n\t\t\tinfo->time = data->snd_info.frames / data->snd_info.samplerate;\n\t\tsndfile_close (data);\n\t}\n}\n\nstatic int sndfile_seek (void *void_data, int sec)\n{\n\tstruct sndfile_data *data = (struct sndfile_data *)void_data;\n\tint res;\n\n\tassert (sec >= 0);\n\n\tres = sf_seek (data->sndfile, data->snd_info.samplerate * sec,\n\t\t\tSEEK_SET);\n\n\tif (res < 0)\n\t\treturn -1;\n\n\treturn res / data->snd_info.samplerate;\n}\n\nstatic int sndfile_decode (void *void_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n\tstruct sndfile_data *data = (struct sndfile_data *)void_data;\n\n\tsound_params->channels = data->snd_info.channels;\n\tsound_params->rate = data->snd_info.samplerate;\n\tsound_params->fmt = SFMT_FLOAT;\n\n\treturn sf_readf_float (data->sndfile, (float *)buf,\n\t\t\tbuf_len / sizeof(float) / data->snd_info.channels)\n\t\t* sizeof(float) * data->snd_info.channels;\n}\n\nstatic int sndfile_get_bitrate (void *unused ATTR_UNUSED)\n{\n\treturn -1;\n}\n\nstatic int sndfile_get_duration (void *void_data)\n{\n\tint result;\n\tstruct sndfile_data *data = (struct sndfile_data *)void_data;\n\n\tresult = -1;\n\tif (!data->timing_broken)\n\t\tresult = data->snd_info.frames / data->snd_info.samplerate;\n\n\treturn result;\n}\n\nstatic void sndfile_get_name (const char *file, char buf[4])\n{\n\tchar *ext;\n\n\text = ext_pos (file);\n\tif (ext) {\n\t\tif (!strcasecmp (ext, \"snd\"))\n\t\t\tstrcpy (buf, \"AU\");\n\t\telse if (!strcasecmp (ext, \"8svx\"))\n\t\t\tstrcpy (buf, \"SVX\");\n\t\telse if (!strcasecmp (ext, \"oga\"))\n\t\t\tstrcpy (buf, \"OGG\");\n\t\telse if (!strcasecmp (ext, \"sf\") || !strcasecmp (ext, \"icram\"))\n\t\t\tstrcpy (buf, \"IRC\");\n\t\telse if (!strcasecmp (ext, \"mat4\") || !strcasecmp (ext, \"mat5\"))\n\t\t\tstrcpy (buf, \"MAT\");\n\t}\n}\n\nstatic int sndfile_our_format_ext (const char *ext)\n{\n\treturn lists_strs_exists (supported_extns, ext);\n}\n\nstatic void sndfile_get_error (void *prv_data, struct decoder_error *error)\n{\n\tstruct sndfile_data *data = (struct sndfile_data *)prv_data;\n\n\tdecoder_error_copy (error, &data->error);\n}\n\nstatic struct decoder sndfile_decoder = {\n\tDECODER_API_VERSION,\n\tsndfile_init,\n\tsndfile_destroy,\n\tsndfile_open,\n\tNULL,\n\tNULL,\n\tsndfile_close,\n\tsndfile_decode,\n\tsndfile_seek,\n\tsndfile_info,\n\tsndfile_get_bitrate,\n\tsndfile_get_duration,\n\tsndfile_get_error,\n\tsndfile_our_format_ext,\n\tNULL,\n\tsndfile_get_name,\n\tNULL,\n\tNULL,\n\tNULL\n};\n\nstruct decoder *plugin_init ()\n{\n\treturn &sndfile_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/sndfile/sndfile.m4",
    "content": "dnl libsndfile\n\nAC_ARG_WITH(sndfile, AS_HELP_STRING([--without-sndfile],\n                                    [Compile without libsndfile]))\n\nif test \"x$with_sndfile\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(sndfile, sndfile >= 1.0.0,\n\t\t\t   [AC_SUBST(sndfile_LIBS)\n\t\t\t   AC_SUBST(sndfile_CFLAGS)\n\t\t\t   want_sndfile=\"yes\"\n\t\t\t   DECODER_PLUGINS=\"$DECODER_PLUGINS sndfile\"],\n\t\t\t   [true])\nfi\n\nAM_CONDITIONAL([BUILD_sndfile], [test \"$want_sndfile\"])\nAC_CONFIG_FILES([decoder_plugins/sndfile/Makefile])\n"
  },
  {
    "path": "decoder_plugins/speex/Makefile.am",
    "content": "lib_LTLIBRARIES = libspeex_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibspeex_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibspeex_decoder_la_LIBADD = $(speex_LIBS)\nlibspeex_decoder_la_CFLAGS = $(speex_CFLAGS) -I$(top_srcdir)\nlibspeex_decoder_la_SOURCES = speex.c\n"
  },
  {
    "path": "decoder_plugins/speex/speex.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * Based on (and includes code from) ogg123 copyright by\n * Stan Seibert <volsung@xiph.org> AND OTHER CONTRIBUTORS\n * and speexdec copyright by Jean-Marc Valin\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <string.h>\n#include <strings.h>\n#include <inttypes.h>\n#include <assert.h>\n#include <speex/speex.h>\n#include <speex/speex_header.h>\n#include <speex/speex_stereo.h>\n#include <speex/speex_callbacks.h>\n#include <ogg/ogg.h>\n\n/*#define DEBUG*/\n\n#include \"common.h\"\n#include \"decoder.h\"\n#include \"io.h\"\n#include \"audio.h\"\n#include \"log.h\"\n\n/* Use speex's audio enhancement feature */\n#define ENHANCE_AUDIO 1\n\nstruct spx_data\n{\n\tstruct io_stream *stream;\n\tstruct decoder_error error;\n\tint ok;\t\t\t/* was the stream opened succesfully? */\n\n\tSpeexBits bits;\n\tvoid *st;\t\t/* speex decoder state */\n\togg_sync_state oy;\n\togg_page og;\n\togg_packet op;\n\togg_stream_state os;\n\tSpeexStereoState stereo;\n\tSpeexHeader *header;\n\n\tint frame_size;\n\tint rate;\n\tint nchannels;\n\tint frames_per_packet;\n\tint bitrate;\n\n\tint16_t *output;\n\tint output_start;\n\tint output_left;\n\tchar *comment_packet;\n\tint comment_packet_len;\n};\n\nstatic void *process_header (struct spx_data *data)\n{\n\tvoid *st;\n\tconst SpeexMode *mode;\n\tint modeID;\n\tSpeexCallback callback;\n\tint enhance = ENHANCE_AUDIO;\n\n\tdata->header = speex_packet_to_header ((char*)data->op.packet,\n\t\t\tdata->op.bytes);\n\tif (!data->header) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Can't open speex file: can't read header\");\n\t\treturn NULL;\n\t}\n\n\tif (data->header->mode >= SPEEX_NB_MODES) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Can't open speex file: Mode number %\"PRId32\n\t\t\t\t\" does not exist in this version\",\n\t\t\t\tdata->header->mode);\n\t\treturn NULL;\n\t}\n\n\tmodeID = data->header->mode;\n\tmode = speex_mode_list[modeID];\n\n\tif (mode->bitstream_version < data->header->mode_bitstream_version) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Can't open speex file: The file was encoded \"\n\t\t\t\t\"with a newer version of Speex.\");\n\t\treturn NULL;\n\t}\n\n\tif (mode->bitstream_version > data->header->mode_bitstream_version) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\"Can't open speex file: The file was encoded \"\n\t\t\t\t\"with an older version of Speex.\");\n\t\treturn NULL;\n\t}\n\n\tst = speex_decoder_init (mode);\n\tspeex_decoder_ctl(st, SPEEX_SET_ENH, &enhance);\n\tspeex_decoder_ctl(st, SPEEX_GET_FRAME_SIZE, &data->frame_size);\n\n\tcallback.callback_id = SPEEX_INBAND_STEREO;\n\tcallback.func = speex_std_stereo_request_handler;\n\tcallback.data = &data->stereo;\n\tspeex_decoder_ctl(st, SPEEX_SET_HANDLER, &callback);\n\tspeex_decoder_ctl(st, SPEEX_SET_SAMPLING_RATE, &data->header->rate);\n\n\treturn st;\n}\n\n/* Read the speex header. Return 0 on error. */\nstatic int read_speex_header (struct spx_data *data)\n{\n\tint packet_count = 0;\n\tint stream_init = 0;\n\tchar *buf;\n\tssize_t nb_read;\n\tint header_packets = 2;\n\n\twhile (packet_count < header_packets) {\n\n\t\t/* Get the ogg buffer for writing */\n\t\tbuf = ogg_sync_buffer (&data->oy, 200);\n\n\t\t/* Read bitstream from input file */\n\t\tnb_read = io_read (data->stream, buf, 200);\n\n\t\tif (nb_read < 0) {\n\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\t\"Can't open speex file: IO error: %s\",\n\t\t\t\t\tio_strerror(data->stream));\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (nb_read == 0) {\n\t\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t\t\t\t\"Can't open speex header\");\n\t\t\treturn 0;\n\t\t}\n\n\t\togg_sync_wrote (&data->oy, nb_read);\n\n\t\t/* Loop for all complete pages we got (most likely only one) */\n\t\twhile (ogg_sync_pageout(&data->oy, &data->og) == 1) {\n\n\t\t\tif (stream_init == 0) {\n\t\t\t\togg_stream_init(&data->os,\n\t\t\t\t\t\togg_page_serialno(&data->og));\n\t\t\t\tstream_init = 1;\n\t\t\t}\n\n\t\t\t/* Add page to the bitstream */\n\t\t\togg_stream_pagein (&data->os, &data->og);\n\n\t\t\t/* Extract all available packets FIXME: EOS! */\n\t\t\twhile (ogg_stream_packetout(&data->os, &data->op) == 1) {\n\n\t\t\t\t/* If first packet, process as Speex header */\n\t\t\t\tif (packet_count == 0) {\n\t\t\t\t\tdata->st = process_header (data);\n\n\t\t\t\t\tif (!data->st) {\n\t\t\t\t\t\togg_stream_clear (&data->os);\n\t\t\t\t\t\treturn 0;\n\t\t\t\t\t}\n\n\t\t\t\t\tdata->rate = data->header->rate;\n\t\t\t\t\tdata->nchannels\n\t\t\t\t\t\t= data->header->nb_channels;\n\t\t\t\t\tdata->frames_per_packet\n\t\t\t\t\t\t= data->header->frames_per_packet;\n\t\t\t\t\t/*data->vbr = data->header->vbr; */\n\n\t\t\t\t\tif (!data->frames_per_packet)\n\t\t\t\t\t\tdata->frames_per_packet=1;\n\n\t\t\t\t\tdata->output = xmalloc (data->frame_size *\n\t\t\t\t\t\t\tdata->nchannels *\n\t\t\t\t\t\t\tdata->frames_per_packet *\n\t\t\t\t\t\t\tsizeof(int16_t));\n\t\t\t\t\tdata->output_start = 0;\n\t\t\t\t\tdata->output_left = 0;\n\n\t\t\t\t\theader_packets += data->header->extra_headers;\n\t\t\t\t}\n\t\t\t\telse if (packet_count == 1) {\n\t\t\t\t\tdata->comment_packet_len\n\t\t\t\t\t\t= data->op.bytes;\n\t\t\t\t\tdata->comment_packet = xmalloc (\n\t\t\t\t\t\t\tsizeof(char) *\n\t\t\t\t\t\t\tdata->comment_packet_len);\n\t\t\t\t\tmemcpy (data->comment_packet,\n\t\t\t\t\t\t\tdata->op.packet,\n\t\t\t\t\t\t\tdata->comment_packet_len);\n\t\t\t\t}\n\n\t\t\t\tpacket_count++;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nstatic struct spx_data *spx_open_internal (struct io_stream *stream)\n{\n\tstruct spx_data *data;\n\tSpeexStereoState stereo = SPEEX_STEREO_STATE_INIT;\n\n\tdata = (struct spx_data *)xmalloc (sizeof(struct spx_data));\n\n\tdecoder_error_init (&data->error);\n\tdata->stream = stream;\n\n\tdata->st = NULL;\n\tdata->stereo = stereo;\n\tdata->header = NULL;\n\tdata->output = NULL;\n\tdata->comment_packet = NULL;\n\tdata->bitrate = -1;\n\togg_sync_init (&data->oy);\n\tspeex_bits_init (&data->bits);\n\n\tif (!read_speex_header(data)) {\n\t\togg_sync_clear (&data->oy);\n\t\tspeex_bits_destroy (&data->bits);\n\t\tdata->ok = 0;\n\t}\n\telse\n\t\tdata->ok = 1;\n\n\treturn data;\n}\n\nstatic void *spx_open (const char *file)\n{\n\tstruct io_stream *stream;\n\tstruct spx_data *data;\n\n\tstream = io_open (file, 1);\n\tif (io_ok (stream))\n\t\tdata = spx_open_internal (stream);\n\telse {\n\t\tdata = (struct spx_data *)xmalloc (sizeof(struct spx_data));\n\t\tdata->stream = stream;\n\t\tdata->header = NULL;\n\t\tdecoder_error_init (&data->error);\n\t\tdecoder_error (&data->error, ERROR_STREAM, 0,\n\t\t\t\t\"Can't open file: %s\", io_strerror(stream));\n\t\tdata->ok = 0;\n\t}\n\n\treturn data;\n}\n\nstatic void *spx_open_stream (struct io_stream *stream)\n{\n\treturn spx_open_internal (stream);\n}\n\nstatic int spx_can_decode (struct io_stream *stream)\n{\n\tchar buf[36];\n\n\tif (io_peek(stream, buf, 36) == 36 && !memcmp(buf, \"OggS\", 4)\n\t\t\t&& !memcmp(buf + 28, \"Speex   \", 8))\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic void spx_close (void *prv_data)\n{\n\tstruct spx_data *data = (struct spx_data *)prv_data;\n\n\tif (data->ok) {\n\t\tif (data->st)\n\t\t\tspeex_decoder_destroy (data->st);\n\t\tif (data->comment_packet)\n\t\t\tfree (data->comment_packet);\n\t\tif (data->output)\n\t\t\tfree (data->output);\n\t\tspeex_bits_destroy (&data->bits);\n\t\togg_stream_clear (&data->os);\n\t\togg_sync_clear (&data->oy);\n\t}\n\n\tio_close (data->stream);\n\tdecoder_error_clear (&data->error);\n\n\tfree (data->header);\n\tfree (data);\n}\n\n#define readint(buf, base) (((buf[base+3]<<24)&0xff000000)| \\\n                           ((buf[base+2]<<16)&0xff0000)| \\\n                           ((buf[base+1]<<8)&0xff00)| \\\n  \t           \t    (buf[base]&0xff))\n\nstatic void parse_comment (const char *str, struct file_tags *tags)\n{\n\tif (!strncasecmp(str, \"title=\", strlen (\"title=\")))\n\t\ttags->title = xstrdup(str + strlen (\"title=\"));\n\telse if (!strncasecmp(str, \"artist=\", strlen (\"artist=\")))\n\t\ttags->artist = xstrdup (str + strlen (\"artist=\"));\n\telse if (!strncasecmp(str, \"album=\", strlen (\"album=\")))\n\t\ttags->album = xstrdup (str + strlen (\"album=\"));\n\telse if (!strncasecmp(str, \"tracknumber=\", strlen (\"tracknumber=\")))\n\t\ttags->track = atoi (str\t+ strlen (\"tracknumber=\"));\n\telse if (!strncasecmp(str, \"track=\", strlen (\"track=\")))\n\t\ttags->track = atoi (str\t+ strlen (\"track=\"));\n}\n\nstatic void get_comments (struct spx_data *data, struct file_tags *tags)\n{\n\tif (data->comment_packet && data->comment_packet_len >= 8) {\n\t\tchar *c = data->comment_packet;\n\t\tint len, i, nb_fields;\n\t\tchar *end;\n\t\tchar *temp = NULL;\n\t\tint temp_len = 0;\n\n\t\t/* Parse out vendor string */\n\t\tend = c + data->comment_packet_len;\n\t\tlen = readint(c, 0);\n\t\tc += 4;\n\n\t\tif (c + len > end) {\n\t\t\tlogit (\"Broken comment\");\n\t\t\treturn;\n\t\t}\n\n\t\tc += len;\n\t\tif (c + 4 > end) {\n\t\t\tlogit (\"Broken comment\");\n\t\t\treturn;\n\t\t}\n\n\t\tnb_fields = readint (c, 0);\n\t\tc += 4;\n\n\t\tfor (i = 0; i < nb_fields; i++) {\n\t\t\tif (c + 4 > end) {\n\t\t\t\tif (temp)\n\t\t\t\t\tfree (temp);\n\t\t\t\tlogit (\"Broken comment\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlen = readint (c, 0);\n\t\t\tc += 4;\n\t\t\tif (c + len > end) {\n\t\t\t\tlogit (\"Broken comment\");\n\t\t\t\tif (temp)\n\t\t\t\t\tfree (temp);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (temp_len < len + 1) {\n\t\t\t\ttemp_len = len + 1;\n\t\t\t\ttemp = xrealloc (temp, temp_len);\n\t\t\t}\n\n\t\t\tstrncpy (temp, c, len);\n\t\t\ttemp[len] = '\\0';\n\t\t\tdebug (\"COMMENT: '%s'\", temp);\n\t\t\tparse_comment (temp, tags);\n\n\t\t\tc += len;\n\t\t}\n\n\t\tif (temp)\n\t\t\tfree(temp);\n\t}\n}\n\nstatic void get_more_data (struct spx_data *data)\n{\n\tchar *buf;\n\tssize_t nb_read;\n\n\tbuf = ogg_sync_buffer (&data->oy, 200);\n\tnb_read = io_read (data->stream, buf, 200);\n\togg_sync_wrote (&data->oy, nb_read);\n}\n\nstatic int count_time (struct spx_data *data)\n{\n\togg_int64_t last_granulepos = 0;\n\n\t/* Seek to somewhere near the last page */\n\tif (io_file_size(data->stream) > 10000) {\n\t\tdebug (\"Seeking near the end\");\n\t\tif (io_seek(data->stream, -10000, SEEK_END) == -1)\n\t\t\tlogit (\"Seeking failed, scanning whole file\");\n\t\togg_sync_reset (&data->oy);\n\t}\n\n\t/* Read granulepos from the last packet */\n\twhile (!io_eof(data->stream)) {\n\n\t\t/* Sync to page and read it */\n\t\twhile (!io_eof(data->stream)) {\n\t\t\tif (ogg_sync_pageout(&data->oy, &data->og) == 1) {\n\t\t\t\tdebug (\"Sync\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (!io_eof(data->stream)) {\n\t\t\t\tdebug (\"Need more data\");\n\t\t\t\tget_more_data (data);\n\t\t\t}\n\t\t}\n\n\t\t/* We have last packet */\n\t\tif (io_eof(data->stream))\n\t\t\tbreak;\n\n\t\tlast_granulepos = ogg_page_granulepos (&data->og);\n\t}\n\n\treturn last_granulepos / data->rate;\n}\n\n/* Fill info structure with data from spx comments */\nstatic void spx_info (const char *file_name, struct file_tags *tags,\n\t\tconst int tags_sel)\n{\n\tstruct io_stream *s;\n\n\ts = io_open (file_name, 0);\n\tif (io_ok (s)) {\n\t\tstruct spx_data *data = spx_open_internal (s);\n\n\t\tif (data->ok) {\n\t\t\tif (tags_sel & TAGS_COMMENTS)\n\t\t\t\tget_comments (data, tags);\n\t\t\tif (tags_sel & TAGS_TIME)\n\t\t\t\ttags->time = count_time (data);\n\t\t}\n\n\t\tspx_close (data);\n\t}\n\telse\n\t\tio_close (s);\n}\n\nstatic int spx_seek (void *prv_data, int sec)\n{\n\tstruct spx_data *data = (struct spx_data *)prv_data;\n\toff_t begin = 0, end, old_pos;\n\n\tassert (sec >= 0);\n\n\tend = io_file_size (data->stream);\n\tif (end == -1)\n\t\treturn -1;\n\told_pos = io_tell (data->stream);\n\n\tdebug (\"Seek request to %ds\", sec);\n\n\twhile (1) {\n\t\toff_t middle = (end + begin) / 2;\n\t\togg_int64_t granule_pos;\n\t\tint position_seconds;\n\n\t\tdebug (\"Seek to %\"PRId64, middle);\n\n\t\tif (io_seek(data->stream, middle, SEEK_SET) == -1) {\n\t\t\tio_seek (data->stream, old_pos, SEEK_SET);\n\t\t\togg_stream_reset (&data->os);\n\t\t\togg_sync_reset (&data->oy);\n\t\t\treturn -1;\n\t\t}\n\n\t\tdebug (\"Syncing...\");\n\n\t\t/* Sync to page and read it */\n\t\togg_sync_reset (&data->oy);\n\t\twhile (!io_eof(data->stream)) {\n\t\t\tif (ogg_sync_pageout(&data->oy, &data->og) == 1) {\n\t\t\t\tdebug (\"Sync\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (!io_eof(data->stream)) {\n\t\t\t\tdebug (\"Need more data\");\n\t\t\t\tget_more_data (data);\n\t\t\t}\n\t\t}\n\n\t\tif (io_eof(data->stream)) {\n\t\t\tdebug (\"EOF when syncing\");\n\t\t\treturn -1;\n\t\t}\n\n\t\tgranule_pos = ogg_page_granulepos(&data->og);\n\t\tposition_seconds = granule_pos / data->rate;\n\n\t\tdebug (\"We are at %ds\", position_seconds);\n\n\t\tif (position_seconds == sec) {\n\t\t\togg_stream_pagein (&data->os, &data->og);\n\t\t\tdebug (\"We have it at granulepos %\"PRId64, granule_pos);\n\t\t\tbreak;\n\t\t}\n\t\telse if (sec < position_seconds) {\n\t\t\tend = middle;\n\t\t\tdebug (\"going back\");\n\t\t}\n\t\telse {\n\t\t\tbegin = middle;\n\t\t\tdebug (\"going forward\");\n\t\t}\n\n\t\tdebug (\"begin - end %\"PRId64\" - %\"PRId64, begin, end);\n\n\t\tif (end - begin <= 200) {\n\n\t\t\t/* Can't find the exact position. */\n\t\t\tsec = position_seconds;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\togg_sync_reset (&data->oy);\n\togg_stream_reset (&data->os);\n\n\treturn sec;\n}\n\nstatic int spx_decode (void *prv_data, char *sound_buf, int nbytes,\n\t\tstruct sound_params *sound_params)\n{\n\tstruct spx_data *data = (struct spx_data *)prv_data;\n  \tint bytes_requested = nbytes;\n\tint16_t *out = (int16_t *)sound_buf;\n\n\tsound_params->channels = data->nchannels;\n\tsound_params->rate = data->rate;\n\tsound_params->fmt = SFMT_S16 | SFMT_NE;\n\n\twhile (nbytes) {\n\t\tint j;\n\n\t\t/* First see if there is anything left in the output buffer and\n\t\t * empty it out */\n\t\tif (data->output_left > 0) {\n\t\t\tint to_copy = nbytes / sizeof(int16_t);\n\n\t\t\tto_copy = MIN(data->output_left, to_copy);\n\n\t\t\tmemcpy (out, data->output + data->output_start,\n\t\t\t\t\tto_copy * sizeof(int16_t));\n\n\t\t\tout += to_copy;\n\t\t\tdata->output_start += to_copy;\n\t\t\tdata->output_left -= to_copy;\n\n\t\t\tnbytes -= to_copy * sizeof(int16_t);\n\t\t}\n\t\telse if (ogg_stream_packetout (&data->os, &data->op) == 1) {\n\t\t\tint16_t *temp_output = data->output;\n\n\t\t\t/* Decode some more samples */\n\n\t\t\t/* Copy Ogg packet to Speex bitstream */\n\t\t\tspeex_bits_read_from (&data->bits,\n\t\t\t\t\t(char*)data->op.packet, data->op.bytes);\n\n\t\t\tfor (j = 0; j < data->frames_per_packet; j++) {\n\n\t\t\t\t/* Decode frame */\n\t\t\t\tspeex_decode_int (data->st, &data->bits,\n\t\t\t\t\t\ttemp_output);\n\t\t\t\tif (data->nchannels == 2)\n\t\t\t\t\tspeex_decode_stereo_int (temp_output,\n\t\t\t\t\t\t\tdata->frame_size,\n\t\t\t\t\t\t\t&data->stereo);\n\n\t\t\t\tspeex_decoder_ctl (data->st, SPEEX_GET_BITRATE,\n\t\t\t\t\t\t&data->bitrate);\n\t\t\t\t/*data->samples_decoded += data->frame_size;*/\n\n\t\t\t\ttemp_output += data->frame_size *\n\t\t\t\t\tdata->nchannels;\n\t\t\t}\n\n\t\t\t/*logit (\"Read %d bytes from page\", data->frame_size *\n\t\t\t\t\tdata->nchannels *\n\t\t\t\t\tdata->frames_per_packet);*/\n\n\t\t\tdata->output_start = 0;\n\t\t\tdata->output_left = data->frame_size *\n\t\t\t\tdata->nchannels * data->frames_per_packet;\n\t\t}\n\t\telse if (ogg_sync_pageout(&data->oy, &data->og) == 1) {\n\n\t\t\t/* Read in another ogg page */\n\t\t\togg_stream_pagein (&data->os, &data->og);\n\t\t\tdebug (\"Granulepos: %\"PRId64, ogg_page_granulepos(&data->og));\n\n\t\t}\n\t\telse if (!io_eof(data->stream)) {\n\t\t\t/* Finally, pull in some more data and try again on the next pass */\n\t\t\tget_more_data (data);\n\t\t}\n\t\telse\n\t\t\tbreak;\n\t}\n\n\treturn bytes_requested - nbytes;\n}\n\n#if 0\nstatic int spx_current_tags (void *prv_data, struct file_tags *tags)\n{\n\tstruct spx_data *data = (struct spx_data *)prv_data;\n\n\treturn 0;\n}\n#endif\n\nstatic int spx_get_bitrate (void *prv_data)\n{\n\tstruct spx_data *data = (struct spx_data *)prv_data;\n\n\treturn data->bitrate / 1000;\n}\n\nstatic int spx_get_duration (void *unused ATTR_UNUSED)\n{\n\t/*struct spx_data *data = (struct spx_data *)prv_data;*/\n\n\treturn -1;\n}\n\nstatic struct io_stream *spx_get_stream (void *prv_data)\n{\n\tstruct spx_data *data = (struct spx_data *)prv_data;\n\n\treturn data->stream;\n}\n\nstatic void spx_get_name (const char *unused ATTR_UNUSED, char buf[4])\n{\n\tstrcpy (buf, \"SPX\");\n}\n\nstatic int spx_our_format_ext (const char *ext)\n{\n\treturn !strcasecmp (ext, \"spx\");\n}\n\nstatic void spx_get_error (void *prv_data, struct decoder_error *error)\n{\n\tstruct spx_data *data = (struct spx_data *)prv_data;\n\n\tdecoder_error_copy (error, &data->error);\n}\n\nstatic int spx_our_mime (const char *mime)\n{\n\treturn !strcasecmp (mime, \"audio/x-speex\")\n\t\t|| !strncasecmp (mime, \"audio/x-speex;\", 14)\n\t\t|| !strcasecmp (mime, \"audio/speex\")\n\t\t|| !strncasecmp (mime, \"audio/speex;\", 12);\n}\n\nstatic struct decoder spx_decoder = {\n\tDECODER_API_VERSION,\n\tNULL,\n\tNULL,\n\tspx_open,\n\tspx_open_stream,\n\tspx_can_decode,\n\tspx_close,\n\tspx_decode,\n\tspx_seek,\n\tspx_info,\n\tspx_get_bitrate,\n\tspx_get_duration,\n\tspx_get_error,\n\tspx_our_format_ext,\n\tspx_our_mime,\n\tspx_get_name,\n\tNULL /*spx_current_tags*/,\n\tspx_get_stream,\n\tNULL\n};\n\nstruct decoder *plugin_init ()\n{\n\treturn &spx_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/speex/speex.m4",
    "content": "dnl speex\n\nAC_ARG_WITH(speex, AS_HELP_STRING([--without-speex],\n                                  [Compile without speex support]))\n\nif test \"x$with_speex\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(speex, [ogg >= 1.0 speex >= 1.0.0],\n\t\t\t  [AC_SUBST(speex_LIBS)\n\t\t\t  AC_SUBST(speex_CFLAGS)\n\t\t\t  want_speex=\"yes\"\n\t\t\t  DECODER_PLUGINS=\"$DECODER_PLUGINS speex\"],\n\t\t\t  [true])\nfi\n\nAM_CONDITIONAL([BUILD_speex], [test \"$want_speex\"])\nAC_CONFIG_FILES([decoder_plugins/speex/Makefile])\n"
  },
  {
    "path": "decoder_plugins/timidity/Makefile.am",
    "content": "lib_LTLIBRARIES = libtimidity_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibtimidity_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibtimidity_decoder_la_LIBADD = $(timidity_LIBS)\nlibtimidity_decoder_la_CFLAGS = $(timidity_CFLAGS) -I$(top_srcdir)\nlibtimidity_decoder_la_SOURCES = timidity.c\n"
  },
  {
    "path": "decoder_plugins/timidity/timidity.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * libTiMidity-plugin Copyright (C) 2007 Hendrik Iben <hiben@tzi.de>\n * The hard work is done by the libTiMidity-Library written by\n * Konstantin Korikov (http://libtimidity.sourceforge.net).\n * I have merely hacked together a wrapper...\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <string.h>\n#include <strings.h>\n#include <assert.h>\n#include <timidity.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"io.h\"\n#include \"decoder.h\"\n#include \"log.h\"\n#include \"files.h\"\n#include \"options.h\"\n\nMidSongOptions midioptions;\n\nstruct timidity_data\n{\n  MidSong *midisong;\n  int length;\n  struct decoder_error error;\n};\n\nstatic struct timidity_data *make_timidity_data(const char *file) {\n  struct timidity_data *data;\n\n  data = (struct timidity_data *)xmalloc (sizeof(struct timidity_data));\n\n  data->midisong = NULL;\n  decoder_error_init (&data->error);\n\n  MidIStream *midistream = mid_istream_open_file(file);\n\n  if(midistream==NULL) {\n    decoder_error(&data->error, ERROR_FATAL, 0,\n                  \"Can't open midifile: %s\", file);\n    return data;\n  }\n\n  data->midisong = mid_song_load(midistream, &midioptions);\n  mid_istream_close(midistream);\n\n  if(data->midisong==NULL) {\n    decoder_error(&data->error, ERROR_FATAL, 0,\n                  \"Can't load midifile: %s\", file);\n    return data;\n  }\n\n  return data;\n}\n\nstatic void *timidity_open (const char *file)\n{\n  struct timidity_data *data = make_timidity_data(file);\n\n  if(data->midisong) {\n    data->length = mid_song_get_total_time(data->midisong);\n  }\n\n\n  if(data->midisong) {\n    debug (\"Opened file %s\", file);\n\n    mid_song_set_volume(data->midisong, options_get_int(\"TiMidity_Volume\"));\n    mid_song_start(data->midisong);\n  }\n\n  return data;\n}\n\nstatic void timidity_close (void *void_data)\n{\n  struct timidity_data *data = (struct timidity_data *)void_data;\n\n  if (data->midisong) {\n    mid_song_free(data->midisong);\n  }\n\n  decoder_error_clear (&data->error);\n  free (data);\n}\n\nstatic void timidity_info (const char *file_name, struct file_tags *info,\n\t\tconst int tags_sel)\n{\n  struct timidity_data *data = make_timidity_data(file_name);\n\n  if(data->midisong==NULL) {\n    free (data);\n    return;\n  }\n\n  if(tags_sel & TAGS_TIME) {\n    info->time = mid_song_get_total_time(data->midisong) / 1000;\n    info->filled |= TAGS_TIME;\n  }\n\n  timidity_close(data);\n}\n\nstatic int timidity_seek (void *void_data, int sec)\n{\n  struct timidity_data *data = (struct timidity_data *)void_data;\n\n  assert (sec >= 0);\n\n  int ms = sec*1000;\n\n  ms = MIN(ms,data->length);\n\n  mid_song_seek(data->midisong, ms);\n\n  return ms/1000;\n}\n\nstatic int timidity_decode (void *void_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n  struct timidity_data *data = (struct timidity_data *)void_data;\n\n  sound_params->channels = midioptions.channels;\n  sound_params->rate = midioptions.rate;\n  sound_params->fmt = (midioptions.format==MID_AUDIO_S16LSB)?(SFMT_S16 | SFMT_LE):SFMT_S8;\n\n  return mid_song_read_wave(data->midisong, (void *)buf, buf_len);\n}\n\nstatic int timidity_get_bitrate (void *unused ATTR_UNUSED)\n{\n  return -1;\n}\n\nstatic int timidity_get_duration (void *void_data)\n{\n  struct timidity_data *data = (struct timidity_data *)void_data;\n  return data->length/1000;\n}\n\nstatic void timidity_get_name (const char *unused ATTR_UNUSED, char buf[4])\n{\n  strcpy (buf, \"MID\");\n}\n\nstatic int timidity_our_format_ext(const char *ext)\n{\n  return !strcasecmp (ext, \"MID\");\n}\n\nstatic int timidity_our_format_mime (const char *mime)\n{\n  return !strcasecmp(mime, \"audio/midi\")\n      || !strncasecmp(mime, \"audio/midi;\", 10);\n}\n\nstatic void timidity_get_error (void *prv_data, struct decoder_error *error)\n{\n  struct timidity_data *data = (struct timidity_data *)prv_data;\n\n  decoder_error_copy (error, &data->error);\n}\n\nstatic void timidity_destroy()\n{\n  mid_exit();\n}\n\nstatic struct decoder timidity_decoder =\n{\n  DECODER_API_VERSION,\n  NULL,\n  timidity_destroy,\n  timidity_open,\n  NULL,\n  NULL,\n  timidity_close,\n  timidity_decode,\n  timidity_seek,\n  timidity_info,\n  timidity_get_bitrate,\n  timidity_get_duration,\n  timidity_get_error,\n  timidity_our_format_ext,\n  timidity_our_format_mime,\n  timidity_get_name,\n  NULL,\n  NULL,\n  NULL\n};\n\nstruct decoder *plugin_init ()\n{\n  char *config;\n  int initresult;\n\n  config = options_get_str(\"TiMidity_Config\");\n  if (config == NULL || strcasecmp(config, \"yes\") == 0)\n    initresult = mid_init(NULL);\n  else if (strcasecmp(config, \"no\") == 0)\n    initresult = mid_init_no_config();\n  else\n    initresult = mid_init(config);\n\n  // Is there a better way to signal failed init?\n  // The decoder-init-function may not return errors AFAIK...\n  if(initresult < 0)\n  {\n    if (config == NULL || strcasecmp(config, \"yes\") == 0)\n      config = \"<default>\";\n    fatal(\"TiMidity-Plugin: Error processing TiMidity-Configuration!\\n\"\n          \"                              Configuration file is: %s\", config);\n  }\n\n  midioptions.rate = options_get_int(\"TiMidity_Rate\");\n  midioptions.format = (options_get_int(\"TiMidity_Bits\")==16)?MID_AUDIO_S16LSB:MID_AUDIO_S8;\n  midioptions.channels = options_get_int(\"TiMidity_Channels\");\n  midioptions.buffer_size = midioptions.rate;\n\n  return &timidity_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/timidity/timidity.m4",
    "content": "dnl libtimidity\n\nAC_ARG_WITH(timidity, AS_HELP_STRING([--without-timidity],\n                                     [Compile without libtimidity]))\n\nif test \"x$with_timidity\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(timidity, libtimidity >= 0.1.0,\n\t\t\t   [AC_SUBST(timidity_LIBS)\n\t\t\t   AC_SUBST(timidity_CFLAGS)\n\t\t\t   want_timidity=\"yes\"\n\t\t\t   DECODER_PLUGINS=\"$DECODER_PLUGINS timidity\"],\n\t\t\t   [true])\nfi\n\nAM_CONDITIONAL([BUILD_timidity], [test \"$want_timidity\"])\nAC_CONFIG_FILES([decoder_plugins/timidity/Makefile])\n"
  },
  {
    "path": "decoder_plugins/vorbis/Makefile.am",
    "content": "lib_LTLIBRARIES = libvorbis_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibvorbis_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibvorbis_decoder_la_LIBADD = $(OGG_VORBIS_LIBS)\nlibvorbis_decoder_la_CFLAGS = $(OGG_VORBIS_CFLAGS) -I$(top_srcdir)\nlibvorbis_decoder_la_SOURCES = vorbis.c\n"
  },
  {
    "path": "decoder_plugins/vorbis/vorbis.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2002 - 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <limits.h>\n#include <inttypes.h>\n#include <string.h>\n#include <strings.h>\n#include <stdio.h>\n#include <errno.h>\n#include <assert.h>\n#ifndef HAVE_TREMOR\n#include <vorbis/vorbisfile.h>\n#include <vorbis/codec.h>\n#else\n#include <tremor/ivorbisfile.h>\n#include <tremor/ivorbiscodec.h>\n#endif\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"decoder.h\"\n#include \"io.h\"\n#include \"audio.h\"\n\n/* These merely silence compiler warnings about unused definitions in\n * the Vorbis library header files. */\n#if defined(HAVE_VAR_ATTRIBUTE_UNUSED) && !defined(HAVE_TREMOR)\nstatic ov_callbacks *vorbis_unused[] ATTR_UNUSED = {\n\t&OV_CALLBACKS_DEFAULT,\n\t&OV_CALLBACKS_NOCLOSE,\n\t&OV_CALLBACKS_STREAMONLY,\n\t&OV_CALLBACKS_STREAMONLY_NOCLOSE\n};\n#endif\n\n/* Tremor defines time as 64-bit integer milliseconds. */\n#ifndef HAVE_TREMOR\nstatic const int64_t time_scaler = 1;\n#else\nstatic const int64_t time_scaler = 1000;\n#endif\n\nstruct vorbis_data\n{\n\tstruct io_stream *stream;\n\tOggVorbis_File vf;\n\tint last_section;\n\tint bitrate;\n\tint avg_bitrate;\n\tint duration;\n\tstruct decoder_error error;\n\tint ok; /* was this stream successfully opened? */\n\n\tint tags_change; /* the tags were changed from the last call of\n\t                    ogg_current_tags() */\n\tstruct file_tags *tags;\n};\n\nstatic void get_comment_tags (OggVorbis_File *vf, struct file_tags *info)\n{\n\tint i;\n\tvorbis_comment *comments;\n\n\tcomments = ov_comment (vf, -1);\n\tfor (i = 0; i < comments->comments; i++) {\n\t\tif (!strncasecmp(comments->user_comments[i], \"title=\",\n\t\t\t\t strlen (\"title=\")))\n\t\t\tinfo->title = xstrdup(comments->user_comments[i]\n\t\t\t\t\t+ strlen (\"title=\"));\n\t\telse if (!strncasecmp(comments->user_comments[i],\n\t\t\t\t\t\"artist=\", strlen (\"artist=\")))\n\t\t\tinfo->artist = xstrdup (\n\t\t\t\t\tcomments->user_comments[i]\n\t\t\t\t\t+ strlen (\"artist=\"));\n\t\telse if (!strncasecmp(comments->user_comments[i],\n\t\t\t\t\t\"album=\", strlen (\"album=\")))\n\t\t\tinfo->album = xstrdup (\n\t\t\t\t\tcomments->user_comments[i]\n\t\t\t\t\t+ strlen (\"album=\"));\n\t\telse if (!strncasecmp(comments->user_comments[i],\n\t\t\t\t\t\"tracknumber=\",\n\t\t\t\t\tstrlen (\"tracknumber=\")))\n\t\t\tinfo->track = atoi (comments->user_comments[i]\n\t\t\t\t\t+ strlen (\"tracknumber=\"));\n\t\telse if (!strncasecmp(comments->user_comments[i],\n\t\t\t\t\t\"track=\", strlen (\"track=\")))\n\t\t\tinfo->track = atoi (comments->user_comments[i]\n\t\t\t\t\t+ strlen (\"track=\"));\n\t}\n}\n\n/* Return a description of an ov_*() error. */\nstatic const char *vorbis_strerror (const int code)\n{\n\tconst char *result;\n\n\tswitch (code) {\n\t\tcase OV_EREAD:\n\t\t\tresult = \"read error\";\n\t\t\tbreak;\n\t\tcase OV_ENOTVORBIS:\n\t\t\tresult = \"not a vorbis file\";\n\t\t\tbreak;\n\t\tcase OV_EVERSION:\n\t\t\tresult = \"vorbis version mismatch\";\n\t\t\tbreak;\n\t\tcase OV_EBADHEADER:\n\t\t\tresult = \"invalid Vorbis bitstream header\";\n\t\t\tbreak;\n\t\tcase OV_EFAULT:\n\t\t\tresult = \"internal (vorbis) logic fault\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tresult = \"unknown error\";\n\t}\n\n\treturn result;\n}\n\n/* Fill info structure with data from ogg comments */\nstatic void vorbis_tags (const char *file_name, struct file_tags *info,\n\t\tconst int tags_sel)\n{\n\tOggVorbis_File vf;\n\tFILE *file;\n\tint err_code;\n\n\tif (!(file = fopen (file_name, \"r\"))) {\n\t\tlog_errno (\"Can't open an OGG file\", errno);\n\t\treturn;\n\t}\n\n\t/* ov_test() is faster than ov_open(), but we can't read file time\n\t * with it. */\n\tif (tags_sel & TAGS_TIME)\n\t\terr_code = ov_open(file, &vf, NULL, 0);\n\telse\n\t\terr_code = ov_test(file, &vf, NULL, 0);\n\n\tif (err_code < 0) {\n\t\tlogit (\"Can't open %s: %s\", file_name, vorbis_strerror (err_code));\n\t\tfclose (file);\n\t\treturn;\n\t}\n\n\tif (tags_sel & TAGS_COMMENTS)\n\t\tget_comment_tags (&vf, info);\n\n\tif (tags_sel & TAGS_TIME) {\n\t\tint64_t vorbis_time;\n\n\t\tvorbis_time = ov_time_total (&vf, -1);\n\t\tif (vorbis_time >= 0)\n\t\t\tinfo->time = vorbis_time / time_scaler;\n\t}\n\n\tov_clear (&vf);\n}\n\nstatic size_t read_cb (void *ptr, size_t size, size_t nmemb, void *datasource)\n{\n\tssize_t res;\n\n\tres = io_read (datasource, ptr, size * nmemb);\n\n\t/* libvorbisfile expects the read callback to return >= 0 with errno\n\t * set to non zero on error. */\n\tif (res < 0) {\n\t\tlogit (\"Read error\");\n\t\tif (errno == 0)\n\t\t\terrno = 0xffff;\n\t\tres = 0;\n\t}\n\telse\n\t\tres /= size;\n\n\treturn res;\n}\n\nstatic int seek_cb (void *datasource, ogg_int64_t offset, int whence)\n{\n\tdebug (\"Seek request to %\"PRId64\" (%s)\", offset,\n\t\t\twhence == SEEK_SET ? \"SEEK_SET\"\n\t\t\t: (whence == SEEK_CUR ? \"SEEK_CUR\" : \"SEEK_END\"));\n\treturn io_seek (datasource, offset, whence) == -1 ? -1 : 0;\n}\n\nstatic int close_cb (void *unused ATTR_UNUSED)\n{\n\treturn 0;\n}\n\nstatic long tell_cb (void *datasource)\n{\n\treturn (long)io_tell (datasource);\n}\n\nstatic void vorbis_open_stream_internal (struct vorbis_data *data)\n{\n\tint res;\n\tov_callbacks callbacks = {\n\t\tread_cb,\n\t\tseek_cb,\n\t\tclose_cb,\n\t\ttell_cb\n\t};\n\n\tdata->tags = tags_new ();\n\n\tres = ov_open_callbacks (data->stream, &data->vf, NULL, 0, callbacks);\n\tif (res < 0) {\n\t\tconst char *vorbis_err = vorbis_strerror (res);\n\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"%s\", vorbis_err);\n\t\tdebug (\"ov_open error: %s\", vorbis_err);\n\t}\n\telse {\n\t\tint64_t duration;\n\n\t\tdata->last_section = -1;\n\t\tdata->avg_bitrate = ov_bitrate (&data->vf, -1) / 1000;\n\t\tdata->bitrate = data->avg_bitrate;\n\t\tdata->duration = -1;\n\t\tduration = ov_time_total (&data->vf, -1);\n\t\tif (duration >= 0)\n\t\t\tdata->duration = duration / time_scaler;\n\t\tdata->ok = 1;\n\t\tget_comment_tags (&data->vf, data->tags);\n\t}\n}\n\nstatic void *vorbis_open (const char *file)\n{\n\tstruct vorbis_data *data;\n\n\tdata = (struct vorbis_data *)xmalloc (sizeof(struct vorbis_data));\n\tdata->ok = 0;\n\n\tdecoder_error_init (&data->error);\n\tdata->tags_change = 0;\n\tdata->tags = NULL;\n\n\tdata->stream = io_open (file, 1);\n\tif (!io_ok(data->stream)) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0,\n\t\t               \"Can't load OGG: %s\", io_strerror(data->stream));\n\t\treturn data;\n\t}\n\n\t/* This a restriction placed on us by the vorbisfile API. */\n#if INT64_MAX > LONG_MAX\n\tif (io_file_size (data->stream) > LONG_MAX) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"File too large!\");\n\t\treturn data;\n\t}\n#endif\n\n\tvorbis_open_stream_internal (data);\n\n\treturn data;\n}\n\nstatic int vorbis_can_decode (struct io_stream *stream)\n{\n\tchar buf[35];\n\n\tif (io_peek (stream, buf, 35) == 35 && !memcmp (buf, \"OggS\", 4)\n\t\t\t&& !memcmp (buf + 28, \"\\01vorbis\", 7))\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic void *vorbis_open_stream (struct io_stream *stream)\n{\n\tstruct vorbis_data *data;\n\n\tdata = (struct vorbis_data *)xmalloc (sizeof(struct vorbis_data));\n\tdata->ok = 0;\n\n\tdecoder_error_init (&data->error);\n\tdata->stream = stream;\n\tvorbis_open_stream_internal (data);\n\n\treturn data;\n}\n\nstatic void vorbis_close (void *prv_data)\n{\n\tstruct vorbis_data *data = (struct vorbis_data *)prv_data;\n\n\tif (data->ok) {\n\t\tov_clear (&data->vf);\n\t}\n\n\tio_close (data->stream);\n\tdecoder_error_clear (&data->error);\n\tif (data->tags)\n\t\ttags_free (data->tags);\n\tfree (data);\n}\n\nstatic int vorbis_seek (void *prv_data, int sec)\n{\n\tstruct vorbis_data *data = (struct vorbis_data *)prv_data;\n\n\tassert (sec >= 0);\n\n\treturn ov_time_seek (&data->vf, sec * time_scaler) ? -1 : sec;\n}\n\nstatic int vorbis_decode (void *prv_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n\tstruct vorbis_data *data = (struct vorbis_data *)prv_data;\n\tint ret;\n\tint current_section;\n\tint bitrate;\n\tvorbis_info *info;\n\n\tdecoder_error_clear (&data->error);\n\n\twhile (1) {\n#ifndef HAVE_TREMOR\n\t\tret = ov_read(&data->vf, buf, buf_len,\n\t\t              (SFMT_NE == SFMT_LE ? 0 : 1),\n\t\t              2, 1, &current_section);\n#else\n\t\tret = ov_read(&data->vf, buf, buf_len, &current_section);\n#endif\n\t\tif (ret == 0)\n\t\t\treturn 0;\n\t\tif (ret < 0) {\n\t\t\tdecoder_error (&data->error, ERROR_STREAM, 0,\n\t\t\t               \"Error in the stream!\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (current_section != data->last_section) {\n\t\t\tlogit (\"section change or first section\");\n\n\t\t\tdata->last_section = current_section;\n\t\t\tdata->tags_change = 1;\n\t\t\ttags_free (data->tags);\n\t\t\tdata->tags = tags_new ();\n\t\t\tget_comment_tags (&data->vf, data->tags);\n\t\t}\n\n\t\tinfo = ov_info (&data->vf, -1);\n\t\tassert (info != NULL);\n\t\tsound_params->channels = info->channels;\n\t\tsound_params->rate = info->rate;\n\t\tsound_params->fmt = SFMT_S16 | SFMT_NE;\n\n\t\t/* Update the bitrate information */\n\t\tbitrate = ov_bitrate_instant (&data->vf);\n\t\tif (bitrate > 0)\n\t\t\tdata->bitrate = bitrate / 1000;\n\n\t\tbreak;\n\t}\n\n\treturn ret;\n}\n\nstatic int vorbis_current_tags (void *prv_data, struct file_tags *tags)\n{\n\tstruct vorbis_data *data = (struct vorbis_data *)prv_data;\n\n\ttags_copy (tags, data->tags);\n\n\tif (data->tags_change) {\n\t\tdata->tags_change = 0;\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nstatic int vorbis_get_bitrate (void *prv_data)\n{\n\tstruct vorbis_data *data = (struct vorbis_data *)prv_data;\n\n\treturn data->bitrate;\n}\n\nstatic int vorbis_get_avg_bitrate (void *prv_data)\n{\n\tstruct vorbis_data *data = (struct vorbis_data *)prv_data;\n\n\treturn data->avg_bitrate;\n}\n\nstatic int vorbis_get_duration (void *prv_data)\n{\n\tstruct vorbis_data *data = (struct vorbis_data *)prv_data;\n\n\treturn data->duration;\n}\n\nstatic struct io_stream *vorbis_get_stream (void *prv_data)\n{\n\tstruct vorbis_data *data = (struct vorbis_data *)prv_data;\n\n\treturn data->stream;\n}\n\nstatic void vorbis_get_name (const char *unused ATTR_UNUSED, char buf[4])\n{\n\tstrcpy (buf, \"OGG\");\n}\n\nstatic int vorbis_our_format_ext (const char *ext)\n{\n\treturn !strcasecmp (ext, \"ogg\")\n\t\t|| !strcasecmp (ext, \"oga\");\n}\n\nstatic void vorbis_get_error (void *prv_data, struct decoder_error *error)\n{\n\tstruct vorbis_data *data = (struct vorbis_data *)prv_data;\n\n\tdecoder_error_copy (error, &data->error);\n}\n\nstatic int vorbis_our_mime (const char *mime)\n{\n\treturn !strcasecmp (mime, \"application/ogg\")\n\t\t|| !strncasecmp (mime, \"application/ogg;\", 16)\n\t\t|| !strcasecmp (mime, \"application/x-ogg\")\n\t\t|| !strncasecmp (mime, \"application/x-ogg;\", 18);\n}\n\nstatic struct decoder vorbis_decoder = {\n\tDECODER_API_VERSION,\n\tNULL,\n\tNULL,\n\tvorbis_open,\n\tvorbis_open_stream,\n\tvorbis_can_decode,\n\tvorbis_close,\n\tvorbis_decode,\n\tvorbis_seek,\n\tvorbis_tags,\n\tvorbis_get_bitrate,\n\tvorbis_get_duration,\n\tvorbis_get_error,\n\tvorbis_our_format_ext,\n\tvorbis_our_mime,\n\tvorbis_get_name,\n\tvorbis_current_tags,\n\tvorbis_get_stream,\n\tvorbis_get_avg_bitrate\n};\n\nstruct decoder *plugin_init ()\n{\n\treturn &vorbis_decoder;\n}\n\n/* Defined and true if the Vorbis decoder is using Tremor, otherwise\n * undefined.  This is used by the decoder plugin loader so it can\n * document which library is being used without requiring the decoder\n * and the loader be built with the same HAVE_TREMOR setting. */\n#ifdef HAVE_TREMOR\nconst bool vorbis_has_tremor = true;\n#endif\n"
  },
  {
    "path": "decoder_plugins/vorbis/vorbis.m4",
    "content": "dnl vorbis\n\nAC_ARG_WITH(vorbis, AS_HELP_STRING([--without-vorbis],\n                                   [Compile without Ogg Vorbis support]))\n\nif test \"x$with_vorbis\" == \"xtremor\"\nthen\n\tPKG_CHECK_MODULES(OGG_VORBIS,\n\t\t\t  [vorbisidec >= 1.0],\n\t\t\t  [AC_SUBST(OGG_VORBIS_LIBS)\n\t\t\t   AC_SUBST(OGG_VORBIS_CFLAGS)\n\t\t\t   AC_DEFINE([HAVE_TREMOR], 1, [Define if you integer Vorbis.])\n\t\t\t   want_vorbis=\"yes\"\n\t\t\t   DECODER_PLUGINS=\"$DECODER_PLUGINS vorbis(tremor)\"],\n\t\t\t  [true])\nelse\n\tif test \"x$with_vorbis\" != \"xno\"\n\tthen\n\t\tPKG_CHECK_MODULES(OGG_VORBIS,\n\t\t\t      [ogg >= 1.0 vorbis >= 1.0 vorbisfile >= 1.0],\n\t\t\t      [AC_SUBST(OGG_VORBIS_LIBS)\n\t\t\t       AC_SUBST(OGG_VORBIS_CFLAGS)\n\t\t\t       want_vorbis=\"yes\"\n\t\t\t       DECODER_PLUGINS=\"$DECODER_PLUGINS vorbis\"],\n\t\t\t      [true])\n\tfi\nfi\n\nAM_CONDITIONAL([BUILD_vorbis], [test \"$want_vorbis\"])\nAC_CONFIG_FILES([decoder_plugins/vorbis/Makefile])\n"
  },
  {
    "path": "decoder_plugins/wavpack/Makefile.am",
    "content": "lib_LTLIBRARIES = libwavpack_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibwavpack_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibwavpack_decoder_la_LIBADD = $(WAVPACK_LIBS)\nlibwavpack_decoder_la_CFLAGS = $(WAVPACK_CFLAGS) -I$(top_srcdir)\nlibwavpack_decoder_la_SOURCES = wavpack.c\n"
  },
  {
    "path": "decoder_plugins/wavpack/wavpack.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * libwavpack-plugin Copyright (C) 2006 Alexandrov Sergey <splav@unsorted.ru>\n * Enables MOC to play wavpack files (actually just a wrapper around\n * wavpack library).\n *\n * Structure of this plugin is an adaption of the libvorbis-plugin from\n * moc.\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <string.h>\n#include <strings.h>\n#include <stdint.h>\n#include <assert.h>\n#include <wavpack/wavpack.h>\n\n#define DEBUG\n\n#include \"common.h\" /* for xmalloc(), xstrdup() etc. */\n#include \"log.h\" /* for logit() and debug() */\n#include \"decoder.h\" /* required: provides decoder structure definition */\n#include \"io.h\" /* if you use io_*() functions to access files. */\n#include \"audio.h\" /* for sound_params structure */\n\nstruct wavpack_data\n{\n\tWavpackContext *wpc;\n\tint sample_num;\n\tint sample_rate;\n\tint avg_bitrate;\n\tint channels;\n\tint duration;\n\tint mode;\n\tstruct decoder_error error;\n\tint ok; /* was this stream successfully opened? */\n};\n\n\nstatic void wav_data_init (struct wavpack_data *data)\n{\n\tdata->sample_num = WavpackGetNumSamples (data->wpc);\n\tdata->sample_rate = WavpackGetSampleRate (data->wpc);\n\tdata->channels = WavpackGetReducedChannels (data->wpc);\n\tdata->duration = data->sample_num / data->sample_rate;\n\tdata->mode = WavpackGetMode (data->wpc);\n\tdata->avg_bitrate = WavpackGetAverageBitrate (data->wpc, 1) / 1000;\n\n\tdata->ok = 1;\n\tdebug (\"File opened. S_n %d. S_r %d. Time %d. Avg_Bitrate %d.\",\n\t\tdata->sample_num, data->sample_rate,\n\t\tdata->duration, data->avg_bitrate\n\t\t);\n}\n\n\nstatic void *wav_open (const char *file)\n{\n\tstruct wavpack_data *data;\n\tdata = (struct wavpack_data *)xmalloc (sizeof(struct wavpack_data));\n\tdata->ok = 0;\n\tdecoder_error_init (&data->error);\n\n\tint o_flags = OPEN_2CH_MAX | OPEN_WVC;\n\n\tchar wv_error[100];\n\n\tif ((data->wpc = WavpackOpenFileInput(file,\n\t\t\t\twv_error, o_flags, 0)) == NULL) {\n\t\tdecoder_error (&data->error, ERROR_FATAL, 0, \"%s\", wv_error);\n\t\tlogit (\"wv_open error: %s\", wv_error);\n\t}\n\telse\n\t\twav_data_init (data);\n\n\treturn data;\n}\n\nstatic void wav_close (void *prv_data)\n{\n\tstruct wavpack_data *data = (struct wavpack_data *)prv_data;\n\n\tif (data->ok) {\n\t\tWavpackCloseFile (data->wpc);\n\t}\n\n\tdecoder_error_clear (&data->error);\n\tfree (data);\n\tlogit (\"File closed\");\n}\n\nstatic int wav_seek (void *prv_data, int sec)\n{\n\tstruct wavpack_data *data = (struct wavpack_data *)prv_data;\n\n\tassert (sec >= 0);\n\n\tif (WavpackSeekSample (data->wpc, sec * data->sample_rate))\n\t\treturn sec;\n\n\tdecoder_error (&data->error, ERROR_FATAL, 0, \"Fatal seeking error!\");\n\treturn -1;\n}\n\n\nstatic int wav_get_bitrate (void *prv_data)\n{\n\tstruct wavpack_data *data = (struct wavpack_data *)prv_data;\n\n\tint bitrate;\n\tbitrate = WavpackGetInstantBitrate (data->wpc) / 1000;\n\n\treturn (bitrate == 0)? data->avg_bitrate : bitrate;\n}\n\nstatic int wav_get_avg_bitrate (void *prv_data)\n{\n\tstruct wavpack_data *data = (struct wavpack_data *)prv_data;\n\n\treturn data->avg_bitrate;\n}\n\nstatic int wav_get_duration (void *prv_data)\n{\n\tstruct wavpack_data *data = (struct wavpack_data *)prv_data;\n\treturn data->duration;\n}\n\nstatic void wav_get_error (void *prv_data, struct decoder_error *error)\n{\n\tstruct wavpack_data *data = (struct wavpack_data *)prv_data;\n\tdecoder_error_copy (error, &data->error);\n}\n\nstatic void wav_info (const char *file_name, struct file_tags *info,\n\t\tconst int tags_sel)\n{\n\tchar wv_error[100];\n\tchar *tag;\n\tint tag_len;\n\n\tWavpackContext *wpc;\n\n\twpc = WavpackOpenFileInput (file_name, wv_error, OPEN_TAGS, 0);\n\n\tif (wpc == NULL) {\n\t\tlogit (\"wv_open error: %s\", wv_error);\n\t\treturn;\n\t}\n\n\tint duration = WavpackGetNumSamples (wpc) / WavpackGetSampleRate (wpc);\n\n\tif(tags_sel & TAGS_TIME) {\n\t\tinfo->time = duration;\n\t\tinfo->filled |= TAGS_TIME;\n\t}\n\n\tif(tags_sel & TAGS_COMMENTS) {\n\t\tif ((tag_len = WavpackGetTagItem (wpc, \"title\", NULL, 0)) > 0) {\n\t\t\tinfo->title = (char *)xmalloc (++tag_len);\n\t\t\tWavpackGetTagItem (wpc, \"title\", info->title, tag_len);\n\t\t}\n\n\t\tif ((tag_len = WavpackGetTagItem (wpc, \"artist\", NULL, 0)) > 0) {\n\t\t\tinfo->artist = (char *)xmalloc (++tag_len);\n\t\t\tWavpackGetTagItem (wpc, \"artist\", info->artist, tag_len);\n\t\t}\n\n\t\tif ((tag_len = WavpackGetTagItem (wpc, \"album\", NULL, 0)) > 0) {\n\t\t\tinfo->album = (char *)xmalloc (++tag_len);\n\t\t\tWavpackGetTagItem (wpc, \"album\", info->album, tag_len);\n\t\t}\n\n\t\tif ((tag_len = WavpackGetTagItem (wpc, \"track\", NULL, 0)) > 0) {\n\t\t\ttag = (char *)xmalloc (++tag_len);\n\t\t\tWavpackGetTagItem (wpc, \"track\", tag, tag_len);\n\t\t\tinfo->track = atoi (tag);\n\t\t\tfree (tag);\n\t\t}\n\n\t\tinfo->filled |= TAGS_COMMENTS;\n\t}\n\n\tWavpackCloseFile (wpc);\n}\n\n\nstatic int wav_decode (void *prv_data, char *buf, int buf_len,\n\t\tstruct sound_params *sound_params)\n{\n\tstruct wavpack_data *data = (struct wavpack_data *)prv_data;\n\tint ret, i, s_num, iBps, oBps;\n\n\tint8_t *buf8 = (int8_t *)buf;\n\tint16_t *buf16 = (int16_t *)buf;\n\tint32_t *buf32 = (int32_t *)buf;\n\n\tiBps = data->channels * WavpackGetBytesPerSample (data->wpc);\n\toBps = (iBps == 6) ? 8 : iBps;\n\ts_num = buf_len / oBps;\n\n\tdecoder_error_clear (&data->error);\n\n\tint32_t *dbuf = (int32_t *)xcalloc (s_num, data->channels * 4);\n\n\tret = WavpackUnpackSamples (data->wpc, dbuf, s_num);\n\n\tif (ret == 0) {\n\t\tfree (dbuf);\n\t\treturn 0;\n\t}\n\n\tif (data->mode & MODE_FLOAT) {\n\t\tsound_params->fmt = SFMT_FLOAT;\n\t\tmemcpy (buf, dbuf, ret * oBps);\n\t} else\t{\n\t\tdebug (\"iBps %d\", iBps);\n\t\tswitch (iBps / data->channels){\n\t\tcase 4: for (i = 0; i < ret * data->channels; i++)\n\t\t\t\tbuf32[i] = dbuf[i];\n\t\t\tsound_params->fmt = SFMT_S32 | SFMT_NE;\n\t\t\tbreak;\n\t\tcase 3: for (i = 0; i < ret * data->channels; i++)\n\t\t\t\tbuf32[i] = dbuf[i] * 256;\n\t\t\tsound_params->fmt = SFMT_S32 | SFMT_NE;\n\t\t\tbreak;\n\t\tcase 2: for (i = 0; i < ret * data->channels; i++)\n\t\t\t\tbuf16[i] = dbuf[i];\n\t\t\tsound_params->fmt = SFMT_S16 | SFMT_NE;\n\t\t\tbreak;\n\t\tcase 1: for (i = 0; i < ret * data->channels; i++)\n\t\t\t\tbuf8[i] = dbuf[i];\n\t\t\tsound_params->fmt = SFMT_S8 | SFMT_NE;\n\t\t}\n\t}\n\n\tsound_params->channels = data->channels;\n\tsound_params->rate = data->sample_rate;\n\n\tfree (dbuf);\n\treturn ret * oBps ;\n}\n\nstatic int wav_our_mime (const char *mime ATTR_UNUSED)\n{\n\t/* We don't support internet streams for now. */\n#if 0\n\treturn !strcasecmp (mime, \"audio/x-wavpack\")\n\t\t|| !strncasecmp (mime, \"audio/x-wavpack;\", 16)\n#endif\n\n\treturn 0;\n}\n\nstatic void wav_get_name (const char *unused ATTR_UNUSED, char buf[4])\n{\n\tstrcpy (buf, \"WV\");\n}\n\nstatic int wav_our_format_ext(const char *ext)\n{\n  return\n    !strcasecmp (ext, \"WV\");\n}\n\nstatic struct decoder wv_decoder = {\n        DECODER_API_VERSION,\n        NULL,//wav_init\n        NULL,//wav_destroy\n        wav_open,\n        NULL,//wav_open_stream,\n        NULL,//wav_can_decode,\n        wav_close,\n        wav_decode,\n        wav_seek,\n        wav_info,\n        wav_get_bitrate,\n        wav_get_duration,\n        wav_get_error,\n        wav_our_format_ext,\n        wav_our_mime,\n        wav_get_name,\n        NULL,//wav_current_tags,\n        NULL,//wav_get_stream\n        wav_get_avg_bitrate\n};\n\nstruct decoder *plugin_init ()\n{\n        return &wv_decoder;\n}\n"
  },
  {
    "path": "decoder_plugins/wavpack/wavpack.m4",
    "content": "dnl wavpack\n\nAC_ARG_WITH(wavpack, AS_HELP_STRING([--without-wavpack],\n                                    [Compile without WavPack support]))\n\nif test \"x$with_wavpack\" != \"xno\"\nthen\n\tPKG_CHECK_MODULES(WAVPACK, [wavpack >= 4.31],\n\t\t\t[AC_SUBST(WAVPACK_LIBS)\n\t\t\tAC_SUBST(WAVPACK_CFLAGS)\n\t\t\twant_wavpack=\"yes\"\n\t\t\tDECODER_PLUGINS=\"$DECODER_PLUGINS wavpack\"],\n\t\t\t[true])\nfi\n\nAM_CONDITIONAL([BUILD_wavpack], [test \"$want_wavpack\"])\nAC_CONFIG_FILES([decoder_plugins/wavpack/Makefile])\n"
  },
  {
    "path": "doxy_pages/decoder_api.doxy",
    "content": "/** \\page decoder_api Decoder API\n\n\\section decoder_api_introduction Introduction\n\nThis document is intended to make it easier to add support for new file\nformats to MOC.  It documents the requirements of decoder plugins and\nsome useful functions.  It also says how to start.\n\n\\section decoder_plugin_idea The idea\n\nEach file format is supported by code in a plugin.  It's something similar to\nplugins in popular audio players like XMMS with exception that there is no\nseparate library and dedicated header files to build such a plugin.  It means\nthat to create a plugin you need to add it to MOC's source directory.  It\nwill be built with the program and a file named like my_plugin.so will be\ncreated and placed in the plugin directory during installation.\n\nSo why plugins?  The idea is to avoid the dependency of the mocp binary on\nvarious libraries used just to support some exotic formats.  If a plugin\nwith an unresolved dependency is found at startup it's just not loaded.\n\n\\section decoder_plugin_how_to_add_source How to add the plugin code\n\nLet's create a plugin named myplug.  First create a directory in the\ndecoder_plugins directory named myplug; you will place the sources there.\nLet's have 2 source files: myplug1.c and myplug2.c in your plugin's directory.\nNow you must create a Makefile.am file there that contains the following lines:\n\n\\code\nlib_LTLIBRARIES = libmyplug_decoder.la\nlibdir = $(plugindir)/$(DECODER_PLUGIN_DIR)\nlibmyplug_decoder_la_LDFLAGS = @PLUGIN_LDFLAGS@\nlibmyplug_decoder_la_LIBADD = $(MYPLUG_LIBS)\nlibmyplug_decoder_la_CFLAGS = $(MYPLUG_CFLAGS) -I$(top_srcdir)\nlibmyplug_decoder_la_SOURCES = myplug1.c myplug2.c\n\\endcode\n\nNext you have to tell the build system to include your newly created directory\nby editting the file Makefile.am in the decoder_plugins directory.  You'll\nsee plenty of examples there, but your new entry will look like this:\n\n\\code\nif BUILD_myplug\n    SUBDIRS += myplug\nendif\n\\endcode\n\nAlmost done, now the hard part.  As you may have already noticed,\nthe code in Makefile.am uses two useful variables: MYPLUG_CFLAGS and\nMYPLUG_LIBS.  They contain additional compiler and linker flags used to\ncompile your plugin.  They are set by the configure script.  You must\ninclude a configure script fragment (myplug.m4 in this case) in your\nmyplug directory and modify decoder_plugins/decoders.m4 to include it.\n\nThe fragment needs to detect libraries and compiler flags needed to\nbuild the plugin, add myplug to the DECODER_PLUGINS variable and set\nthings up so the plugin can be built.  As an example, let's look at the\ncode detecting libFLAC which is using an autoconf macro provided by the\nFLAC library and is very simple:\n\n\\code\nAC_ARG_WITH(flac, AS_HELP_STRING([--without-flac],\n                                 [Compile without FLAC support]))\n\nif test \"x$with_flac\" != \"xno\"\nthen\n    AM_PATH_LIBFLAC([AC_SUBST(LIBFLAC_LIBS)\n                     AC_SUBST(LIBFLAC_CFLAGS)\n                     $want_flac=\"yes\"\n                     DECODER_PLUGINS=\"$DECODER_PLUGINS flac\"])\nfi\n\nAM_CONDITIONAL([BUILD_flac], [test \"$want_flac\"])\nAC_CONFIG_FILES([decoder_plugins/flac/Makefile])\n\\endcode\n\nIf your decoder uses packages which don't have pkg-config configurations,\nyou can use something like this:\n\n\\code\nAC_ARG_WITH(myplug, AS_HELP_STRING([--without-myplug],\n                                   [Compile without MyPlug support]))\n\nif test \"x$with_myplug\" != \"xno\"\nthen\n    AC_CHECK_LIB(required_library, SomeFunction, [myplug_OK=\"yes\"])\n    if test \"x$myplug_OK\" = \"xyes\"\n    then\n        MYPLUG_LIBS='-lrequired_library'\n        AC_SUBST(MYPLUG_LIBS)\n        MYPLUG_CFLAGS='-I/usr/include/required_library_includes'\n        AC_SUBST(MYPLUG_CFLAGS)\n        $want_myplug=\"yes\"\n        DECODER_PLUGINS=\"$DECODER_PLUGINS myplug\"\n    fi\nfi\n\nAM_CONDITIONAL([BUILD_myplug], [test \"$want_flac\"])\nAC_CONFIG_FILES([decoder_plugins/myplug/Makefile])\n\\endcode\n\nIn the absence of any library dependencies, this reduces to:\n\n\\code\nAC_ARG_WITH(myplug, AS_HELP_STRING([--without-myplug],\n                                   [Compile without MyPlug support]))\n\nif test \"x$with_myplug\" != \"xno\"\nthen\n    MYPLUG_LIBS='-lrequired_library'\n    AC_SUBST(MYPLUG_LIBS)\n    MYPLUG_CFLAGS='-I/usr/include/required_library_includes'\n    AC_SUBST(MYPLUG_CFLAGS)\n    $want_myplug=\"yes\"\n    DECODER_PLUGINS=\"$DECODER_PLUGINS myplug\"\nfi\n\nAM_CONDITIONAL([BUILD_myplug], [test \"$want_flac\"])\nAC_CONFIG_FILES([decoder_plugins/myplug/Makefile])\n\\endcode\n\nNow you must recreate the configure script and all Makefile.in files:\nrun 'autoreconf [-i]' (it requires autoconf, automake and libtool -- the\nnewest versions of these tools are preferred; older versions may not work).\n\n\\section decoder_plugin_how_to_begin First code\n\nThe only public (not static) function that your plugin must contain is a\nfunction named plugin_init.  It returns a structure (struct decoder) with\npointers to functions provided by the plugin.  Let's look at ogg's plugin\ncode:\n\n\\code\nstatic struct decoder ogg_decoder = {\n\tDECODER_API_VERSION,\n\togg_open,\n\togg_open_stream,\n\togg_can_decode,\n\togg_close,\n\togg_decode,\n\togg_seek,\n\togg_info,\n\togg_get_bitrate,\n\togg_get_duration,\n\togg_get_error,\n\togg_our_format_ext,\n\togg_our_mime,\n\togg_get_name,\n\togg_current_tags,\n\togg_get_stream\n};\n\nstruct decoder *plugin_init ()\n{\n\treturn &ogg_decoder;\n}\n\\endcode\n\nThis is something that every plugin must have.\n\nAlso, the first lines of code in each C source file should be:\n\n\\code\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\\endcode\n\nYou may place comments above them (for example, containing copyright\ninformation).\n\nIncludes you need are:\n\n\\verbatim\n#include \"main.h\"     /* for xmalloc(), xstrdup() etc. */\n#include \"log.h\"      /* for logit() and debug() */\n#include \"decoder.h\"  /* required: provides decoder structure definition */\n#include \"io.h\"       /* if you use io_*() functions to access files. */\n#include \"audio.h\"    /* for sound_params structure */\n\\endverbatim\n\n\\section decoder_plugin_requirements Requirements\n\nFirst thing you must remember is that your plugin is running in multi-thread\nprogram.  When it is asked to open a file it must return a pointer to the\nstructure containing your private data.  This pointer will be passed to each\nfunction associated with the stream.  You must not use any global or static\n(in function) variables, because more than one file your plugin handles may\nbe open at a time (for example, when precaching).\n\nWhen you operate on a file it is preferred (but not required) that you use\nio_*() functions from io.h instead of standard open(), read(), write()\netc.  MOC's I/O functions provide sophisticated features: buffering in a\nseparate thread and reading from Internet streams.  They were made to\nbe used like standard UNIX input/output functions: they take similar\nparameters and return similar values.\n\n\\section decoder_plugin_useful_api The API\n\nNow it's time to read the documentation and find out what all that\nfunctions must do and what functions you may use to achieve that.\n\n\\li \\ref decoder\n\\li \\ref decoder_error_funcs\n\nFor debugging purposes you may use two macros: debug() and logit() from log.h.\nThe first one does nothing if you don't define DEBUG before including log.h,\nand works like logit() if you do.  logit() works like printf(), but it only\nprints to the log file when the --debug command line option is used.\nAlso, logit() includes time and source file name of the\nfunction name where it was invoked.  You don't need to add the end of line\ncharacter to make lines, each invocation of logit() makes a new line.\n\nIt is also preferred that you use xmalloc(), xrealloc(), xstrdup() functions\nfrom common.h instead of those without x.  They check if allocation of memory\nsucceeds, so you don't need to do that.\n\n\\section decoder_plugin_useful_testing Testing and Debugging\n\nThere is a script in the 'tools' subdirectory which will help you to test\nyour decoder.  It checks that the samples which arrive at MOC's player\ncode are the same as those produced by the library the decoder is using.\nIt does this by having MOC compute the MD5 sum of the audio file as it is\nplaying it and writing the result to the server's log file.  The log file\nis then read by the 'md5check.sh' tool and the MD5 sum checked against one\ncomputed from the audio file using the library's native decoding program.\n\nYou will need to add a new function and case item to call it to the script\nso it can recognise your new decoder's library.  It is important to use\nthe program associated with the decoder library if one exists because\nother programs may not produce exactly the same samples, particularly\nfor lossy formats.  It is also possible that bugs in the library mean\nthe samples produced are not correct, but the tool is intended to test\nthe passage of the samples through the driver and not the fidelity of\nthe library used.\n\n*/\n"
  },
  {
    "path": "doxy_pages/main_page.doxy",
    "content": "/** \\mainpage Internal API documentation\n\n\\author Damian Pietras <daper (at) daper (dot) net>\n\n\\section introduction Introduction\n\nThis document describes the part of the internal API that can be used to add\nsupport for a new file format or sound output method.  Everything that is\nrequired or may be useful is documented, the rest changes too often.\n\nI hope this documentation is up-to-date enough and contains sufficiently few\nerrors to make it useful... .\n\n\\section documented_parts Documented parts of the API\n\nThere are two main parts of the API you in which you could be interested:\n\n\\li \\ref decoder_api\n\\li \\ref sound_output_driver_api\n\n*/\n"
  },
  {
    "path": "doxy_pages/sound_output_driver_api.doxy",
    "content": "/** \\page sound_output_driver_api Sound output driver API\n\n\\section sound_output_driver_api_introduction Introduction\n\nThis is the sound output driver API.  Sorry, no introduction here, you must\nsee hw_funcs description and some examples.  (null_out.c does nothing, but\nit's the simplest driver and can be used as a template.)\n\n*/\n"
  },
  {
    "path": "equalizer.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004-2008 Damian Pietras <daper@daper.net>\n *\n * Equalizer-extension Copyright (C) 2008 Hendrik Iben <hiben@tzi.de>\n * Provides a parametric biquadratic equalizer.\n *\n * This code is based on the 'Cookbook formulae for audio EQ biquad filter\n * coefficients' by Robert Bristow-Johnson.\n * http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt\n *\n * TODO:\n * - Merge somehow with softmixer code to avoid multiple endianness\n *   conversions.\n * - Implement equalizer routines for integer samples... conversion\n *   to float (and back) is lazy...\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n  #include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <string.h>\n#include <strings.h>\n#include <assert.h>\n#include <stdint.h>\n#include <math.h>\n#include <stdlib.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <locale.h>\n\n#include \"common.h\"\n#include \"audio.h\"\n#include \"audio_conversion.h\"\n#include \"options.h\"\n#include \"log.h\"\n#include \"files.h\"\n#include \"equalizer.h\"\n\n#define TWOPI (2.0 * M_PI)\n\n#define NEWLINE 0x0A\n#define CRETURN 0x0D\n#define SPACE   0x20\n\n#define EQSET_HEADER \"EQSET\"\n\n#define EQUALIZER_CFG_ACTIVE \"Active:\"\n#define EQUALIZER_CFG_PRESET \"Preset:\"\n#define EQUALIZER_CFG_MIXIN \"Mixin:\"\n\n#define EQUALIZER_SAVE_FILE \"equalizer\"\n#define EQUALIZER_SAVE_OPTION \"Equalizer_SaveState\"\n\ntypedef struct t_biquad t_biquad;\n\nstruct t_biquad\n{\n  float a0, a1, a2, a3, a4;\n  float x1, x2, y1, y2;\n  float cf, bw, gain, srate;\n  int israte;\n};\n\ntypedef struct t_eq_setup t_eq_setup;\n\nstruct t_eq_setup\n{\n  char *name;\n  float preamp;\n  int bcount;\n  float *cf;\n  float *bw;\n  float *dg;\n};\n\ntypedef struct t_eq_set t_eq_set;\n\nstruct t_eq_set\n{\n  char *name;\n  int channels;\n  float preamp;\n  int bcount;\n  t_biquad *b;\n};\n\ntypedef struct t_eq_set_list t_eq_set_list;\n\nstruct t_eq_set_list\n{\n  t_eq_set *set;\n  t_eq_set_list *prev, *next;\n};\n\ntypedef struct t_active_set t_active_set;\n\nstruct t_active_set\n{\n  int srate;\n  t_eq_set *set;\n};\n\ntypedef struct t_eq_settings t_eq_settings;\n\nstruct t_eq_settings\n{\n  char *preset_name;\n  int bcount;\n  float *gain;\n  t_eq_settings *next;\n};\n\n/* config processing */\nstatic char *skip_line(char *s);\nstatic char *skip_whitespace(char *s);\nstatic int read_float(char *s, float *f, char **endp);\nstatic int read_setup(char *name, char *desc, t_eq_setup **sp);\nstatic void equalizer_adjust_preamp();\nstatic void equalizer_read_config();\nstatic void equalizer_write_config();\n\n/* biquad application */\nstatic inline void apply_biquads(float *src, float *dst, int channels, int len, t_biquad *b, int blen);\n\n/* biquad filter creation */\nstatic t_biquad *mk_biquad(float dbgain, float cf, float srate, float bw, t_biquad *b);\n\n/* equalizer list processing */\nstatic t_eq_set_list *append_eq_set(t_eq_set *eqs, t_eq_set_list *l);\nstatic void clear_eq_set(t_eq_set_list *l);\n\n/* sound processing */\nstatic void equ_process_buffer_u8(uint8_t *buf, size_t samples);\nstatic void equ_process_buffer_s8(int8_t *buf, size_t samples);\nstatic void equ_process_buffer_u16(uint16_t *buf, size_t samples);\nstatic void equ_process_buffer_s16(int16_t *buf, size_t samples);\nstatic void equ_process_buffer_u32(uint32_t *buf, size_t samples);\nstatic void equ_process_buffer_s32(int32_t *buf, size_t samples);\nstatic void equ_process_buffer_float(float *buf, size_t samples);\n\n/* static global variables */\nstatic t_eq_set_list equ_list, *current_equ;\n\nstatic int sample_rate, equ_active, equ_channels;\n\nstatic float mixin_rate, r_mixin_rate;\nstatic float preamp, preampf;\n\nstatic char *eqsetdir;\n\nstatic char *config_preset_name;\n\n/* public functions */\nint equalizer_is_active()\n{\n  return equ_active?1:0;\n}\n\nint equalizer_set_active(int active)\n{\n  return equ_active = active?1:0;\n}\n\nchar *equalizer_current_eqname()\n{\n  if(equ_active && current_equ && current_equ->set)\n  {\n    return xstrdup(current_equ->set->name);\n  }\n\n  return xstrdup(\"off\");\n}\n\nvoid equalizer_next()\n{\n  if(current_equ)\n  {\n    if(current_equ->next)\n    {\n      current_equ = current_equ->next;\n    }\n    else\n    {\n      current_equ = &equ_list;\n    }\n\n    if(!current_equ->set && !(current_equ == &equ_list && !current_equ->next))\n      equalizer_next();\n  }\n\n  equalizer_adjust_preamp();\n}\n\nvoid equalizer_prev()\n{\n  if(current_equ)\n  {\n    if(current_equ->prev)\n    {\n      current_equ = current_equ->prev;\n    }\n    else\n    {\n      while(current_equ->next)\n        current_equ = current_equ->next;\n    }\n\n    if(!current_equ->set && !(current_equ == &equ_list && !current_equ->next))\n      equalizer_prev();\n  }\n\n  equalizer_adjust_preamp();\n}\n\n/* biquad functions */\n\n/* Create a Peaking EQ Filter.\n * See 'Audio EQ Cookbook' for more information\n */\nstatic t_biquad *mk_biquad(float dbgain, float cf, float srate, float bw, t_biquad *b)\n{\n  if(b==NULL)\n    b = (t_biquad *)xmalloc(sizeof(t_biquad));\n\n  float A = powf(10.0f, dbgain / 40.0f);\n  float omega = TWOPI * cf / srate;\n  float sn = sin(omega);\n  float cs = cos(omega);\n  float alpha = sn * sinh(M_LN2 / 2.0f * bw * omega / sn);\n\n  float alpha_m_A = alpha * A;\n  float alpha_d_A = alpha / A;\n\n  float b0 = 1.0f + alpha_m_A;\n  float b1 = -2.0f * cs;\n  float b2 = 1.0f - alpha_m_A;\n  float a0 = 1.0f + alpha_d_A;\n  float a1 = b1;\n  float a2 = 1.0f - alpha_d_A;\n\n  b->a0 = b0 / a0;\n  b->a1 = b1 / a0;\n  b->a2 = b2 / a0;\n  b->a3 = a1 / a0;\n  b->a4 = a2 / a0;\n\n  b->x1 = 0.0f;\n  b->x2 = 0.0f;\n  b->y1 = 0.0f;\n  b->y2 = 0.0f;\n\n  b->cf = cf;\n  b->bw = bw;\n  b->srate = srate;\n  b->israte = (int)srate;\n  b->gain = dbgain;\n\n  return b;\n}\n\n/*\n * not used but keep as example use for biquad filter\nstatic inline void biquad(float *src, float *dst, int len, t_biquad *b)\n{\n  while(len-->0)\n  {\n    float s = *src++;\n    float f = s * b->a0 + b->a1 * b->x1 + b->a2 * b->x2 - b->a3 * b->y1 - b->a4 * b->y2;\n    *dst++=f;\n    b->x2 = b->x1;\n    b->x1 = s;\n    b->y2 = b->y1;\n    b->y1 = f;\n  }\n}\n*/\n\n/* Applies a set of biquadratic filters to a buffer of floating point\n * samples.\n * It is safe to have the same input and output buffer.\n *\n * blen is the sample-count ignoring channels (samples per channel * channels)\n */\nstatic inline void apply_biquads(float *src, float *dst, int channels, int len, t_biquad *b, int blen)\n{\n  int bi, ci, boffs, idx;\n  while(len>0)\n  {\n    boffs = 0;\n    for(ci=0; ci<channels; ci++)\n    {\n      float s = *src++;\n      float f = s;\n      for(bi=0; bi<blen; bi++)\n      {\n        idx = boffs + bi;\n        f =\n          s * b[idx].a0 \\\n          + b[idx].a1 * b[idx].x1 \\\n          + b[idx].a2 * b[idx].x2 \\\n          - b[idx].a3 * b[idx].y1 \\\n          - b[idx].a4 * b[idx].y2;\n        b[idx].x2 = b[idx].x1;\n        b[idx].x1 = s;\n        b[idx].y2 = b[idx].y1;\n        b[idx].y1 = f;\n        s = f;\n      }\n      *dst++=f;\n      boffs += blen;\n      len--;\n    }\n  }\n}\n\n/*\n preamping\n XMMS / Beep Media Player / Audacious use all the same code but\n do something I do not understand for preamping...\n\n actually preamping by X dB should be like\n sample * 10^(X/20)\n\n they do:\n sample * (( 1.0 + 0.0932471 * X + 0.00279033 * X^2 ) / 2)\n\n what are these constants ?\n the equations are not even close to each other in their results...\n - hiben\n*/\nstatic void equalizer_adjust_preamp()\n{\n  if(current_equ && current_equ->set)\n  {\n    preamp = current_equ->set->preamp;\n    preampf = powf(10.0f, current_equ->set->preamp / 20.0f);\n  }\n}\n\nstatic void equalizer_read_config()\n{\n  char *curloc = xstrdup(setlocale(LC_NUMERIC, NULL));\n  setlocale(LC_NUMERIC, \"C\"); // posix decimal point\n\n  char *sfile = xstrdup(create_file_name(\"equalizer\"));\n\n  FILE *cf = fopen(sfile, \"r\");\n\n  free (sfile);\n\n  if(cf==NULL)\n  {\n    logit (\"Unable to read equalizer configuration\");\n    if (curloc)\n\t    free (curloc);\n    return;\n  }\n\n  char *linebuffer = NULL;\n  char presetbuf[128];\n  presetbuf[0] = 0;\n\n  int tmp;\n  float ftmp;\n\n  while((linebuffer=read_line(cf)))\n  {\n    if(\n      strncasecmp\n      (\n          linebuffer\n        , EQUALIZER_CFG_ACTIVE\n        , strlen(EQUALIZER_CFG_ACTIVE)\n      ) == 0\n    )\n    {\n      if(sscanf(linebuffer, \"%*s %i\", &tmp)>0)\n        {\n          if(tmp>0)\n          {\n            equ_active = 1;\n          }\n          else\n          {\n            equ_active = 0;\n          }\n        }\n    }\n    if(\n      strncasecmp\n      (\n          linebuffer\n        , EQUALIZER_CFG_MIXIN\n        , strlen(EQUALIZER_CFG_MIXIN)\n      ) == 0\n    )\n    {\n      if(sscanf(linebuffer, \"%*s %f\", &ftmp)>0)\n        {\n          if(RANGE(0.0f, ftmp, 1.0f))\n          {\n            mixin_rate = ftmp;\n          }\n        }\n    }\n    if(\n      strncasecmp\n      (\n          linebuffer\n        , EQUALIZER_CFG_PRESET\n        , strlen(EQUALIZER_CFG_PRESET)\n      ) == 0\n    )\n    {\n      if(sscanf(linebuffer, \"%*s %127s\", presetbuf)>0)\n        {\n          /* ignore too large strings... */\n          if(strlen(presetbuf)<127)\n          {\n            if(config_preset_name)\n              free(config_preset_name);\n            config_preset_name = xstrdup(presetbuf);\n          }\n        }\n    }\n    free(linebuffer);\n  }\n\n  fclose(cf);\n\n  if (curloc) {\n\t  setlocale(LC_NUMERIC, curloc);\n\t  free (curloc);\n  }\n}\n\nstatic void equalizer_write_config()\n{\n  char *curloc = xstrdup(setlocale(LC_NUMERIC, NULL));\n  setlocale(LC_NUMERIC, \"C\"); /* posix decimal point */\n\n  char *cfname = create_file_name(EQUALIZER_SAVE_FILE);\n\n  FILE *cf = fopen(cfname, \"w\");\n\n  if(cf==NULL)\n  {\n    logit (\"Unable to write equalizer configuration\");\n    if (curloc)\n\t    free (curloc);\n    return;\n  }\n\n  fprintf(cf, \"%s %i\\n\", EQUALIZER_CFG_ACTIVE, equ_active);\n  if(current_equ && current_equ->set)\n    fprintf(cf, \"%s %s\\n\", EQUALIZER_CFG_PRESET, current_equ->set->name);\n  fprintf(cf, \"%s %f\\n\", EQUALIZER_CFG_MIXIN, mixin_rate);\n\n  fclose(cf);\n\n  if (curloc) {\n\t  setlocale(LC_NUMERIC, curloc);\n\t  free (curloc);\n  }\n\n  logit (\"Equalizer configuration written\");\n}\n\nvoid equalizer_init()\n{\n  equ_active = 1;\n\n  equ_list.set = NULL;\n  equ_list.next = NULL;\n  equ_list.prev = NULL;\n\n  sample_rate = 44100;\n\n  equ_channels = 2;\n\n  preamp = 0.0f;\n\n  preampf = powf(10.0f, preamp / 20.0f);\n\n  eqsetdir = xstrdup(create_file_name(\"eqsets\"));\n\n  config_preset_name = NULL;\n\n  mixin_rate = 0.25f;\n\n  equalizer_read_config();\n\n  r_mixin_rate = 1.0f - mixin_rate;\n\n  equalizer_refresh();\n\n  logit (\"Equalizer initialized\");\n}\n\nvoid equalizer_shutdown()\n{\n  if(options_get_bool(EQUALIZER_SAVE_OPTION))\n    equalizer_write_config();\n\n  clear_eq_set(&equ_list);\n\n  logit (\"Equalizer stopped\");\n}\n\nvoid equalizer_refresh()\n{\n  t_eq_setup *eqs = NULL;\n  char buf[1024];\n\n  char *current_set_name = NULL;\n\n  if(current_equ && current_equ->set)\n  {\n    current_set_name = xstrdup(current_equ->set->name);\n  }\n  else\n  {\n    if(config_preset_name)\n      current_set_name = xstrdup(config_preset_name);\n  }\n\n  clear_eq_set(&equ_list);\n\n  current_equ = NULL;\n\n  DIR *d = opendir(eqsetdir);\n\n  if(!d)\n  {\n    return;\n  }\n\n  struct dirent *de = readdir(d);\n  struct stat st;\n\n  t_eq_set_list *last_elem;\n\n  last_elem = &equ_list;\n\n  while(de)\n  {\n    sprintf(buf, \"eqsets/%s\", de->d_name);\n\n    char *filename = xstrdup(create_file_name(buf));\n\n    stat(filename, &st);\n\n    if( S_ISREG(st.st_mode) )\n    {\n      FILE *f = fopen(filename, \"r\");\n\n      if(f)\n      {\n        char filebuffer[4096];\n\n        char *fb = filebuffer;\n\n        int maxread = 4095 - (fb - filebuffer);\n\n        // read in whole file\n        while(!feof(f) && maxread>0)\n        {\n          maxread = 4095 - (fb - filebuffer);\n          int rb = fread(fb, sizeof(char), maxread, f);\n          fb+=rb;\n        }\n\n        fclose(f);\n\n        *fb = 0;\n        int r = read_setup(de->d_name, filebuffer, &eqs);\n\n        if(r==0)\n        {\n          int i, channel;\n          t_eq_set *eqset = (t_eq_set *)xmalloc(sizeof(t_eq_set));\n          eqset->b = (t_biquad *)xmalloc(sizeof(t_biquad)*eqs->bcount*equ_channels);\n\n          eqset->name = xstrdup(eqs->name);\n          eqset->preamp = eqs->preamp;\n          eqset->bcount = eqs->bcount;\n          eqset->channels = equ_channels;\n\n          for(i=0; i<eqs->bcount; i++)\n          {\n            mk_biquad(eqs->dg[i], eqs->cf[i], sample_rate, eqs->bw[i], &eqset->b[i]);\n\n            for(channel=1; channel<equ_channels; channel++)\n            {\n              eqset->b[channel*eqset->bcount + i] = eqset->b[i];\n            }\n          }\n\n          last_elem = append_eq_set(eqset, last_elem);\n\n          free(eqs->name);\n          free(eqs->cf);\n          free(eqs->bw);\n          free(eqs->dg);\n\n        }\n        else\n        {\n          switch(r)\n          {\n            case 0:\n              logit (\"This should not happen: No error but no EQSET was parsed: %s\", filename);\n              break;\n            case -1:\n              logit (\"Not an EQSET (empty file): %s\", filename);\n              break;\n            case -2:\n              logit (\"Not an EQSET (invalid header): %s\", filename);\n              break;\n            case -3:\n              logit (\"Error while parsing settings from EQSET: %s\", filename);\n              break;\n            default:\n              logit (\"Unknown error while parsing EQSET: %s\", filename);\n              break;\n          }\n        }\n\n        if(eqs)\n          free(eqs);\n\n        eqs = NULL;\n      }\n    }\n\n    free(filename);\n\n    de = readdir(d);\n  }\n\n  closedir(d);\n\n  current_equ = &equ_list;\n\n  if(current_set_name)\n  {\n    current_equ = &equ_list;\n\n    while(current_equ)\n    {\n      if(current_equ->set)\n      {\n        if(strcmp(current_set_name, current_equ->set->name)==0)\n          break;\n      }\n      current_equ = current_equ->next;\n    }\n\n    if(current_equ)\n    {\n      // only free name when EQ was found to allow logging\n      free(current_set_name);\n    }\n  }\n\n  if (!current_equ && current_set_name)\n  {\n    logit (\"EQ %s not found.\", current_set_name);\n    /* equalizer not found, pick next equalizer */\n    current_equ = &equ_list;\n    // free name now\n    free(current_set_name);\n  }\n  if(current_equ && !current_equ->set)\n    equalizer_next();\n\n  equalizer_adjust_preamp();\n}\n\n/* sound processing code */\nvoid equalizer_process_buffer(char *buf, size_t size, const struct sound_params *sound_params)\n{\n  debug (\"EQ Processing %zu bytes...\", size);\n\n  if(!equ_active || !current_equ || !current_equ->set)\n    return;\n\n  if(sound_params->rate != current_equ->set->b->israte || sound_params->channels != equ_channels)\n  {\n    logit (\"Recreating filters due to sound parameter changes...\");\n    sample_rate = sound_params->rate;\n    equ_channels = sound_params->channels;\n\n    equalizer_refresh();\n  }\n\n  long sound_endianness = sound_params->fmt & SFMT_MASK_ENDIANNESS;\n  long sound_format = sound_params->fmt & SFMT_MASK_FORMAT;\n\n  int samplewidth = sfmt_Bps(sound_format);\n  int is_float = (sound_params->fmt & SFMT_MASK_FORMAT) == SFMT_FLOAT;\n\n  int need_endianness_swap = 0;\n\n  if((sound_endianness != SFMT_NE) && (samplewidth > 1) && (!is_float))\n  {\n    need_endianness_swap = 1;\n  }\n\n  assert (size % (samplewidth * sound_params->channels) == 0);\n\n  /* setup samples to perform arithmetic */\n  if(need_endianness_swap)\n  {\n    debug (\"Converting endianness before mixing\");\n    if(samplewidth == 4)\n      audio_conv_bswap_32((int32_t *)buf, size / sizeof(int32_t));\n    else\n      audio_conv_bswap_16((int16_t *)buf, size / sizeof(int16_t));\n  }\n\n  switch(sound_format)\n  {\n    case SFMT_U8:\n      equ_process_buffer_u8((uint8_t *)buf, size);\n      break;\n    case SFMT_S8:\n      equ_process_buffer_s8((int8_t *)buf, size);\n      break;\n    case SFMT_U16:\n      equ_process_buffer_u16((uint16_t *)buf, size / sizeof(uint16_t));\n      break;\n    case SFMT_S16:\n      equ_process_buffer_s16((int16_t *)buf, size / sizeof(int16_t));\n      break;\n    case SFMT_U32:\n      equ_process_buffer_u32((uint32_t *)buf, size / sizeof(uint32_t));\n      break;\n    case SFMT_S32:\n      equ_process_buffer_s32((int32_t *)buf, size / sizeof(int32_t));\n      break;\n    case SFMT_FLOAT:\n      equ_process_buffer_float((float *)buf, size / sizeof(float));\n      break;\n  }\n\n  /* restore sample-endianness */\n  if(need_endianness_swap)\n  {\n    debug (\"Restoring endianness after mixing\");\n    if(samplewidth == 4)\n      audio_conv_bswap_32((int32_t *)buf, size / sizeof(int32_t));\n    else\n      audio_conv_bswap_16((int16_t *)buf, size / sizeof(int16_t));\n  }\n}\n\nstatic void equ_process_buffer_u8(uint8_t *buf, size_t samples)\n{\n  size_t i;\n  float *tmp;\n\n  debug (\"equalizing\");\n\n  tmp = (float *)xmalloc (samples * sizeof (float));\n\n  for(i=0; i<samples; i++)\n    tmp[i] = preampf * (float)buf[i];\n\n  apply_biquads(tmp, tmp, equ_channels, samples, current_equ->set->b, current_equ->set->bcount);\n\n  for(i=0; i<samples; i++)\n  {\n    tmp[i] = r_mixin_rate * tmp[i] + mixin_rate * buf[i];\n    tmp[i] = CLAMP(0, tmp[i], UINT8_MAX);\n    buf[i] = (uint8_t)tmp[i];\n  }\n\n  free(tmp);\n}\n\nstatic void equ_process_buffer_s8(int8_t *buf, size_t samples)\n{\n  size_t i;\n  float *tmp;\n\n  debug (\"equalizing\");\n\n  tmp = (float *)xmalloc (samples * sizeof (float));\n\n  for(i=0; i<samples; i++)\n    tmp[i] = preampf * (float)buf[i];\n\n  apply_biquads(tmp, tmp, equ_channels, samples, current_equ->set->b, current_equ->set->bcount);\n\n  for(i=0; i<samples; i++)\n  {\n    tmp[i] = r_mixin_rate * tmp[i] + mixin_rate * buf[i];\n    tmp[i] = CLAMP(INT8_MIN, tmp[i], INT8_MAX);\n    buf[i] = (int8_t)tmp[i];\n  }\n\n  free(tmp);\n}\n\nstatic void equ_process_buffer_u16(uint16_t *buf, size_t samples)\n{\n  size_t i;\n  float *tmp;\n\n  debug (\"equalizing\");\n\n  tmp = (float *)xmalloc (samples * sizeof (float));\n\n  for(i=0; i<samples; i++)\n    tmp[i] = preampf * (float)buf[i];\n\n  apply_biquads(tmp, tmp, equ_channels, samples, current_equ->set->b, current_equ->set->bcount);\n\n  for(i=0; i<samples; i++)\n  {\n    tmp[i] = r_mixin_rate * tmp[i] + mixin_rate * buf[i];\n    tmp[i] = CLAMP(0, tmp[i], UINT16_MAX);\n    buf[i] = (uint16_t)tmp[i];\n  }\n\n  free(tmp);\n}\n\nstatic void equ_process_buffer_s16(int16_t *buf, size_t samples)\n{\n  size_t i;\n  float *tmp;\n\n  debug (\"equalizing\");\n\n  tmp = (float *)xmalloc (samples * sizeof (float));\n\n  for(i=0; i<samples; i++)\n    tmp[i] = preampf * (float)buf[i];\n\n  apply_biquads(tmp, tmp, equ_channels, samples, current_equ->set->b, current_equ->set->bcount);\n\n  for(i=0; i<samples; i++)\n  {\n    tmp[i] = r_mixin_rate * tmp[i] + mixin_rate * buf[i];\n    tmp[i] = CLAMP(INT16_MIN, tmp[i], INT16_MAX);\n    buf[i] = (int16_t)tmp[i];\n  }\n\n  free(tmp);\n}\n\nstatic void equ_process_buffer_u32(uint32_t *buf, size_t samples)\n{\n  size_t i;\n  float *tmp;\n\n  debug (\"equalizing\");\n\n  tmp = (float *)xmalloc (samples * sizeof (float));\n\n  for(i=0; i<samples; i++)\n    tmp[i] = preampf * (float)buf[i];\n\n  apply_biquads(tmp, tmp, equ_channels, samples, current_equ->set->b, current_equ->set->bcount);\n\n  for(i=0; i<samples; i++)\n  {\n    tmp[i] = r_mixin_rate * tmp[i] + mixin_rate * buf[i];\n    tmp[i] = CLAMP(0, tmp[i], UINT32_MAX);\n    buf[i] = (uint32_t)tmp[i];\n  }\n\n  free(tmp);\n}\n\nstatic void equ_process_buffer_s32(int32_t *buf, size_t samples)\n{\n  size_t i;\n  float *tmp;\n\n  debug (\"equalizing\");\n\n  tmp = (float *)xmalloc (samples * sizeof (float));\n\n  for(i=0; i<samples; i++)\n    tmp[i] = preampf * (float)buf[i];\n\n  apply_biquads(tmp, tmp, equ_channels, samples, current_equ->set->b, current_equ->set->bcount);\n\n  for(i=0; i<samples; i++)\n  {\n    tmp[i] = r_mixin_rate * tmp[i] + mixin_rate * buf[i];\n    tmp[i] = CLAMP(INT32_MIN, tmp[i], INT32_MAX);\n    buf[i] = (int32_t)tmp[i];\n  }\n\n  free(tmp);\n}\n\nstatic void equ_process_buffer_float(float *buf, size_t samples)\n{\n  size_t i;\n  float *tmp;\n\n  debug (\"equalizing\");\n\n  tmp = (float *)xmalloc (samples * sizeof (float));\n\n  for(i=0; i<samples; i++)\n    tmp[i] = preampf * (float)buf[i];\n\n  apply_biquads(tmp, tmp, equ_channels, samples, current_equ->set->b, current_equ->set->bcount);\n\n  for(i=0; i<samples; i++)\n  {\n    tmp[i] = r_mixin_rate * tmp[i] + mixin_rate * buf[i];\n    tmp[i] = CLAMP(-1.0f, tmp[i], 1.0f);\n    buf[i] = tmp[i];\n  }\n\n  free(tmp);\n}\n\n/* equalizer list maintenance */\nstatic t_eq_set_list *append_eq_set(t_eq_set *eqs, t_eq_set_list *l)\n{\n  if(l->set == NULL)\n  {\n    l->set = eqs;\n  }\n  else\n  {\n    if(l->next)\n    {\n      append_eq_set(eqs, l->next);\n    }\n    else\n    {\n      l->next = (t_eq_set_list *)xmalloc(sizeof(t_eq_set_list));\n      l->next->set = NULL;\n      l->next->next = NULL;\n      l->next->prev = l;\n      l = append_eq_set(eqs, l->next);\n    }\n  }\n\n  return l;\n}\n\nstatic void clear_eq_set(t_eq_set_list *l)\n{\n  if(l->set)\n  {\n    free(l->set->name);\n    free(l->set->b);\n    free(l->set);\n    l->set = NULL;\n  }\n  if(l->next)\n  {\n    clear_eq_set(l->next);\n    free(l->next);\n    l->next = NULL;\n  }\n}\n\n/* parsing stuff */\nstatic int read_setup(char *name, char *desc, t_eq_setup **sp)\n{\n  char *curloc = xstrdup(setlocale(LC_NUMERIC, NULL));\n  setlocale(LC_NUMERIC, \"C\"); // posix decimal point\n\n  t_eq_setup *s = *sp;\n\n  desc = skip_whitespace(desc);\n\n  if(!*desc)\n  {\n\t\tif (curloc)\n\t\t\tfree (curloc);\n    return -1;\n  }\n\n  if(strncasecmp(desc, EQSET_HEADER, sizeof(EQSET_HEADER)-1))\n  {\n\t\tif (curloc)\n\t\t\tfree (curloc);\n    return -2;\n  }\n\n  desc+=5;\n\n  desc = skip_whitespace(skip_line(desc));\n\n  if(s==NULL)\n  {\n    s=(t_eq_setup *)xmalloc(sizeof(t_eq_setup));\n    *sp = s;\n  }\n\n  s->name = xstrdup(name);\n  s->bcount = 0;\n  s->preamp = 0.0f;\n  int max_values = 16;\n  s->cf = (float *)xmalloc(max_values*sizeof(float));\n  s->bw = (float *)xmalloc(max_values*sizeof(float));\n  s->dg = (float *)xmalloc(max_values*sizeof(float));\n\n  int r;\n\n  while(*desc)\n  {\n    char *endp;\n\n    float cf = 0.0f;\n\n    r = read_float(desc, &cf, &endp);\n\n    if(r!=0)\n    {\n      free(s->name);\n      free(s->cf);\n      free(s->bw);\n      free(s->dg);\n\t\t\tif (curloc)\n\t\t\t\tfree (curloc);\n      return -3;\n    }\n\n    desc = skip_whitespace(endp);\n\n    float bw = 0.0f;\n\n    r = read_float(desc, &bw, &endp);\n\n    if(r!=0)\n    {\n      free(s->name);\n      free(s->cf);\n      free(s->bw);\n      free(s->dg);\n\t\t\tif (curloc)\n\t\t\t\tfree (curloc);\n      return -3;\n    }\n\n    desc = skip_whitespace(endp);\n\n    float dg = 0.0f;\n\n    /* 0Hz means preamp, only one parameter then */\n    if(cf!=0.0f)\n    {\n      r = read_float(desc, &dg, &endp);\n\n      if(r!=0)\n      {\n        free(s->name);\n        free(s->cf);\n        free(s->bw);\n        free(s->dg);\n\t\t\t\tif (curloc)\n\t\t\t\t\tfree (curloc);\n        return -3;\n      }\n\n      desc = skip_whitespace(endp);\n\n      if(s->bcount>=(max_values-1))\n      {\n        max_values*=2;\n        s->cf=xrealloc(s->cf, max_values*sizeof(float));\n        s->bw=xrealloc(s->bw, max_values*sizeof(float));\n        s->dg=xrealloc(s->dg, max_values*sizeof(float));\n      }\n\n      s->cf[s->bcount]=cf;\n      s->bw[s->bcount]=bw;\n      s->dg[s->bcount]=dg;\n\n      s->bcount++;\n    }\n    else\n    {\n      s->preamp = bw;\n    }\n  }\n\n  if (curloc) {\n\t  setlocale(LC_NUMERIC, curloc); // posix decimal point\n\t  free (curloc);\n  }\n\n  return 0;\n}\n\nstatic char *skip_line(char *s)\n{\n  int dos_line = 0;\n  while(*s && (*s!=CRETURN && *s!=NEWLINE) )\n    s++;\n\n  if(*s==CRETURN)\n    dos_line = 1;\n\n  if(*s)\n    s++;\n\n  if(dos_line && *s==NEWLINE)\n    s++;\n\n  return s;\n}\n\nstatic char *skip_whitespace(char *s)\n{\n  while(*s && (*s<=SPACE))\n    s++;\n\n  if(!*s)\n    return s;\n\n  if(*s=='#')\n  {\n    s = skip_line(s);\n\n    s = skip_whitespace(s);\n  }\n\n  return s;\n}\n\nstatic int read_float(char *s, float *f, char **endp)\n{\n  errno = 0;\n\n  float t = strtof(s, endp);\n\n  if(errno==ERANGE)\n    return -1;\n\n  if(*endp == s)\n    return -2;\n\n  *f = t;\n\n  return 0;\n}\n"
  },
  {
    "path": "equalizer.h",
    "content": "#ifndef EQUALIZER_H\n#define EQUALIZER_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid equalizer_init();\nvoid equalizer_shutdown();\nvoid equalizer_process_buffer(char *buf, size_t size, const struct sound_params *sound_params);\nvoid equalizer_refresh();\nint equalizer_is_active();\nint equalizer_set_active(int active);\nchar *equalizer_current_eqname();\nvoid equalizer_next();\nvoid equalizer_prev();\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "fifo_buf.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stddef.h>\n#include <sys/types.h>\n#include <assert.h>\n#include <string.h>\n\n#include \"common.h\"\n#include \"fifo_buf.h\"\n\nstruct fifo_buf\n{\n\tint size;                           /* Size of the buffer */\n\tint pos;                            /* Current position */\n\tint fill;                           /* Current fill */\n\tchar buf[];                         /* The buffer content */\n};\n\n/* Initialize and return a new fifo_buf structure of the size requested. */\nstruct fifo_buf *fifo_buf_new (const size_t size)\n{\n\tstruct fifo_buf *b;\n\n\tassert (size > 0);\n\n\tb = xmalloc (offsetof (struct fifo_buf, buf) + size);\n\n\tb->size = size;\n\tb->pos = 0;\n\tb->fill = 0;\n\n\treturn b;\n}\n\n/* Destroy the buffer object. */\nvoid fifo_buf_free (struct fifo_buf *b)\n{\n\tassert (b != NULL);\n\n\tfree (b);\n}\n\n/* Put data into the buffer. Returns number of bytes actually put. */\nsize_t fifo_buf_put (struct fifo_buf *b, const char *data, size_t size)\n{\n\tsize_t written = 0;\n\n\tassert (b != NULL);\n\tassert (b->buf != NULL);\n\n\twhile (b->fill < b->size && written < size) {\n\t\tsize_t write_from;\n\t\tsize_t to_write;\n\n\t\tif (b->pos + b->fill < b->size) {\n\t\t\twrite_from = b->pos + b->fill;\n\t\t\tto_write = b->size - (b->pos + b->fill);\n\t\t}\n\t\telse {\n\t\t\twrite_from = b->fill - b->size + b->pos;\n\t\t\tto_write = b->size - b->fill;\n\t\t}\n\n\t\tif (to_write > size - written)\n\t\t\tto_write = size - written;\n\n\t\tmemcpy (b->buf + write_from, data + written, to_write);\n\t\tb->fill += to_write;\n\t\twritten += to_write;\n\t}\n\n\treturn written;\n}\n\n/* Copy data from the beginning of the buffer to the user buffer. Returns the\n * number of bytes copied. */\nsize_t fifo_buf_peek (struct fifo_buf *b, char *user_buf, size_t user_buf_size)\n{\n\tsize_t user_buf_pos = 0, written = 0;\n\tssize_t left, pos;\n\n\tassert (b != NULL);\n\tassert (b->buf != NULL);\n\n\tleft = b->fill;\n\tpos = b->pos;\n\n\twhile (left && written < user_buf_size) {\n\t\tsize_t to_copy = pos + left <= b->size\n\t\t\t? left : b->size - pos;\n\n\t\tif (to_copy > user_buf_size - written)\n\t\t\tto_copy = user_buf_size - written;\n\n\t\tmemcpy (user_buf + user_buf_pos, b->buf + pos, to_copy);\n\t\tuser_buf_pos += to_copy;\n\t\twritten += to_copy;\n\n\t\tleft -= to_copy;\n\t\tpos += to_copy;\n\t\tif (pos == b->size)\n\t\t\tpos = 0;\n\t}\n\n\treturn written;\n}\n\nsize_t fifo_buf_get (struct fifo_buf *b, char *user_buf, size_t user_buf_size)\n{\n\tsize_t user_buf_pos = 0, written = 0;\n\n\tassert (b != NULL);\n\tassert (b->buf != NULL);\n\n\twhile (b->fill && written < user_buf_size) {\n\t\tsize_t to_copy = b->pos + b->fill <= b->size\n\t\t\t? b->fill : b->size - b->pos;\n\n\t\tif (to_copy > user_buf_size - written)\n\t\t\tto_copy = user_buf_size - written;\n\n\t\tmemcpy (user_buf + user_buf_pos, b->buf + b->pos, to_copy);\n\t\tuser_buf_pos += to_copy;\n\t\twritten += to_copy;\n\n\t\tb->fill -= to_copy;\n\t\tb->pos += to_copy;\n\t\tif (b->pos == b->size)\n\t\t\tb->pos = 0;\n\t}\n\n\treturn written;\n}\n\n/* Get the amount of free space in the buffer. */\nsize_t fifo_buf_get_space (const struct fifo_buf *b)\n{\n\tassert (b != NULL);\n\tassert (b->buf != NULL);\n\n\treturn b->size - b->fill;\n\n}\n\nsize_t fifo_buf_get_fill (const struct fifo_buf *b)\n{\n\tassert (b != NULL);\n\treturn b->fill;\n}\n\nsize_t fifo_buf_get_size (const struct fifo_buf *b)\n{\n\tassert (b != NULL);\n\treturn b->size;\n}\n\nvoid fifo_buf_clear (struct fifo_buf *b)\n{\n\tassert (b != NULL);\n\tb->fill = 0;\n}\n"
  },
  {
    "path": "fifo_buf.h",
    "content": "#ifndef FIFO_BUF_H\n#define FIFO_BUF_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct fifo_buf;\n\nstruct fifo_buf *fifo_buf_new (const size_t size);\nvoid fifo_buf_free (struct fifo_buf *b);\nsize_t fifo_buf_put (struct fifo_buf *b, const char *data, size_t size);\nsize_t fifo_buf_get (struct fifo_buf *b, char *user_buf, size_t user_buf_size);\nsize_t fifo_buf_peek (struct fifo_buf *b, char *user_buf, size_t user_buf_size);\nsize_t fifo_buf_get_space (const struct fifo_buf *b);\nvoid fifo_buf_clear (struct fifo_buf *b);\nsize_t fifo_buf_get_fill (const struct fifo_buf *b);\nsize_t fifo_buf_get_size (const struct fifo_buf *b);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "files.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <assert.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <string.h>\n#include <strings.h>\n#include <errno.h>\n#include <stdlib.h>\n#include <dirent.h>\n\n#ifdef HAVE_LIBMAGIC\n#include <magic.h>\n#include <pthread.h>\n#endif\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"playlist.h\"\n#include \"lists.h\"\n#include \"interface.h\"\n#include \"decoder.h\"\n#include \"options.h\"\n#include \"files.h\"\n#include \"playlist_file.h\"\n#include \"log.h\"\n#include \"utf8.h\"\n\n#define READ_LINE_INIT_SIZE\t256\n\n#ifdef HAVE_LIBMAGIC\nstatic magic_t cookie = NULL;\nstatic char *cached_file = NULL;\nstatic char *cached_result = NULL;\n#endif\n\nvoid files_init ()\n{\n#ifdef HAVE_LIBMAGIC\n\tassert (cookie == NULL);\n\n\tcookie = magic_open (MAGIC_SYMLINK | MAGIC_MIME | MAGIC_ERROR |\n\t                     MAGIC_NO_CHECK_COMPRESS | MAGIC_NO_CHECK_ELF |\n\t                     MAGIC_NO_CHECK_TAR | MAGIC_NO_CHECK_TOKENS |\n\t                     MAGIC_NO_CHECK_FORTRAN | MAGIC_NO_CHECK_TROFF);\n\tif (cookie == NULL)\n\t\tlog_errno (\"Error allocating magic cookie\", errno);\n\telse if (magic_load (cookie, NULL) != 0) {\n\t\tlogit (\"Error loading magic database: %s\", magic_error (cookie));\n\t\tmagic_close (cookie);\n\t\tcookie = NULL;\n\t}\n#endif\n}\n\nvoid files_cleanup ()\n{\n#ifdef HAVE_LIBMAGIC\n\tfree (cached_file);\n\tcached_file = NULL;\n\tfree (cached_result);\n\tcached_result = NULL;\n\tmagic_close (cookie);\n\tcookie = NULL;\n#endif\n}\n\n/* Is the string a URL? */\ninline int is_url (const char *str)\n{\n\treturn !strncasecmp (str, \"http://\", sizeof (\"http://\") - 1)\n\t\t|| !strncasecmp (str, \"ftp://\", sizeof (\"ftp://\") - 1);\n}\n\n/* Return 1 if the file is a directory, 0 if not, -1 on error. */\nint is_dir (const char *file)\n{\n\tstruct stat file_stat;\n\n\tif (is_url (file))\n\t\treturn 0;\n\n\tif (stat (file, &file_stat) == -1) {\n\t\tchar *err = xstrerror (errno);\n\t\terror (\"Can't stat %s: %s\", file, err);\n\t\tfree (err);\n\t\treturn -1;\n\t}\n\treturn S_ISDIR(file_stat.st_mode) ? 1 : 0;\n}\n\n/* Return 1 if the file can be read by this user, 0 if not */\nint can_read_file (const char *file)\n{\n\treturn access(file, R_OK) == 0;\n}\n\nenum file_type file_type (const char *file)\n{\n\tstruct stat file_stat;\n\n\tassert (file != NULL);\n\n\tif (is_url(file))\n\t\treturn F_URL;\n\tif (stat(file, &file_stat) == -1)\n\t\treturn F_OTHER; /* Ignore the file if stat() failed */\n\tif (S_ISDIR(file_stat.st_mode))\n\t\treturn F_DIR;\n\tif (is_sound_file(file))\n\t\treturn F_SOUND;\n\tif (is_plist_file(file))\n\t\treturn F_PLAYLIST;\n\treturn F_OTHER;\n}\n\n/* Given a file name, return the mime type or NULL. */\nchar *file_mime_type (const char *file ASSERT_ONLY)\n{\n\tchar *result = NULL;\n\n\tassert (file != NULL);\n\n#ifdef HAVE_LIBMAGIC\n\tstatic pthread_mutex_t magic_mtx = PTHREAD_MUTEX_INITIALIZER;\n\n\tif (cookie != NULL) {\n\t\tLOCK(magic_mtx);\n\t\tif (cached_file && !strcmp (cached_file, file))\n\t\t\tresult = xstrdup (cached_result);\n\t\telse {\n\t\t\tfree (cached_file);\n\t\t\tfree (cached_result);\n\t\t\tcached_file = cached_result = NULL;\n\t\t\tresult = xstrdup (magic_file (cookie, file));\n\t\t\tif (result == NULL)\n\t\t\t\tlogit (\"Error interrogating file: %s\", magic_error (cookie));\n\t\t\telse {\n\t\t\t\tcached_file = xstrdup (file);\n\t\t\t\tcached_result = xstrdup (result);\n\t\t\t}\n\t\t}\n\t\tUNLOCK(magic_mtx);\n\t}\n#endif\n\n\treturn result;\n}\n\n/* Make a title from the file name for the item.  If hide_extn != 0,\n * strip the file name from extension. */\nvoid make_file_title (struct plist *plist, const int num,\n\t\tconst bool hide_extension)\n{\n\tassert (plist != NULL);\n\tassert (LIMIT(num, plist->num));\n\tassert (!plist_deleted (plist, num));\n\n\tif (file_type (plist->items[num].file) != F_URL) {\n\t\tchar *file = xstrdup (plist->items[num].file);\n\n\t\tif (hide_extension) {\n\t\t\tchar *extn;\n\n\t\t\textn = ext_pos (file);\n\t\t\tif (extn)\n\t\t\t\t*(extn - 1) = 0;\n\t\t}\n\n\t\tif (options_get_bool (\"FileNamesIconv\"))\n\t\t{\n\t\t\tchar *old_title = file;\n\t\t\tfile = files_iconv_str (file);\n\t\t\tfree (old_title);\n\t\t}\n\n\t\tplist_set_title_file (plist, num, file);\n\t\tfree (file);\n\t}\n\telse\n\t\tplist_set_title_file (plist, num, plist->items[num].file);\n}\n\n/* Make a title from the tags for the item. */\nvoid make_tags_title (struct plist *plist, const int num)\n{\n\tbool hide_extn;\n\tchar *title;\n\n\tassert (plist != NULL);\n\tassert (LIMIT(num, plist->num));\n\tassert (!plist_deleted (plist, num));\n\n\tif (file_type (plist->items[num].file) == F_URL) {\n\t\tmake_file_title (plist, num, false);\n\t\treturn;\n\t}\n\n\tif (plist->items[num].title_tags)\n\t\treturn;\n\n\tassert (plist->items[num].file != NULL);\n\n\tif (plist->items[num].tags->title) {\n\t\ttitle = build_title (plist->items[num].tags);\n\t\tplist_set_title_tags (plist, num, title);\n\t\tfree (title);\n\t\treturn;\n\t}\n\n\thide_extn = options_get_bool (\"HideFileExtension\");\n\tmake_file_title (plist, num, hide_extn);\n}\n\n/* Switch playlist titles to title_file */\nvoid switch_titles_file (struct plist *plist)\n{\n\tint i;\n\tbool hide_extn;\n\n\thide_extn = options_get_bool (\"HideFileExtension\");\n\n\tfor (i = 0; i < plist->num; i++) {\n\t\tif (plist_deleted (plist, i))\n\t\t\tcontinue;\n\n\t\tif (!plist->items[i].title_file)\n\t\t\tmake_file_title (plist, i, hide_extn);\n\n\t\tassert (plist->items[i].title_file != NULL);\n\t}\n}\n\n/* Switch playlist titles to title_tags */\nvoid switch_titles_tags (struct plist *plist)\n{\n\tint i;\n\tbool hide_extn;\n\n\thide_extn = options_get_bool (\"HideFileExtension\");\n\n\tfor (i = 0; i < plist->num; i++) {\n\t\tif (plist_deleted (plist, i))\n\t\t\tcontinue;\n\n\t\tif (!plist->items[i].title_tags && !plist->items[i].title_file)\n\t\t\tmake_file_title (plist, i, hide_extn);\n\t}\n}\n\n/* Add file to the directory path in buf resolving '../' and removing './'. */\n/* buf must be absolute path. */\nvoid resolve_path (char *buf, size_t size, const char *file)\n{\n\tint rc;\n\tchar *f; /* points to the char in *file we process */\n\tchar path[2*PATH_MAX]; /* temporary path */\n\tsize_t len = 0; /* number of characters in the buffer */\n\n\tassert (buf[0] == '/');\n\n\trc = snprintf(path, sizeof(path), \"%s/%s/\", buf, file);\n\tif (rc >= ssizeof(path))\n\t\tfatal (\"Path too long!\");\n\n\tf = path;\n\twhile (*f) {\n\t\tif (!strncmp(f, \"/../\", 4)) {\n\t\t\tchar *slash = strrchr (buf, '/');\n\n\t\t\tassert (slash != NULL);\n\n\t\t\tif (slash == buf) {\n\n\t\t\t\t/* make '/' from '/directory' */\n\t\t\t\tbuf[1] = 0;\n\t\t\t\tlen = 1;\n\t\t\t}\n\t\t\telse {\n\n\t\t\t\t/* strip one element */\n\t\t\t\t*(slash) = 0;\n\t\t\t\tlen -= len - (slash - buf);\n\t\t\t\tbuf[len] = 0;\n\t\t\t}\n\n\t\t\tf+= 3;\n\t\t}\n\t\telse if (!strncmp(f, \"/./\", 3))\n\n\t\t\t/* skip '/.' */\n\t\t\tf += 2;\n\t\telse if (!strncmp(f, \"//\", 2))\n\n\t\t\t/* remove double slash */\n\t\t\tf++;\n\t\telse if (len == size - 1)\n\t\t\tfatal (\"Path too long!\");\n\t\telse  {\n\t\t\tbuf[len++] = *(f++);\n\t\t\tbuf[len] = 0;\n\t\t}\n\t}\n\n\t/* remove dot from '/dir/.' */\n\tif (len >= 2 && buf[len-1] == '.' && buf[len-2] == '/')\n\t\tbuf[--len] = 0;\n\n\t/* strip trailing slash */\n\tif (len > 1 && buf[len-1] == '/')\n\t\tbuf[--len] = 0;\n}\n\n/* Read selected tags for a file into tags structure (or create it if NULL).\n * If some tags are already present, don't read them.\n * If present_tags is NULL, allocate new tags. */\nstruct file_tags *read_file_tags (const char *file,\n\t\tstruct file_tags *tags, const int tags_sel)\n{\n\tstruct decoder *df;\n\tint needed_tags;\n\n\tassert (file != NULL);\n\n\tif (tags == NULL)\n\t\ttags = tags_new ();\n\n\tif (file_type (file) == F_URL)\n\t\treturn tags;\n\n\tneeded_tags = ~tags->filled & tags_sel;\n\tif (!needed_tags) {\n\t\tdebug (\"No need to read any tags\");\n\t\treturn tags;\n\t}\n\n\tdf = get_decoder (file);\n\tif (!df) {\n\t\tlogit (\"Can't find decoder functions for %s\", file);\n\t\treturn tags;\n\t}\n\n\t/* This makes sure that we don't cause a memory leak */\n\tassert (!((needed_tags & TAGS_COMMENTS) &&\n\t          (tags->title || tags->artist || tags->album)));\n\n\tdf->info (file, tags, needed_tags);\n\ttags->filled |= tags_sel;\n\n\treturn tags;\n}\n\n/* Read the content of the directory, make an array of absolute paths for\n * all recognized files. Put directories, playlists and sound files\n * in proper structures. Return 0 on error.*/\nint read_directory (const char *directory, lists_t_strs *dirs,\n\t\tlists_t_strs *playlists, struct plist *plist)\n{\n\tDIR *dir;\n\tstruct dirent *entry;\n\tbool show_hidden = options_get_bool (\"ShowHiddenFiles\");\n\tint dir_is_root;\n\n\tassert (directory != NULL);\n\tassert (*directory == '/');\n\tassert (dirs != NULL);\n\tassert (playlists != NULL);\n\tassert (plist != NULL);\n\n\tif (!(dir = opendir(directory))) {\n\t\terror_errno (\"Can't read directory\", errno);\n\t\treturn 0;\n\t}\n\n\tif (!strcmp(directory, \"/\"))\n\t\tdir_is_root = 1;\n\telse\n\t\tdir_is_root = 0;\n\n\twhile ((entry = readdir(dir))) {\n\t\tint rc;\n\t\tchar file[PATH_MAX];\n\t\tenum file_type type;\n\n\t\tif (user_wants_interrupt()) {\n\t\t\terror (\"Interrupted! Not all files read!\");\n\t\t\tbreak;\n\t\t}\n\n\t\tif (!strcmp(entry->d_name, \".\") || !strcmp(entry->d_name, \"..\"))\n\t\t\tcontinue;\n\t\tif (!show_hidden && entry->d_name[0] == '.')\n\t\t\tcontinue;\n\n\t\trc = snprintf(file, sizeof(file), \"%s/%s\",\n\t\t              dir_is_root ? \"\" : directory, entry->d_name);\n\t\tif (rc >= ssizeof(file)) {\n\t\t\terror (\"Path too long!\");\n\t\t\tclosedir (dir);\n\t\t\treturn 0;\n\t\t}\n\n\t\ttype = file_type (file);\n\t\tif (type == F_SOUND)\n\t\t\tplist_add (plist, file);\n\t\telse if (type == F_DIR)\n\t\t\tlists_strs_append (dirs, file);\n\t\telse if (type == F_PLAYLIST)\n\t\t\tlists_strs_append (playlists, file);\n\t}\n\n\tclosedir (dir);\n\n\treturn 1;\n}\n\nstatic int dir_symlink_loop (const ino_t inode_no, const ino_t *dir_stack,\n\t\tconst int depth)\n{\n\tint i;\n\n\tfor (i = 0; i < depth; i++)\n\t\tif (dir_stack[i] == inode_no)\n\t\t\treturn 1;\n\n\treturn 0;\n}\n\n/* Recursively add files from the directory to the playlist.\n * Return 1 if OK (and even some errors), 0 if the user interrupted. */\nstatic int read_directory_recurr_internal (const char *directory, struct plist *plist,\n\t\tino_t **dir_stack, int *depth)\n{\n\tDIR *dir;\n\tstruct dirent *entry;\n\tstruct stat st;\n\n\tif (stat (directory, &st)) {\n\t\tchar *err = xstrerror (errno);\n\t\terror (\"Can't stat %s: %s\", directory, err);\n\t\tfree (err);\n\t\treturn 0;\n\t}\n\n\tassert (plist != NULL);\n\tassert (directory != NULL);\n\n\tif (*dir_stack && dir_symlink_loop(st.st_ino, *dir_stack, *depth)) {\n\t\tlogit (\"Detected symlink loop on %s\", directory);\n\t\treturn 1;\n\t}\n\n\tif (!(dir = opendir(directory))) {\n\t\terror_errno (\"Can't read directory\", errno);\n\t\treturn 1;\n\t}\n\n\t(*depth)++;\n\t*dir_stack = (ino_t *)xrealloc (*dir_stack, sizeof(ino_t) * (*depth));\n\t(*dir_stack)[*depth - 1] = st.st_ino;\n\n\twhile ((entry = readdir(dir))) {\n\t\tint rc;\n\t\tchar file[PATH_MAX];\n\t\tenum file_type type;\n\n\t\tif (user_wants_interrupt()) {\n\t\t\terror (\"Interrupted! Not all files read!\");\n\t\t\tbreak;\n\t\t}\n\n\t\tif (!strcmp(entry->d_name, \".\") || !strcmp(entry->d_name, \"..\"))\n\t\t\tcontinue;\n\t\trc = snprintf(file, sizeof(file), \"%s/%s\", directory, entry->d_name);\n\t\tif (rc >= ssizeof(file)) {\n\t\t\terror (\"Path too long!\");\n\t\t\tcontinue;\n\t\t}\n\t\ttype = file_type (file);\n\t\tif (type == F_DIR)\n\t\t\tread_directory_recurr_internal(file, plist, dir_stack, depth);\n\t\telse if (type == F_SOUND && plist_find_fname(plist, file) == -1)\n\t\t\tplist_add (plist, file);\n\t}\n\n\t(*depth)--;\n\t*dir_stack = (ino_t *)xrealloc (*dir_stack, sizeof(ino_t) * (*depth));\n\n\tclosedir (dir);\n\treturn 1;\n}\n\nint read_directory_recurr (const char *directory, struct plist *plist)\n{\n\tint ret;\n\tint depth = 0;\n\tino_t *dir_stack = NULL;\n\n\tret = read_directory_recurr_internal (directory, plist, &dir_stack,\n\t\t\t&depth);\n\n\tif (dir_stack)\n\t\tfree (dir_stack);\n\n\treturn ret;\n}\n\n/* Return the file extension position or NULL if the file has no extension. */\nchar *ext_pos (const char *file)\n{\n\tchar *ext = strrchr (file, '.');\n\tchar *slash = strrchr (file, '/');\n\n\t/* don't treat dot in ./file or /.file as a dot before extension */\n\tif (ext && (!slash || slash < ext) && ext != file && *(ext-1) != '/')\n\t\text++;\n\telse\n\t\text = NULL;\n\n\treturn ext;\n}\n\n/* Read one line from a file, strip trailing end of line chars.\n * Returned memory is malloc()ed.  Return NULL on error or EOF. */\nchar *read_line (FILE *file)\n{\n\tint line_alloc = READ_LINE_INIT_SIZE;\n\tint len = 0;\n\tchar *line = (char *)xmalloc (sizeof(char) * line_alloc);\n\n\twhile (1) {\n\t\tif (!fgets(line + len, line_alloc - len, file))\n\t\t\tbreak;\n\t\tlen = strlen(line);\n\n\t\tif (line[len-1] == '\\n')\n\t\t\tbreak;\n\n\t\t/* If we are here, it means that line is longer than the buffer. */\n\t\tline_alloc *= 2;\n\t\tline = (char *)xrealloc (line, sizeof(char) * line_alloc);\n\t}\n\n\tif (len == 0) {\n\t\tfree (line);\n\t\treturn NULL;\n\t}\n\n\tif (line[len-1] == '\\n')\n\t\tline[--len] = 0;\n\tif (len > 0 && line[len-1] == '\\r')\n\t\tline[--len] = 0;\n\n\treturn line;\n}\n\n/* Return malloc()ed string in form \"base/name\". */\nstatic char *add_dir_file (const char *base, const char *name)\n{\n\tchar *path;\n\tint base_is_root;\n\n\tbase_is_root = !strcmp (base, \"/\") ? 1 : 0;\n\tpath = (char *)xmalloc (sizeof(char) *\n\t\t\t(strlen(base) + strlen(name) + 2));\n\n\tsprintf (path, \"%s/%s\", base_is_root ? \"\" : base, name);\n\n\treturn path;\n}\n\n/* Find directories having a prefix of 'pattern'.\n * - If there are no matches, NULL is returned.\n * - If there is one such directory, it is returned with a trailing '/'.\n * - Otherwise the longest common prefix is returned (with no trailing '/').\n * (This is used for directory auto-completion.)\n * Returned memory is malloc()ed.\n * 'pattern' is temporarily modified! */\nchar *find_match_dir (char *pattern)\n{\n\tchar *slash;\n\tDIR *dir;\n\tstruct dirent *entry;\n\tint name_len;\n\tchar *name;\n\tchar *matching_dir = NULL;\n\tchar *search_dir;\n\tint unambiguous = 1;\n\n\tif (!pattern[0])\n\t\treturn NULL;\n\n\t/* strip the last directory */\n\tslash = strrchr (pattern, '/');\n\tif (!slash)\n\t\treturn NULL;\n\tif (slash == pattern) {\n\t\t/* only '/dir' */\n\t\tsearch_dir = xstrdup (\"/\");\n\t}\n\telse {\n\t\t*slash = 0;\n\t\tsearch_dir = xstrdup (pattern);\n\t\t*slash = '/';\n\t}\n\n\tname = slash + 1;\n\tname_len = strlen (name);\n\n\tif (!(dir = opendir(search_dir)))\n\t\treturn NULL;\n\n\twhile ((entry = readdir(dir))) {\n\t\tif (strcmp(entry->d_name, \".\") && strcmp(entry->d_name, \"..\")\n\t\t\t\t&& !strncmp(entry->d_name, name, name_len)) {\n\t\t\tchar *path = add_dir_file (search_dir, entry->d_name);\n\n\t\t\tif (is_dir(path) == 1) {\n\t\t\t\tif (matching_dir) {\n\n\t\t\t\t\t/* More matching directories - strip\n\t\t\t\t\t * matching_dir to the part that is\n\t\t\t\t\t * common to both paths */\n\t\t\t\t\tint i = 0;\n\n\t\t\t\t\twhile (matching_dir[i] == path[i]\n\t\t\t\t\t\t\t&& path[i])\n\t\t\t\t\t\ti++;\n\t\t\t\t\tmatching_dir[i] = 0;\n\t\t\t\t\tfree (path);\n\t\t\t\t\tunambiguous = 0;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tmatching_dir = path;\n\t\t\t}\n\t\t\telse\n\t\t\t\tfree (path);\n\t\t}\n\t}\n\n\tclosedir (dir);\n\tfree (search_dir);\n\n\tif (matching_dir && unambiguous) {\n\t\tmatching_dir = (char *)xrealloc (matching_dir,\n\t\t\t\tsizeof(char) * (strlen(matching_dir) + 2));\n\t\tstrcat (matching_dir, \"/\");\n\t}\n\n\treturn matching_dir;\n}\n\n/* Return != 0 if the file exists. */\nint file_exists (const char *file)\n{\n\tstruct stat file_stat;\n\n\tif (!stat(file, &file_stat))\n\t\treturn 1;\n\n\t/* Log any error other than non-existence. */\n\tif (errno != ENOENT)\n\t\tlog_errno (\"Error\", errno);\n\n\treturn 0;\n}\n\n/* Get the modification time of a file. Return (time_t)-1 on error */\ntime_t get_mtime (const char *file)\n{\n\tstruct stat stat_buf;\n\n\tif (stat(file, &stat_buf) != -1)\n\t\treturn stat_buf.st_mtime;\n\n\treturn (time_t)-1;\n}\n\n/* Convert file path to absolute path;\n * resulting string is allocated and must be freed afterwards. */\nchar *absolute_path (const char *path, const char *cwd)\n{\n\tchar tmp[2*PATH_MAX];\n\tchar *result;\n\n\tassert (path);\n\tassert (cwd);\n\n\tif(path[0] != '/' && !is_url(path)) {\n\t\tstrncpy (tmp, cwd, sizeof(tmp));\n\t\ttmp[sizeof(tmp)-1] = 0;\n\n\t\tresolve_path (tmp, sizeof(tmp), path);\n\n\t\tresult = (char *)xmalloc (sizeof(char) * (strlen(tmp)+1));\n\t\tstrcpy (result, tmp);\n\t}\n\telse {\n\t\tresult = (char *)xmalloc (sizeof(char) * (strlen(path)+1));\n\t\tstrcpy (result, path);\n\t}\n\n\treturn result;\n}\n\n/* Check that a file which may cause other applications to be invoked\n * is secure against tampering. */\nbool is_secure (const char *file)\n{\n    struct stat sb;\n\n\tassert (file && file[0]);\n\n\tif (stat (file, &sb) == -1)\n\t\treturn true;\n\tif (!S_ISREG(sb.st_mode))\n\t\treturn false;\n\tif (sb.st_mode & (S_IWGRP|S_IWOTH))\n\t\treturn false;\n\tif (sb.st_uid != 0 && sb.st_uid != geteuid ())\n\t\treturn false;\n\n\treturn true;\n}\n"
  },
  {
    "path": "files.h",
    "content": "#ifndef FILES_H\n#define FILES_H\n\n#include <stdio.h>\n#include \"lists.h\"\n#include \"playlist.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define FILES_LIST_INIT_SIZE\t64\n\nvoid files_init ();\nvoid files_cleanup ();\nint read_directory (const char *directory, lists_t_strs *dirs,\n\t\tlists_t_strs *playlists, struct plist *plist);\nint read_directory_recurr (const char *directory, struct plist *plist);\nvoid resolve_path (char *buf, size_t size, const char *file);\nchar *ext_pos (const char *file);\nenum file_type file_type (const char *file);\nchar *file_mime_type (const char *file);\nint is_url (const char *str);\nchar *read_line (FILE *file);\nchar *find_match_dir (char *dir);\nint file_exists (const char *file);\ntime_t get_mtime (const char *file);\nstruct file_tags *read_file_tags (const char *file,\n\t\tstruct file_tags *present_tags, const int tags_sel);\nvoid switch_titles_file (struct plist *plist);\nvoid switch_titles_tags (struct plist *plist);\nvoid make_tags_title (struct plist *plist, const int num);\nvoid make_file_title (struct plist *plist, const int num,\n\t\tconst bool hide_extension);\nint is_dir (const char *file);\nint can_read_file (const char *file);\nchar *absolute_path (const char *path, const char *cwd);\nbool is_secure (const char *file);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "interface.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 - 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdarg.h>\n#include <locale.h>\n#include <assert.h>\n#include <string.h>\n#include <strings.h>\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n#include <signal.h>\n#include <ctype.h>\n#include <unistd.h>\n#include <sys/socket.h>\n#include <sys/wait.h>\n#include <dirent.h>\n#include <sys/select.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"interface_elements.h\"\n#include \"interface.h\"\n#include \"lists.h\"\n#include \"playlist.h\"\n#include \"playlist_file.h\"\n#include \"protocol.h\"\n#include \"keys.h\"\n#include \"options.h\"\n#include \"files.h\"\n#include \"decoder.h\"\n#include \"themes.h\"\n#include \"softmixer.h\"\n#include \"utf8.h\"\n\n#define INTERFACE_LOG\t\"mocp_client_log\"\n#define PLAYLIST_FILE\t\"playlist.m3u\"\n\n#define QUEUE_CLEAR_THRESH 128\n\n/* Socket of the server connection. */\nstatic int srv_sock = -1;\n\nstatic struct plist *playlist = NULL; /* our playlist */\nstatic struct plist *queue = NULL; /* our queue */\nstatic struct plist *dir_plist = NULL; /* contents of the current directory */\n\n/* Queue for events coming from the server. */\nstatic struct event_queue events;\n\n/* Current working directory (the directory we show). */\nstatic char cwd[PATH_MAX] = \"\";\n\n/* If the user presses quit, or we receive a termination signal. */\nstatic volatile enum want_quit want_quit = NO_QUIT;\n\n/* If user presses CTRL-C, set this to 1.  This should interrupt long\n * operations which block the interface. */\nstatic volatile int wants_interrupt = 0;\n\n#ifdef SIGWINCH\n/* If we get SIGWINCH. */\nstatic volatile int want_resize = 0;\n#endif\n\n/* Are we waiting for the playlist we have loaded and sent to the clients? */\nstatic int waiting_for_plist_load = 0;\n\n/* Information about the currently played file. */\nstatic struct file_info curr_file;\n\n/* Silent seeking - where we are in seconds. -1 - no seeking. */\nstatic int silent_seek_pos = -1;\nstatic time_t silent_seek_key_last = (time_t)0; /* when the silent seek key was\n\t\t\t\t\t\t   last used */\n\n/* When the menu was last moved (arrow keys, page up, etc.) */\nstatic time_t last_menu_move_time = (time_t)0;\n\nstatic void sig_quit (int sig LOGIT_ONLY)\n{\n\tlog_signal (sig);\n\twant_quit = QUIT_CLIENT;\n}\n\nstatic void sig_interrupt (int sig LOGIT_ONLY)\n{\n\tlog_signal (sig);\n\twants_interrupt = 1;\n}\n\n#ifdef SIGWINCH\nstatic void sig_winch (int sig LOGIT_ONLY)\n{\n\tlog_signal (sig);\n\twant_resize = 1;\n}\n#endif\n\nint user_wants_interrupt ()\n{\n\treturn wants_interrupt;\n}\n\nstatic void clear_interrupt ()\n{\n\twants_interrupt = 0;\n}\n\nstatic void send_int_to_srv (const int num)\n{\n\tif (!send_int(srv_sock, num))\n\t\tfatal (\"Can't send() int to the server!\");\n}\n\nstatic void send_bool_to_srv (const bool t)\n{\n\tif (!send_int(srv_sock, t ? 1 : 0))\n\t\tfatal (\"Can't send() bool to the server!\");\n}\n\nstatic void send_str_to_srv (const char *str)\n{\n\tif (!send_str(srv_sock, str))\n\t\tfatal (\"Can't send() string to the server!\");\n}\n\nstatic void send_item_to_srv (const struct plist_item *item)\n{\n\tif (!send_item(srv_sock, item))\n\t\tfatal (\"Can't send() item to the server!\");\n}\n\nstatic int get_int_from_srv ()\n{\n\tint num;\n\n\tif (!get_int(srv_sock, &num))\n\t\tfatal (\"Can't receive value from the server!\");\n\n\treturn num;\n}\n\nstatic bool get_bool_from_srv ()\n{\n\tint num;\n\n\tif (!get_int(srv_sock, &num))\n\t\tfatal (\"Can't receive value from the server!\");\n\n\treturn num == 1 ? true : false;\n}\n\n/* Returned memory is malloc()ed. */\nstatic char *get_str_from_srv ()\n{\n\tchar *str = get_str (srv_sock);\n\n\tif (!str)\n\t\tfatal (\"Can't receive string from the server!\");\n\n\treturn str;\n}\n\nstatic struct file_tags *recv_tags_from_srv ()\n{\n\tstruct file_tags *tags = recv_tags (srv_sock);\n\n\tif (!tags)\n\t\tfatal (\"Can't receive tags from the server!\");\n\n\treturn tags;\n}\n\n/* Noblocking version of get_int_from_srv(): return 0 if there are no data. */\nstatic int get_int_from_srv_noblock (int *num)\n{\n\tenum noblock_io_status st;\n\n\tif ((st = get_int_noblock(srv_sock, num)) == NB_IO_ERR)\n\t\tfatal (\"Can't receive value from the server!\");\n\n\treturn st == NB_IO_OK ? 1 : 0;\n}\n\nstatic struct plist_item *recv_item_from_srv ()\n{\n\tstruct plist_item *item;\n\n\tif (!(item = recv_item(srv_sock)))\n\t\tfatal (\"Can't receive item from the server!\");\n\n\treturn item;\n}\n\nstatic struct tag_ev_response *recv_tags_data_from_srv ()\n{\n\tstruct tag_ev_response *r;\n\n\tr = (struct tag_ev_response *)xmalloc (sizeof(struct tag_ev_response));\n\n\tr->file = get_str_from_srv ();\n\tif (!(r->tags = recv_tags(srv_sock)))\n\t\tfatal (\"Can't receive tags event's data from the server!\");\n\n\treturn r;\n}\n\nstatic struct move_ev_data *recv_move_ev_data_from_srv ()\n{\n\tstruct move_ev_data *d;\n\n\tif (!(d = recv_move_ev_data(srv_sock)))\n\t\tfatal (\"Can't receive move data from the server!\");\n\n\treturn d;\n}\n\n/* Receive data for the given type of event and return them. Return NULL if\n * there is no data for the event. */\nstatic void *get_event_data (const int type)\n{\n\tswitch (type) {\n\t\tcase EV_PLIST_ADD:\n\t\tcase EV_QUEUE_ADD:\n\t\t\treturn recv_item_from_srv ();\n\t\tcase EV_PLIST_DEL:\n\t\tcase EV_QUEUE_DEL:\n\t\tcase EV_STATUS_MSG:\n\t\tcase EV_SRV_ERROR:\n\t\t\treturn get_str_from_srv ();\n\t\tcase EV_FILE_TAGS:\n\t\t\treturn recv_tags_data_from_srv ();\n\t\tcase EV_PLIST_MOVE:\n\t\tcase EV_QUEUE_MOVE:\n\t\t\treturn recv_move_ev_data_from_srv ();\n\t}\n\n\treturn NULL;\n}\n\n/* Wait for EV_DATA handling other events. */\nstatic void wait_for_data ()\n{\n\tint event;\n\n\tdo {\n\t\tevent = get_int_from_srv ();\n\t\tif (event == EV_EXIT)\n\t\t\tinterface_fatal (\"The server exited!\");\n\t\tif (event != EV_DATA)\n\t\t\tevent_push (&events, event, get_event_data(event));\n\t } while (event != EV_DATA);\n}\n\n/* Get an integer value from the server that will arrive after EV_DATA. */\nstatic int get_data_int ()\n{\n\twait_for_data ();\n\treturn get_int_from_srv ();\n}\n\n/* Get a boolean value from the server that will arrive after EV_DATA. */\nstatic bool get_data_bool ()\n{\n\twait_for_data ();\n\treturn get_bool_from_srv ();\n}\n\n/* Get a string value from the server that will arrive after EV_DATA. */\nstatic char *get_data_str ()\n{\n\twait_for_data ();\n\treturn get_str_from_srv ();\n}\n\nstatic struct file_tags *get_data_tags ()\n{\n\twait_for_data ();\n\treturn recv_tags_from_srv ();\n}\n\nstatic int send_tags_request (const char *file, const int tags_sel)\n{\n\tassert (file != NULL);\n\tassert (tags_sel != 0);\n\n\tif (file_type(file) == F_SOUND) {\n\t\tsend_int_to_srv (CMD_GET_FILE_TAGS);\n\t\tsend_str_to_srv (file);\n\t\tsend_int_to_srv (tags_sel);\n\t\tdebug (\"Asking for tags for %s\", file);\n\n\t\treturn 1;\n\t}\n\telse {\n\t\tdebug (\"Not sending tags request for URL (%s)\", file);\n\t\treturn 0;\n\t}\n}\n\n/* Send all items from this playlist to other clients. */\nstatic void send_items_to_clients (const struct plist *plist)\n{\n\tint i;\n\n\tfor (i = 0; i < plist->num; i++)\n\t\tif (!plist_deleted(plist, i)) {\n\t\t\tsend_int_to_srv (CMD_CLI_PLIST_ADD);\n\t\t\tsend_item_to_srv (&plist->items[i]);\n\t\t}\n}\n\nstatic void init_playlists ()\n{\n\tdir_plist = (struct plist *)xmalloc (sizeof(struct plist));\n\tplist_init (dir_plist);\n\tplaylist = (struct plist *)xmalloc (sizeof(struct plist));\n\tplist_init (playlist);\n\tqueue = (struct plist *)xmalloc (sizeof(struct plist));\n\tplist_init (queue);\n\n\t/* set serial numbers for the playlist */\n\tsend_int_to_srv (CMD_GET_SERIAL);\n\tplist_set_serial (playlist, get_data_int());\n}\n\nstatic void file_info_reset (struct file_info *f)\n{\n\tf->file = NULL;\n\tf->tags = NULL;\n\tf->title = NULL;\n\tf->bitrate = -1;\n\tf->rate = -1;\n\tf->curr_time = -1;\n\tf->total_time = -1;\n\tf->channels = 1;\n\tf->state = STATE_STOP;\n}\n\nstatic void file_info_cleanup (struct file_info *f)\n{\n\tif (f->tags)\n\t\ttags_free (f->tags);\n\tif (f->file)\n\t\tfree (f->file);\n\tif (f->title)\n\t\tfree (f->title);\n\n\tf->file = NULL;\n\tf->tags = NULL;\n\tf->title = NULL;\n}\n\n/* Initialise the block marker information. */\nstatic void file_info_block_init (struct file_info *f)\n{\n\tf->block_file = NULL;\n}\n\n/* Reset the block start and end markers. */\nstatic void file_info_block_reset (struct file_info *f)\n{\n\tif (f->block_file)\n\t\tfree (f->block_file);\n\tf->block_file = NULL;\n}\n\n/* Enter the current time into a block start or end marker. */\nstatic void file_info_block_mark (int *marker)\n{\n\tif (curr_file.state == STATE_STOP) {\n\t\terror (\"Cannot make block marks while stopped.\");\n\t} else if (file_type (curr_file.file) == F_URL) {\n\t\terror (\"Cannot make block marks in URLs.\");\n\t} else if (file_type (curr_file.file) != F_SOUND) {\n\t\terror (\"Cannot make block marks in non-audio files.\");\n\t} else if (!curr_file.block_file) {\n\t\terror (\"Cannot make block marks in files of unknown duration.\");\n\t} else {\n\t\t*marker = curr_file.curr_time;\n\t\tiface_set_block (curr_file.block_start, curr_file.block_end);\n\t}\n}\n\n/* Get a boolean option from the server (like Shuffle) and set it. */\nstatic void sync_bool_option (const char *name)\n{\n\tbool value;\n\n\tsend_int_to_srv (CMD_GET_OPTION);\n\tsend_str_to_srv (name);\n\tvalue = get_data_bool ();\n\toptions_set_bool (name, value);\n\tiface_set_option_state (name, value);\n}\n\n/* Get the server options and set our options like them. */\nstatic void get_server_options ()\n{\n\tsync_bool_option (\"Shuffle\");\n\tsync_bool_option (\"Repeat\");\n\tsync_bool_option (\"AutoNext\");\n}\n\nstatic int get_server_plist_serial ()\n{\n\tsend_int_to_srv (CMD_PLIST_GET_SERIAL);\n\treturn get_data_int ();\n}\n\nstatic int get_mixer_value ()\n{\n\tsend_int_to_srv (CMD_GET_MIXER);\n\treturn get_data_int ();\n}\n\nstatic int get_state ()\n{\n\tsend_int_to_srv (CMD_GET_STATE);\n\treturn get_data_int ();\n}\n\nstatic int get_channels ()\n{\n\tsend_int_to_srv (CMD_GET_CHANNELS);\n\treturn get_data_int ();\n}\n\nstatic int get_rate ()\n{\n\tsend_int_to_srv (CMD_GET_RATE);\n\treturn get_data_int ();\n}\n\nstatic int get_bitrate ()\n{\n\tsend_int_to_srv (CMD_GET_BITRATE);\n\treturn get_data_int ();\n}\n\nstatic int get_avg_bitrate ()\n{\n\tsend_int_to_srv (CMD_GET_AVG_BITRATE);\n\treturn get_data_int ();\n}\n\nstatic int get_curr_time ()\n{\n\tsend_int_to_srv (CMD_GET_CTIME);\n\treturn get_data_int ();\n}\n\nstatic char *get_curr_file ()\n{\n\tsend_int_to_srv (CMD_GET_SNAME);\n\treturn get_data_str ();\n}\n\nstatic void update_mixer_value ()\n{\n\tint val;\n\n\tval = get_mixer_value ();\n\tiface_set_mixer_value (MAX(val, 0));\n}\n\nstatic void update_mixer_name ()\n{\n\tchar *name;\n\n\tsend_int_to_srv (CMD_GET_MIXER_CHANNEL_NAME);\n\tname = get_data_str();\n\tdebug (\"Mixer name: %s\", name);\n\n\tiface_set_mixer_name (name);\n\n\tfree (name);\n\n\tupdate_mixer_value ();\n}\n\n/* Make new cwd path from CWD and this path. */\nstatic void set_cwd (const char *path)\n{\n\tif (path[0] == '/')\n\t\tstrcpy (cwd, \"/\"); /* for absolute path */\n\telse if (!cwd[0]) {\n\t\tif (!getcwd(cwd, sizeof(cwd)))\n\t\t\tfatal (\"Can't get CWD: %s\", xstrerror (errno));\n\t}\n\n\tresolve_path (cwd, sizeof(cwd), path);\n}\n\n/* Try to find the directory we can start and set cwd to it. */\nstatic void set_start_dir ()\n{\n\tif (!getcwd(cwd, sizeof (cwd))) {\n\t\tif (errno == ERANGE)\n\t\t\tfatal (\"CWD is larger than PATH_MAX!\");\n\t\tstrncpy (cwd, get_home (), sizeof (cwd));\n\t\tif (cwd[sizeof (cwd) - 1])\n\t\t\tfatal (\"Home directory path is longer than PATH_MAX!\");\n\t}\n}\n\n/* Set cwd to last directory written to a file, return 1 on success. */\nstatic int read_last_dir ()\n{\n\tFILE *dir_file;\n\tint res = 1;\n\tint read;\n\n\tif (!(dir_file = fopen(create_file_name(\"last_directory\"), \"r\")))\n\t\treturn 0;\n\n\tif ((read = fread(cwd, sizeof(char), sizeof(cwd)-1, dir_file)) == 0)\n\t\tres = 0;\n\telse\n\t\tcwd[read] = 0;\n\n\tfclose (dir_file);\n\treturn res;\n}\n\n/* Check if dir2 is in dir1. */\nstatic int is_subdir (const char *dir1, const char *dir2)\n{\n\treturn !strncmp(dir1, dir2, strlen(dir1)) ? 1 : 0;\n}\n\nstatic int sort_strcmp_func (const void *a, const void *b)\n{\n\treturn strcoll (*(char **)a, *(char **)b);\n}\n\nstatic int sort_dirs_func (const void *a, const void *b)\n{\n\tchar *sa = *(char **)a;\n\tchar *sb = *(char **)b;\n\n\t/* '../' is always first */\n\tif (!strcmp(sa, \"../\"))\n\t\treturn -1;\n\tif (!strcmp(sb, \"../\"))\n\t\treturn 1;\n\n\treturn strcoll (sa, sb);\n}\n\nstatic int get_tags_setting ()\n{\n\tint needed_tags = 0;\n\n\tif (options_get_bool(\"ReadTags\"))\n\t\tneeded_tags |= TAGS_COMMENTS;\n\tif (!strcasecmp(options_get_symb(\"ShowTime\"), \"yes\"))\n\t\tneeded_tags |= TAGS_TIME;\n\n\treturn needed_tags;\n}\n\n/* For each file in the playlist, send a request for all the given tags if\n * the file is missing any of those tags.  Return the number of requests. */\nstatic int ask_for_tags (const struct plist *plist, const int tags_sel)\n{\n\tint i;\n\tint req = 0;\n\n\tassert (plist != NULL);\n\n\tif (tags_sel != 0) {\n\t\tfor (i = 0; i < plist->num; i++) {\n\t\t\tif (!plist_deleted(plist, i) &&\n\t\t\t    (!plist->items[i].tags ||\n\t\t\t     ~plist->items[i].tags->filled & tags_sel)) {\n\t\t\t\tchar *file;\n\n\t\t\t\tfile = plist_get_file (plist, i);\n\t\t\t\treq += send_tags_request (file, tags_sel);\n\t\t\t\tfree (file);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn req;\n}\n\nstatic void interface_message (const char *format, ...)\n{\n\tva_list va;\n\tchar *msg;\n\n\tva_start (va, format);\n\tmsg = format_msg_va (format, va);\n\tva_end (va);\n\n\tiface_message (msg);\n\n\tfree (msg);\n}\n\n/* Update tags (and titles) for the given item on the playlist with new tags. */\nstatic void update_item_tags (struct plist *plist, const int num,\n\t\tconst struct file_tags *tags)\n{\n\tstruct file_tags *old_tags = plist_get_tags (plist, num);\n\n\tplist_set_tags (plist, num, tags);\n\n\t/* Get the time from the old tags if it's not present in the new tags.\n\t * FIXME: There is risk, that the file was modified and the time\n\t * from the old tags is not valid. */\n\tif (!(tags->filled & TAGS_TIME) && old_tags && old_tags->time != -1)\n\t\tplist_set_item_time (plist, num, old_tags->time);\n\n\tif (plist->items[num].title_tags) {\n\t\tfree (plist->items[num].title_tags);\n\t\tplist->items[num].title_tags = NULL;\n\t}\n\n\tmake_tags_title (plist, num);\n\n\tif (options_get_bool (\"ReadTags\") && !plist->items[num].title_tags) {\n\t\tif (!plist->items[num].title_file)\n\t\t\tmake_file_title (plist, num,\n\t\t\t\t\toptions_get_bool (\"HideFileExtension\"));\n\t}\n\n\tif (old_tags)\n\t\ttags_free (old_tags);\n}\n\n/* Truncate string at screen-upsetting whitespace. */\nstatic void sanitise_string (char *str)\n{\n\tif (!str)\n\t\treturn;\n\n\twhile (*str) {\n\t\tif (*str != ' ' && isspace (*str)) {\n\t\t\t*str = 0x00;\n\t\t\tbreak;\n\t\t}\n\t\tstr++;\n\t}\n}\n\n/* Handle EV_FILE_TAGS. */\nstatic void ev_file_tags (const struct tag_ev_response *data)\n{\n\tint n;\n\n\tassert (data != NULL);\n\tassert (data->file != NULL);\n\tassert (data->tags != NULL);\n\n\tdebug (\"Received tags for %s\", data->file);\n\n\tsanitise_string (data->tags->title);\n\tsanitise_string (data->tags->artist);\n\tsanitise_string (data->tags->album);\n\n\tif ((n = plist_find_fname(dir_plist, data->file)) != -1) {\n\t\tupdate_item_tags (dir_plist, n, data->tags);\n\t\tiface_update_item (IFACE_MENU_DIR, dir_plist, n);\n\t}\n\n\tif ((n = plist_find_fname(playlist, data->file)) != -1) {\n\t\tupdate_item_tags (playlist, n, data->tags);\n\t\tiface_update_item (IFACE_MENU_PLIST, playlist, n);\n\t}\n\n\tif (curr_file.file && !strcmp(data->file, curr_file.file)) {\n\n\t\tdebug (\"Tags apply to the currently played file.\");\n\n\t\tif (data->tags->time != -1) {\n\t\t\tcurr_file.total_time = data->tags->time;\n\t\t\tiface_set_total_time (curr_file.total_time);\n\t\t\tif (file_type (curr_file.file) == F_SOUND) {\n\t\t\t\tif (!curr_file.block_file) {\n\t\t\t\t\tcurr_file.block_file = xstrdup(curr_file.file);\n\t\t\t\t\tcurr_file.block_start = 0;\n\t\t\t\t\tcurr_file.block_end = curr_file.total_time;\n\t\t\t\t}\n\t\t\t\tiface_set_block (curr_file.block_start, curr_file.block_end);\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\tdebug (\"No time information\");\n\n\t\tif (data->tags->title) {\n\t\t\tif (curr_file.title)\n\t\t\t\tfree (curr_file.title);\n\t\t\tcurr_file.title = build_title (data->tags);\n\t\t\tiface_set_played_file_title (curr_file.title);\n\t\t}\n\n\t\tif (curr_file.tags)\n\t\t\ttags_free (curr_file.tags);\n\t\tcurr_file.tags = tags_dup (data->tags);\n\t}\n}\n\n/* Update the current time. */\nstatic void update_ctime ()\n{\n\tcurr_file.curr_time = get_curr_time ();\n\tif (silent_seek_pos == -1)\n\t\tiface_set_curr_time (curr_file.curr_time);\n}\n\n/* Use new tags for current file title (for Internet streams). */\nstatic void update_curr_tags ()\n{\n\tif (curr_file.file && is_url(curr_file.file)) {\n\t\tif (curr_file.tags)\n\t\t\ttags_free (curr_file.tags);\n\t\tsend_int_to_srv (CMD_GET_TAGS);\n\t\tcurr_file.tags = get_data_tags ();\n\n\t\tif (curr_file.tags->title) {\n\t\t\tif (curr_file.title)\n\t\t\t\tfree (curr_file.title);\n\t\t\tcurr_file.title = build_title (curr_file.tags);\n\t\t\tiface_set_played_file_title (curr_file.title);\n\t\t}\n\t}\n}\n\n/* Make sure that the currently played file is visible if it is in one of our\n * menus. */\nstatic void follow_curr_file ()\n{\n\tif (curr_file.file && file_type(curr_file.file) == F_SOUND\n\t\t\t&& last_menu_move_time <= time(NULL) - 2) {\n\t\tint server_plist_serial = get_server_plist_serial();\n\n\t\tif (server_plist_serial == plist_get_serial(playlist))\n\t\t\tiface_make_visible (IFACE_MENU_PLIST, curr_file.file);\n\t\telse if (server_plist_serial == plist_get_serial(dir_plist))\n\t\t\tiface_make_visible (IFACE_MENU_DIR, curr_file.file);\n\t\telse\n\t\t\tlogit (\"Not my playlist.\");\n\t}\n}\n\nstatic void update_curr_file ()\n{\n\tchar *file;\n\n\tfile = get_curr_file ();\n\n\tif (!file[0] || curr_file.state == STATE_STOP) {\n\n\t\t/* Nothing is played/paused. */\n\n\t\tfile_info_cleanup (&curr_file);\n\t\tfile_info_reset (&curr_file);\n\t\tiface_set_played_file (NULL);\n\t\tiface_load_lyrics (NULL);\n\t\tfree (file);\n\t}\n\telse if (file[0] &&\n\t\t\t(!curr_file.file || strcmp(file, curr_file.file))) {\n\n\t\t/* played file has changed */\n\n\t\tfile_info_cleanup (&curr_file);\n\t\tif (curr_file.block_file && strcmp(file, curr_file.block_file))\n\t\t\tfile_info_block_reset (&curr_file);\n\n\t\t/* The total time could not get reset. */\n\t\tiface_set_total_time (-1);\n\n\t\tiface_set_played_file (file);\n\t\tsend_tags_request (file, TAGS_COMMENTS | TAGS_TIME);\n\t\tcurr_file.file = file;\n\n\t\t/* make a title that will be used until we get tags */\n\t\tif (file_type(file) == F_URL || !strchr(file, '/')) {\n\t\t\tcurr_file.title = xstrdup (file);\n\t\t\tupdate_curr_tags ();\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (options_get_bool (\"FileNamesIconv\"))\n\t\t\t{\n\t\t\t\tcurr_file.title = files_iconv_str (\n\t\t\t\t\tstrrchr(file, '/') + 1);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tcurr_file.title = xstrdup (\n\t\t\t\t\tstrrchr(file, '/') + 1);\n\t\t\t}\n\t\t}\n\n\t\tiface_set_played_file (file);\n\t\tiface_set_played_file_title (curr_file.title);\n\t\t/* Try to load the lyrics of the new file. */\n\t\tiface_load_lyrics (file);\n\t\t/* Silent seeking makes no sense if the playing file has changed. */\n\t\tsilent_seek_pos = -1;\n\t\tiface_set_curr_time (curr_file.curr_time);\n\n\t\tif (options_get_bool(\"FollowPlayedFile\"))\n\t\t\tfollow_curr_file ();\n\t}\n\telse\n\t\tfree (file);\n}\n\nstatic void update_rate ()\n{\n\tcurr_file.rate = get_rate ();\n\tiface_set_rate (curr_file.rate);\n}\n\nstatic void update_channels ()\n{\n\tcurr_file.channels = get_channels () == 2 ? 2 : 1;\n\tiface_set_channels (curr_file.channels);\n}\n\nstatic void update_bitrate ()\n{\n\tcurr_file.bitrate = get_bitrate ();\n\tiface_set_bitrate (curr_file.bitrate);\n}\n\n/* Get and show the server state. */\nstatic void update_state ()\n{\n\tint old_state = curr_file.state;\n\n\t/* play | stop | pause */\n\tcurr_file.state = get_state ();\n\tiface_set_state (curr_file.state);\n\n\t/* Silent seeking makes no sense if the state has changed. */\n\tif (old_state != curr_file.state)\n\t\tsilent_seek_pos = -1;\n\n\tupdate_curr_file ();\n\n\tupdate_channels ();\n\tupdate_bitrate ();\n\tupdate_rate ();\n\tupdate_ctime ();\n}\n\n/* Handle EV_PLIST_ADD. */\nstatic void event_plist_add (const struct plist_item *item)\n{\n\tif (plist_find_fname(playlist, item->file) == -1) {\n\t\tint item_num = plist_add_from_item (playlist, item);\n\t\tint needed_tags = 0;\n\t\tint i;\n\n\t\tif (options_get_bool(\"ReadTags\")\n\t\t\t\t&& (!item->tags || !item->tags->title))\n\t\t\tneeded_tags |= TAGS_COMMENTS;\n\t\tif (!strcasecmp(options_get_symb(\"ShowTime\"), \"yes\")\n\t\t\t\t&& (!item->tags || item->tags->time == -1))\n\t\t\tneeded_tags |= TAGS_TIME;\n\n\t\tif (needed_tags)\n\t\t\tsend_tags_request (item->file, needed_tags);\n\n\t\tif (options_get_bool (\"ReadTags\"))\n\t\t\tmake_tags_title (playlist, item_num);\n\t\telse\n\t\t\tmake_file_title (playlist, item_num,\n\t\t\t\t\toptions_get_bool (\"HideFileExtension\"));\n\n\t\t/* Just calling iface_update_queue_positions (queue, playlist,\n\t\t * NULL, NULL) is too slow in cases when we receive a large\n\t\t * number of items from server (e.g., loading playlist w/\n\t\t * SyncPlaylist on).  Since we know the filename in question,\n\t\t * we try to find it in queue and eventually update the value.\n\t\t */\n\t\tif ((i = plist_find_fname(queue, item->file)) != -1) {\n\t\t\tplaylist->items[item_num].queue_pos\n\t\t\t\t= plist_get_position (queue, i);\n\t\t}\n\n\t\tiface_add_to_plist (playlist, item_num);\n\n\t\tif (waiting_for_plist_load) {\n\t\t\tif (iface_in_dir_menu())\n\t\t\t\tiface_switch_to_plist ();\n\t\t\twaiting_for_plist_load = 0;\n\t\t}\n\t}\n}\n\n/* Handle EV_QUEUE_ADD. */\nstatic void event_queue_add (const struct plist_item *item)\n{\n\tif (plist_find_fname(queue, item->file) == -1) {\n\t\tplist_add_from_item (queue, item);\n\t\tiface_set_files_in_queue (plist_count(queue));\n\t\tiface_update_queue_position_last (queue, playlist, dir_plist);\n\t\tlogit (\"Adding %s to queue\", item->file);\n\t}\n\telse\n\t\tlogit (\"Adding file already present in queue\");\n}\n\n/* Get error message from the server and show it. */\nstatic void update_error (char *err)\n{\n\terror (\"%s\", err);\n}\n\n/* Send the playlist to the server to be forwarded to another client. */\nstatic void forward_playlist ()\n{\n\tint i;\n\n\tdebug (\"Forwarding the playlist...\");\n\n\tsend_int_to_srv (CMD_SEND_PLIST);\n\tsend_int_to_srv (plist_get_serial(playlist));\n\n\tfor (i = 0; i < playlist->num; i++)\n\t\tif (!plist_deleted(playlist, i))\n\t\t\tsend_item_to_srv (&playlist->items[i]);\n\n\tsend_item_to_srv (NULL);\n}\n\nstatic int recv_server_plist (struct plist *plist)\n{\n\tint end_of_list = 0;\n\tstruct plist_item *item;\n\n\tlogit (\"Asking server for the playlist from other client.\");\n\tsend_int_to_srv (CMD_GET_PLIST);\n\tlogit (\"Waiting for response\");\n\twait_for_data ();\n\n\tif (!get_int_from_srv()) {\n\t\tdebug (\"There is no playlist\");\n\t\treturn 0; /* there are no other clients with a playlist */\n\t}\n\n\tlogit (\"There is a playlist, getting...\");\n\twait_for_data ();\n\n\tlogit (\"Transfer...\");\n\n\tplist_set_serial (plist, get_int_from_srv());\n\n\tdo {\n\t\titem = recv_item_from_srv ();\n\t\tif (item->file[0])\n\t\t\tplist_add_from_item (plist, item);\n\t\telse\n\t\t\tend_of_list = 1;\n\t\tplist_free_item_fields (item);\n\t\tfree (item);\n\t} while (!end_of_list);\n\n\treturn 1;\n}\n\nstatic void recv_server_queue (struct plist *queue)\n{\n\tint end_of_list = 0;\n\tstruct plist_item *item;\n\n\tlogit (\"Asking server for the queue.\");\n\tsend_int_to_srv (CMD_GET_QUEUE);\n\tlogit (\"Waiting for response\");\n\twait_for_data (); /* There must always be (possibly empty) queue. */\n\n\tdo {\n\t\titem = recv_item_from_srv ();\n\t\tif (item->file[0])\n\t\t\tplist_add_from_item (queue, item);\n\t\telse\n\t\t\tend_of_list = 1;\n\t\tplist_free_item_fields (item);\n\t\tfree (item);\n\t} while (!end_of_list);\n}\n\n/* Clear the playlist locally. */\nstatic void clear_playlist ()\n{\n\tif (iface_in_plist_menu())\n\t\tiface_switch_to_dir ();\n\tplist_clear (playlist);\n\tiface_clear_plist ();\n\n\tif (!waiting_for_plist_load)\n\t\tinterface_message (\"The playlist was cleared.\");\n\tiface_set_status (\"\");\n}\n\nstatic void clear_queue ()\n{\n\tiface_clear_queue_positions (queue, playlist, dir_plist);\n\n\tplist_clear (queue);\n\tiface_set_files_in_queue (0);\n\n\tinterface_message (\"The queue was cleared.\");\n}\n\n/* Handle EV_PLIST_DEL. */\nstatic void event_plist_del (char *file)\n{\n\tint item = plist_find_fname (playlist, file);\n\n\tif (item != -1) {\n\t\tchar *file;\n\t\tint have_all_times;\n\t\tint playlist_total_time;\n\n\t\tfile = plist_get_file (playlist, item);\n\t\tplist_delete (playlist, item);\n\n\t\tiface_del_plist_item (file);\n\t\tplaylist_total_time = plist_total_time (playlist,\n\t\t\t\t&have_all_times);\n\t\tiface_plist_set_total_time (playlist_total_time,\n\t\t\t\thave_all_times);\n\t\tfree (file);\n\n\t\tif (plist_count(playlist) == 0)\n\t\t\tclear_playlist ();\n\t}\n\telse\n\t\tlogit (\"Server requested deleting an item not present on the\"\n\t\t\t\t\" playlist.\");\n}\n\n/* Handle EV_QUEUE_DEL. */\nstatic void event_queue_del (char *file)\n{\n\tint item = plist_find_fname (queue, file);\n\n\tif (item != -1) {\n\t\tplist_delete (queue, item);\n\n\t\t/* Free the deleted items occasionally.\n\t\t * QUEUE_CLEAR_THRESH is chosen to be two times\n\t\t * the initial size of the playlist. */\n\t\tif (plist_count(queue) == 0\n\t\t\t\t&& queue->num >= QUEUE_CLEAR_THRESH)\n\t\t\tplist_clear (queue);\n\n\t\tiface_set_files_in_queue (plist_count(queue));\n\t\tiface_update_queue_positions (queue, playlist, dir_plist, file);\n\t\tlogit (\"Deleting %s from queue\", file);\n\t}\n\telse\n\t\tlogit (\"Deleting an item not present in the queue\");\n\n}\n\n/* Swap 2 file on the playlist. */\nstatic void swap_playlist_items (const char *file1, const char *file2)\n{\n\tassert (file1 != NULL);\n\tassert (file2 != NULL);\n\n\tplist_swap_files (playlist, file1, file2);\n\tiface_swap_plist_items (file1, file2);\n}\n\n/* Handle EV_PLIST_MOVE. */\nstatic void event_plist_move (const struct move_ev_data *d)\n{\n\tassert (d != NULL);\n\tassert (d->from != NULL);\n\tassert (d->to != NULL);\n\n\tswap_playlist_items (d->from, d->to);\n}\n\n/* Handle EV_QUEUE_MOVE. */\nstatic void event_queue_move (const struct move_ev_data *d)\n{\n\tassert (d != NULL);\n\tassert (d->from != NULL);\n\tassert (d->to != NULL);\n\n\tplist_swap_files (queue, d->from, d->to);\n}\n\n/* Handle server event. */\nstatic void server_event (const int event, void *data)\n{\n\tlogit (\"EVENT: 0x%02x\", event);\n\n\tswitch (event) {\n\t\tcase EV_BUSY:\n\t\t\tinterface_fatal (\"The server is busy; \"\n\t\t\t                 \"too many other clients are connected!\");\n\t\t\tbreak;\n\t\tcase EV_CTIME:\n\t\t\tupdate_ctime ();\n\t\t\tbreak;\n\t\tcase EV_STATE:\n\t\t\tupdate_state ();\n\t\t\tbreak;\n\t\tcase EV_EXIT:\n\t\t\tinterface_fatal (\"The server exited!\");\n\t\t\tbreak;\n\t\tcase EV_BITRATE:\n\t\t\tupdate_bitrate ();\n\t\t\tbreak;\n\t\tcase EV_RATE:\n\t\t\tupdate_rate ();\n\t\t\tbreak;\n\t\tcase EV_CHANNELS:\n\t\t\tupdate_channels ();\n\t\t\tbreak;\n\t\tcase EV_SRV_ERROR:\n\t\t\tupdate_error ((char *)data);\n\t\t\tbreak;\n\t\tcase EV_OPTIONS:\n\t\t\tget_server_options ();\n\t\t\tbreak;\n\t\tcase EV_SEND_PLIST:\n\t\t\tforward_playlist ();\n\t\t\tbreak;\n\t\tcase EV_PLIST_ADD:\n\t\t\tif (options_get_bool(\"SyncPlaylist\"))\n\t\t\t\tevent_plist_add ((struct plist_item *)data);\n\t\t\tbreak;\n\t\tcase EV_PLIST_CLEAR:\n\t\t\tif (options_get_bool(\"SyncPlaylist\"))\n\t\t\t\tclear_playlist ();\n\t\t\tbreak;\n\t\tcase EV_PLIST_DEL:\n\t\t\tif (options_get_bool(\"SyncPlaylist\"))\n\t\t\t\tevent_plist_del ((char *)data);\n\t\t\tbreak;\n\t\tcase EV_PLIST_MOVE:\n\t\t\tif (options_get_bool(\"SyncPlaylist\"))\n\t\t\t\tevent_plist_move ((struct move_ev_data *)data);\n\t\t\tbreak;\n\t\tcase EV_TAGS:\n\t\t\tupdate_curr_tags ();\n\t\t\tbreak;\n\t\tcase EV_STATUS_MSG:\n\t\t\tiface_set_status ((char *)data);\n\t\t\tbreak;\n\t\tcase EV_MIXER_CHANGE:\n\t\t\tupdate_mixer_name ();\n\t\t\tbreak;\n\t\tcase EV_FILE_TAGS:\n\t\t\tev_file_tags ((struct tag_ev_response *)data);\n\t\t\tbreak;\n\t\tcase EV_AVG_BITRATE:\n\t\t\tcurr_file.avg_bitrate = get_avg_bitrate ();\n\t\t\tbreak;\n\t\tcase EV_QUEUE_ADD:\n\t\t\tevent_queue_add ((struct plist_item *)data);\n\t\t\tbreak;\n\t\tcase EV_QUEUE_DEL:\n\t\t\tevent_queue_del ((char *)data);\n\t\t\tbreak;\n\t\tcase EV_QUEUE_CLEAR:\n\t\t\tclear_queue ();\n\t\t\tbreak;\n\t\tcase EV_QUEUE_MOVE:\n\t\t\tevent_queue_move ((struct move_ev_data *)data);\n\t\t\tbreak;\n\t\tcase EV_AUDIO_START:\n\t\t\tbreak;\n\t\tcase EV_AUDIO_STOP:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tinterface_fatal (\"Unknown event: 0x%02x!\", event);\n\t}\n\n\tfree_event_data (event, data);\n}\n\n/* Send requests for the given tags for every file on the playlist and wait\n * for all responses. If no_iface has non-zero value, it will not access the\n * interface. */\nstatic void fill_tags (struct plist *plist, const int tags_sel,\n\t\tconst int no_iface)\n{\n\tint files;\n\n\tassert (plist != NULL);\n\tassert (tags_sel != 0);\n\n\tiface_set_status (\"Reading tags...\");\n\tfiles = ask_for_tags (plist, tags_sel);\n\n\t/* Process events until we have all tags. */\n\twhile (files && !user_wants_interrupt()) {\n\t\tint type;\n\t\tvoid *data;\n\n\t\t/* Event queue is not initialized if there is no interface. */\n\t\tif (!no_iface && !event_queue_empty (&events)) {\n\t\t\tstruct event e = *event_get_first (&events);\n\n\t\t\ttype = e.type;\n\t\t\tdata = e.data;\n\n\t\t\tevent_pop (&events);\n\n\t\t}\n\t\telse {\n\t\t\ttype = get_int_from_srv ();\n\t\t\tdata = get_event_data (type);\n\t\t}\n\n\t\tif (type == EV_FILE_TAGS) {\n\t\t\tstruct tag_ev_response *ev\n\t\t\t\t= (struct tag_ev_response *)data;\n\t\t\tint n;\n\n\t\t\tif ((n = plist_find_fname(plist, ev->file)) != -1) {\n\t\t\t\tif ((ev->tags->filled & tags_sel))\n\t\t\t\t\tfiles--;\n\t\t\t\tupdate_item_tags (plist, n, ev->tags);\n\t\t\t}\n\t\t}\n\t\telse if (no_iface)\n\t\t\tabort (); /* can't handle other events without the interface */\n\n\t\tif (!no_iface)\n\t\t\tserver_event (type, data);\n\t\telse\n\t\t\tfree_event_data (type, data);\n\t}\n\n\tiface_set_status (\"\");\n}\n\n/* Load the directory content into dir_plist and switch the menu to it.\n * If dir is NULL, go to the cwd.  If reload is not zero, we are reloading\n * the current directory, so use iface_update_dir_content().\n * Return 1 on success, 0 on error. */\nstatic int go_to_dir (const char *dir, const int reload)\n{\n\tstruct plist *old_dir_plist;\n\tchar last_dir[PATH_MAX];\n\tconst char *new_dir = dir ? dir : cwd;\n\tint going_up = 0;\n\tlists_t_strs *dirs, *playlists;\n\n\tiface_set_status (\"Reading directory...\");\n\n\tif (dir && is_subdir(dir, cwd)) {\n\t\tstrcpy (last_dir, strrchr(cwd, '/') + 1);\n\t\tstrcat (last_dir, \"/\");\n\t\tgoing_up = 1;\n\t}\n\n\told_dir_plist = dir_plist;\n\tdir_plist = (struct plist *)xmalloc (sizeof(struct plist));\n\tplist_init (dir_plist);\n\tdirs = lists_strs_new (FILES_LIST_INIT_SIZE);\n\tplaylists = lists_strs_new (FILES_LIST_INIT_SIZE);\n\n\tif (!read_directory(new_dir, dirs, playlists, dir_plist)) {\n\t\tiface_set_status (\"\");\n\t\tplist_free (dir_plist);\n\t\tlists_strs_free (dirs);\n\t\tlists_strs_free (playlists);\n\t\tfree (dir_plist);\n\t\tdir_plist = old_dir_plist;\n\t\treturn 0;\n\t}\n\n\t/* TODO: use CMD_ABORT_TAGS_REQUESTS (what if we requested tags for the\n\t playlist?) */\n\n\tplist_free (old_dir_plist);\n\tfree (old_dir_plist);\n\n\tif (dir) /* if dir is NULL, we went to cwd */\n\t\tstrcpy (cwd, dir);\n\n\tswitch_titles_file (dir_plist);\n\n\tplist_sort_fname (dir_plist);\n\tlists_strs_sort (dirs, sort_dirs_func);\n\tlists_strs_sort (playlists, sort_strcmp_func);\n\n\task_for_tags (dir_plist, get_tags_setting());\n\n\tif (reload)\n\t\tiface_update_dir_content (IFACE_MENU_DIR, dir_plist, dirs, playlists);\n\telse\n\t\tiface_set_dir_content (IFACE_MENU_DIR, dir_plist, dirs, playlists);\n\tlists_strs_free (dirs);\n\tlists_strs_free (playlists);\n\tif (going_up)\n\t\tiface_set_curr_item_title (last_dir);\n\n\tiface_set_title (IFACE_MENU_DIR, cwd);\n\tiface_update_queue_positions (queue, NULL, dir_plist, NULL);\n\n\tif (iface_in_plist_menu())\n\t\tiface_switch_to_dir ();\n\n\treturn 1;\n}\n\n/* Make sure that the server's playlist has different serial from ours. */\nstatic void change_srv_plist_serial ()\n{\n\tint serial;\n\n\tdo {\n\t\tsend_int_to_srv (CMD_GET_SERIAL);\n\t\tserial = get_data_int ();\n\t } while (serial == plist_get_serial(playlist) ||\n\t          serial == plist_get_serial(dir_plist));\n\n\tsend_int_to_srv (CMD_PLIST_SET_SERIAL);\n\tsend_int_to_srv (serial);\n}\n\nstatic void enter_first_dir ();\n\n/* Switch between the directory view and the playlist. */\nstatic void toggle_menu ()\n{\n\tint num;\n\n\tif (iface_in_plist_menu()) {\n\t\tif (!cwd[0])\n\t\t\t/* we were at the playlist from the startup */\n\t\t\tenter_first_dir ();\n\t\telse\n\t\t\tiface_switch_to_dir ();\n\t}\n\telse if ((num = plist_count(playlist)))\n\t\tiface_switch_to_plist ();\n\telse\n\t\terror (\"The playlist is empty.\");\n}\n\n/* Load the playlist file and switch the menu to it. Return 1 on success. */\nstatic int go_to_playlist (const char *file, const int load_serial,\n                           bool default_playlist)\n{\n\tif (plist_count(playlist)) {\n\t\terror (\"Please clear the playlist, because \"\n\t\t\t\t\"I'm not sure you want to do this.\");\n\t\treturn 0;\n\t}\n\n\tplist_clear (playlist);\n\n\tiface_set_status (\"Loading playlist...\");\n\tif (plist_load(playlist, file, cwd, load_serial)) {\n\n\t\tif (options_get_bool(\"SyncPlaylist\")) {\n\t\t\tsend_int_to_srv (CMD_LOCK);\n\t\t\tif (!load_serial)\n\t\t\t\tchange_srv_plist_serial ();\n\t\t\tsend_int_to_srv (CMD_CLI_PLIST_CLEAR);\n\t\t\tiface_set_status (\"Notifying clients...\");\n\t\t\tsend_items_to_clients (playlist);\n\t\t\tiface_set_status (\"\");\n\t\t\twaiting_for_plist_load = 1;\n\t\t\tsend_int_to_srv (CMD_UNLOCK);\n\n\t\t\t/* We'll use the playlist received from the\n\t\t\t * server to be synchronized with other clients.\n\t\t\t */\n\t\t\tplist_clear (playlist);\n\t\t}\n\t\telse {\n\t\t\tif (!default_playlist)\n\t\t\t\ttoggle_menu ();\n\t\t\tiface_set_dir_content (IFACE_MENU_PLIST, playlist, NULL, NULL);\n\t\t\tiface_update_queue_positions (queue, playlist, NULL, NULL);\n\t\t}\n\n\t\tinterface_message (\"Playlist loaded.\");\n\t}\n\telse {\n\t\tinterface_message (\"The playlist is empty\");\n\t\tiface_set_status (\"\");\n\t\treturn 0;\n\t}\n\n\treturn 1;\n}\n\n/* Enter to the initial directory or toggle to the initial playlist (only\n * if the function has not been called yet). */\nstatic void enter_first_dir ()\n{\n\tstatic int first_run = 1;\n\n\tif (options_get_bool(\"StartInMusicDir\")) {\n\t\tchar *music_dir;\n\n\t\tif ((music_dir = options_get_str(\"MusicDir\"))) {\n\t\t\tset_cwd (music_dir);\n\t\t\tif (first_run && file_type(music_dir) == F_PLAYLIST\n\t\t\t\t\t&& plist_count(playlist) == 0\n\t\t\t\t\t&& go_to_playlist(music_dir, 0, false)) {\n\t\t\t\tcwd[0] = 0;\n\t\t\t\tfirst_run = 0;\n\t\t\t}\n\t\t\telse if (file_type(cwd) == F_DIR\n\t\t\t\t\t&& go_to_dir(NULL, 0)) {\n\t\t\t\tfirst_run = 0;\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t\terror (\"MusicDir is not set\");\n\t}\n\n\tif (!(read_last_dir() && go_to_dir(NULL, 0))) {\n\t\tset_start_dir ();\n\t\tif (!go_to_dir(NULL, 0))\n\t\t\tinterface_fatal (\"Can't enter any directory!\");\n\t}\n\n\tfirst_run = 0;\n}\n\n/* Request the playlist from the server (given by another client).  Make\n * the titles.  Return 0 if such a list doesn't exist. */\nstatic int get_server_playlist (struct plist *plist)\n{\n\tiface_set_status (\"Getting the playlist...\");\n\tdebug (\"Getting the playlist...\");\n\tif (recv_server_plist(plist)) {\n\t\task_for_tags (plist, get_tags_setting());\n\t\tif (options_get_bool (\"ReadTags\"))\n\t\t\tswitch_titles_tags (plist);\n\t\telse\n\t\t\tswitch_titles_file (plist);\n\t\tiface_set_status (\"\");\n\t\treturn 1;\n\t}\n\n\tiface_set_status (\"\");\n\n\treturn 0;\n}\n\n/* Get the playlist from another client and use it as our playlist.\n * Return 0 if there is no client with a playlist. */\nstatic int use_server_playlist ()\n{\n\tif (get_server_playlist(playlist)) {\n\t\tiface_set_dir_content (IFACE_MENU_PLIST, playlist, NULL, NULL);\n\t\tiface_update_queue_positions (queue, playlist, NULL, NULL);\n\t\treturn 1;\n\t}\n\n\treturn 0;\n}\n\nstatic void use_server_queue ()\n{\n\tiface_set_status (\"Getting the queue...\");\n\tdebug (\"Getting the queue...\");\n\n\trecv_server_queue(queue);\n\tiface_set_files_in_queue (plist_count(queue));\n\tiface_update_queue_positions (queue, playlist, dir_plist, NULL);\n\tiface_set_status (\"\");\n}\n\n/* Process a single directory argument. */\nstatic void process_dir_arg (const char *dir)\n{\n\tset_cwd (dir);\n\tif (!go_to_dir (NULL, 0))\n\t\tenter_first_dir ();\n}\n\n/* Process a single playlist argument. */\nstatic void process_plist_arg (const char *file)\n{\n\tchar path[PATH_MAX + 1];   /* the playlist's directory */\n\tchar *slash;\n\n\tif (file[0] == '/')\n\t\tstrcpy (path, \"/\");\n\telse if (!getcwd (path, sizeof (path)))\n\t\tinterface_fatal (\"Can't get CWD: %s\", xstrerror (errno));\n\n\tresolve_path (path, sizeof (path), file);\n\tslash = strrchr (path, '/');\n\tassert (slash != NULL);\n\t*slash = 0;\n\n\tiface_set_status (\"Loading playlist...\");\n\tplist_load (playlist, file, path, 0);\n\tiface_set_status (\"\");\n}\n\n/* Process a list of arguments. */\nstatic void process_multiple_args (lists_t_strs *args)\n{\n\tint size, ix;\n\tconst char *arg;\n\tchar this_cwd[PATH_MAX];\n\n\tif (!getcwd (this_cwd, sizeof (cwd)))\n\t\tinterface_fatal (\"Can't get CWD: %s\", xstrerror (errno));\n\n\tsize = lists_strs_size (args);\n\n\tfor (ix = 0; ix < size; ix += 1) {\n\t\tint dir;\n\t\tchar path[2 * PATH_MAX];\n\n\t\targ = lists_strs_at (args, ix);\n\t\tdir = is_dir (arg);\n\n\t\tif (is_url (arg)) {\n\t\t\tstrncpy (path, arg, sizeof (path));\n\t\t\tpath[sizeof(path) - 1] = 0;\n\t\t}\n\t\telse {\n\t\t\tif (arg[0] == '/')\n\t\t\t\tstrcpy (path, \"/\");\n\t\t\telse\n\t\t\t\tstrcpy (path, this_cwd);\n\t\t\tresolve_path (path, sizeof (path), arg);\n\t\t}\n\n\t\tif (dir == 1)\n\t\t\tread_directory_recurr (path, playlist);\n\t\telse if (!dir && (is_sound_file (path) || is_url (path))) {\n\t\t\tif (plist_find_fname (playlist, path) == -1)\n\t\t\t\tplist_add (playlist, path);\n\t\t}\n\t\telse if (is_plist_file (path)) {\n\t\t\tchar *plist_dir, *slash;\n\n\t\t\t/* Here we've chosen to resolve the playlist's relative paths\n\t\t\t * with respect to the directory of the playlist (or of the\n\t\t\t * symlink being used to reference it).  If some other base is\n\t\t\t * desired, then we probably need to introduce a new option. */\n\n\t\t\tplist_dir = xstrdup (path);\n\t\t\tslash = strrchr (plist_dir, '/');\n\t\t\tassert (slash != NULL);\n\t\t\t*slash = 0;\n\n\t\t\tplist_load (playlist, path, plist_dir, 0);\n\n\t\t\tfree (plist_dir);\n\t\t}\n\t}\n}\n\n/* Process file names passed as arguments. */\nstatic void process_args (lists_t_strs *args)\n{\n\tint size;\n\tconst char *arg;\n\n\tsize = lists_strs_size (args);\n\targ = lists_strs_at (args, 0);\n\n\tif (size == 1 && is_dir (arg) == 1) {\n\t\tprocess_dir_arg (arg);\n\t\treturn;\n\t}\n\n\tif (size == 1 && is_plist_file (arg))\n\t\tprocess_plist_arg (arg);\n\telse\n\t\tprocess_multiple_args (args);\n\n\tif (plist_count (playlist) && !options_get_bool (\"SyncPlaylist\")) {\n\t\tswitch_titles_file (playlist);\n\t\task_for_tags (playlist, get_tags_setting ());\n\t\tiface_set_dir_content (IFACE_MENU_PLIST, playlist, NULL, NULL);\n\t\tiface_update_queue_positions (queue, playlist, NULL, NULL);\n\t\tiface_switch_to_plist ();\n\t}\n\telse\n\t\tenter_first_dir ();\n}\n\n/* Load the playlist from .moc directory. */\nstatic void load_playlist ()\n{\n\tchar *plist_file = create_file_name (PLAYLIST_FILE);\n\n\tif (file_type(plist_file) == F_PLAYLIST) {\n\t\tgo_to_playlist (plist_file, 1, true);\n\n\t\t/* We don't want to switch to the playlist after loading. */\n\t\twaiting_for_plist_load = 0;\n\t}\n}\n\n#ifdef SIGWINCH\n/* Handle resizing xterm. */\nstatic void do_resize ()\n{\n\tiface_resize ();\n\tlogit (\"resize\");\n\twant_resize = 0;\n}\n#endif\n\n/* Strip the last directory from the path. Returned memory is mallod()ed. */\nstatic char *dir_up (const char *path)\n{\n\tchar *slash;\n\tchar *dir;\n\n\tassert (path != NULL);\n\n\tdir = xstrdup (path);\n\tslash = strrchr (dir, '/');\n\tassert (slash != NULL);\n\tif (slash == dir)\n\t\t*(slash + 1) = 0;\n\telse\n\t\t*slash = 0;\n\n\treturn dir;\n}\n\nstatic void go_dir_up ()\n{\n\tchar *dir;\n\n\tdir = dir_up (cwd);\n\tgo_to_dir (dir, 0);\n\tfree (dir);\n}\n\n/* Return a generated playlist serial from the server and make sure\n * it's not the same as our playlist's serial. */\nstatic int get_safe_serial ()\n{\n\tint serial;\n\n\tdo {\n\t\tsend_int_to_srv (CMD_GET_SERIAL);\n\t\tserial = get_data_int ();\n\t} while (playlist &&\n\t\t\tserial == plist_get_serial(playlist)); /* check only the\n\t\t\t\t\t\t\t   playlist, because dir_plist has serial\n\t\t\t\t\t\t\t   -1 */\n\n\treturn serial;\n}\n\n/* Send the playlist to the server. If clear != 0, clear the server's playlist\n * before sending. */\nstatic void send_playlist (struct plist *plist, const int clear)\n{\n\tint i;\n\n\tif (clear)\n\t\tsend_int_to_srv (CMD_LIST_CLEAR);\n\n\tfor (i = 0; i < plist->num; i++) {\n\t\tif (!plist_deleted(plist, i)) {\n\t\t\tsend_int_to_srv (CMD_LIST_ADD);\n\t\t\tsend_str_to_srv (plist->items[i].file);\n\t\t}\n\t}\n}\n\n/* Send the playlist to the server if necessary and request playing this\n * item. */\nstatic void play_it (const char *file)\n{\n\tstruct plist *curr_plist;\n\n\tassert (file != NULL);\n\n\tif (iface_in_dir_menu())\n\t\tcurr_plist = dir_plist;\n\telse\n\t\tcurr_plist = playlist;\n\n\tsend_int_to_srv (CMD_LOCK);\n\n\tif (plist_get_serial(curr_plist) == -1 || get_server_plist_serial()\n\t\t\t!= plist_get_serial(curr_plist)) {\n\t\tint serial;\n\n\t\tlogit (\"The server has different playlist\");\n\n\t\tserial = get_safe_serial();\n\t\tplist_set_serial (curr_plist, serial);\n\t\tsend_int_to_srv (CMD_PLIST_SET_SERIAL);\n\t\tsend_int_to_srv (serial);\n\n\t\tsend_playlist (curr_plist, 1);\n\t}\n\telse\n\t\tlogit (\"The server already has my playlist\");\n\tsend_int_to_srv (CMD_PLAY);\n\tsend_str_to_srv (file);\n\n\tsend_int_to_srv (CMD_UNLOCK);\n}\n\n/* Action when the user selected a file. */\nstatic void go_file ()\n{\n\tenum file_type type = iface_curritem_get_type ();\n\tchar *file = iface_get_curr_file ();\n\n\tif (!file)\n\t\treturn;\n\n\tif (type == F_SOUND || type == F_URL)\n\t\tplay_it (file);\n\telse if (type == F_DIR && iface_in_dir_menu()) {\n\t\tif (!strcmp(file, \"..\"))\n\t\t\tgo_dir_up ();\n\t\telse\n\t\t\tgo_to_dir (file, 0);\n\t}\n\telse if (type == F_PLAYLIST)\n\t\tgo_to_playlist (file, 0, false);\n\n\tfree (file);\n}\n\n/* pause/unpause */\nstatic void switch_pause ()\n{\n\tswitch (curr_file.state) {\n\t\tcase STATE_PLAY:\n\t\t\tsend_int_to_srv (CMD_PAUSE);\n\t\t\tbreak;\n\t\tcase STATE_PAUSE:\n\t\t\tsend_int_to_srv (CMD_UNPAUSE);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tlogit (\"User pressed pause when not playing.\");\n\t}\n}\n\nstatic void set_mixer (int val)\n{\n\tval = CLAMP(0, val, 100);\n\tsend_int_to_srv (CMD_SET_MIXER);\n\tsend_int_to_srv (val);\n}\n\nstatic void adjust_mixer (const int diff)\n{\n\tset_mixer (get_mixer_value() + diff);\n}\n\n/* Recursively add the content of a directory to the playlist. */\nstatic void add_dir_plist ()\n{\n\tstruct plist plist;\n\tchar *file;\n\tenum file_type type;\n\n\tif (iface_in_plist_menu()) {\n\t\terror (\"Can't add to the playlist a file from the playlist.\");\n\t\treturn;\n\t}\n\n\tfile = iface_get_curr_file ();\n\n\tif (!file)\n\t\treturn;\n\n\ttype = iface_curritem_get_type ();\n\tif (type != F_DIR && type != F_PLAYLIST) {\n\t\terror (\"This is not a directory or a playlist.\");\n\t\tfree (file);\n\t\treturn;\n\t}\n\n\tif (!strcmp(file, \"..\")) {\n\t\terror (\"Can't add '..'.\");\n\t\tfree (file);\n\t\treturn;\n\t}\n\n\tiface_set_status (\"Reading directories...\");\n\tplist_init (&plist);\n\n\tif (type == F_DIR) {\n\t\tread_directory_recurr (file, &plist);\n\t\tplist_sort_fname (&plist);\n\t}\n\telse\n\t\tplist_load (&plist, file, cwd, 0);\n\n\tsend_int_to_srv (CMD_LOCK);\n\n\tplist_remove_common_items (&plist, playlist);\n\n\t/* Add the new files to the server's playlist if the server has our\n\t * playlist. */\n\tif (get_server_plist_serial() == plist_get_serial(playlist))\n\t\tsend_playlist (&plist, 0);\n\n\tif (options_get_bool(\"SyncPlaylist\")) {\n\t\tiface_set_status (\"Notifying clients...\");\n\t\tsend_items_to_clients (&plist);\n\t\tiface_set_status (\"\");\n\t}\n\telse {\n\t\tint i;\n\n\t\tswitch_titles_file (&plist);\n\t\task_for_tags (&plist, get_tags_setting());\n\n\t\tfor (i = 0; i < plist.num; i++)\n\t\t\tif (!plist_deleted(&plist, i))\n\t\t\t\tiface_add_to_plist (&plist, i);\n\t\tplist_cat (playlist, &plist);\n\t}\n\n\tsend_int_to_srv (CMD_UNLOCK);\n\n\tplist_free (&plist);\n\tfree (file);\n}\n\n/* To avoid lots of locks and unlocks, this assumes a lock is sent before\n * the first call and an unlock after the last.\n *\n * It's also assumed to be in the menu.\n */\nstatic void remove_file_from_playlist (const char *file)\n{\n\tassert (file != NULL);\n\tassert (plist_count(playlist) > 0);\n\n\tif (options_get_bool(\"SyncPlaylist\")) {\n\t\tsend_int_to_srv (CMD_CLI_PLIST_DEL);\n\t\tsend_str_to_srv (file);\n\t}\n\telse {\n\t\tint n = plist_find_fname (playlist, file);\n\n\t\tassert (n != -1);\n\n\t\tplist_delete (playlist, n);\n\t\tiface_del_plist_item (file);\n\n\t\tif (plist_count(playlist) == 0)\n\t\t\tclear_playlist ();\n\t}\n\n\t/* Delete this item from the server's playlist if it has our\n\t * playlist. */\n\tif (get_server_plist_serial() == plist_get_serial(playlist)) {\n\t\tsend_int_to_srv (CMD_DELETE);\n\t\tsend_str_to_srv (file);\n\t}\n}\n\n/* Remove all dead entries (point to non-existent or unreadable). */\nstatic void remove_dead_entries_plist ()\n{\n\tconst char *file = NULL;\n\tint i;\n\n\tif (! iface_in_plist_menu()) {\n\t\terror (\"Can't prune when not in the playlist.\");\n\t\treturn;\n\t}\n\n\tsend_int_to_srv (CMD_LOCK);\n\tfor (i = 0, file = plist_get_next_dead_entry(playlist, &i);\n\t     file != NULL;\n\t     file = plist_get_next_dead_entry(playlist, &i)) {\n\t\tremove_file_from_playlist (file);\n\t}\n\tsend_int_to_srv (CMD_UNLOCK);\n}\n\n/* Add the currently selected file to the playlist. */\nstatic void add_file_plist ()\n{\n\tchar *file;\n\n\tif (iface_in_plist_menu()) {\n\t\terror (\"Can't add to the playlist a file from the playlist.\");\n\t\treturn;\n\t}\n\n\tif (iface_curritem_get_type() == F_DIR) {\n\t\tadd_dir_plist();\n\t\treturn;\n\t}\n\n\tfile = iface_get_curr_file ();\n\n\tif (!file)\n\t\treturn;\n\n\tif (iface_curritem_get_type() != F_SOUND) {\n\t\terror (\"You can only add a file using this command.\");\n\t\tfree (file);\n\t\treturn;\n\t}\n\n\tif (plist_find_fname(playlist, file) == -1) {\n\t\tstruct plist_item *item = &dir_plist->items[\n\t\t\tplist_find_fname(dir_plist, file)];\n\n\t\tsend_int_to_srv (CMD_LOCK);\n\n\t\tif (options_get_bool(\"SyncPlaylist\")) {\n\t\t\tsend_int_to_srv (CMD_CLI_PLIST_ADD);\n\t\t\tsend_item_to_srv (item);\n\t\t}\n\t\telse {\n\t\t\tint added;\n\n\t\t\tadded = plist_add_from_item (playlist, item);\n\t\t\tiface_add_to_plist (playlist, added);\n\t\t}\n\n\t\t/* Add to the server's playlist if the server has our\n\t\t * playlist. */\n\t\tif (get_server_plist_serial() == plist_get_serial(playlist)) {\n\t\t\tsend_int_to_srv (CMD_LIST_ADD);\n\t\t\tsend_str_to_srv (file);\n\t\t}\n\t\tsend_int_to_srv (CMD_UNLOCK);\n\t}\n\telse\n\t\terror (\"The file is already on the playlist.\");\n\n\tiface_menu_key (KEY_CMD_MENU_DOWN);\n\n\tfree (file);\n}\n\nstatic void queue_toggle_file ()\n{\n\tchar *file;\n\n\tfile = iface_get_curr_file ();\n\n\tif (!file)\n\t\treturn;\n\n\tif (iface_curritem_get_type() != F_SOUND\n\t\t\t&& iface_curritem_get_type() != F_URL) {\n\t\terror (\"You can only add a file or URL using this command.\");\n\t\tfree (file);\n\t\treturn;\n\t}\n\n\t/* Check if the file is already in the queue; if it isn't, add it,\n\t * otherwise, remove it. */\n\n\tif (plist_find_fname(queue, file) == -1) {\n\t\t/* Add item to the server's queue. */\n\t\tsend_int_to_srv (CMD_QUEUE_ADD);\n\t\tsend_str_to_srv (file);\n\n\t\tlogit (\"Added to queue: %s\", file);\n\t}\n\telse {\n\t\t/* Delete this item from the server's queue. */\n\t\tsend_int_to_srv (CMD_QUEUE_DEL);\n\t\tsend_str_to_srv (file);\n\n\t\tlogit (\"Removed from queue: %s\", file);\n\t}\n\n\tiface_menu_key (KEY_CMD_MENU_DOWN);\n\n\tfree (file);\n}\n\nstatic void toggle_option (const char *name)\n{\n\tsend_int_to_srv (CMD_SET_OPTION);\n\tsend_str_to_srv (name);\n\tsend_bool_to_srv (!options_get_bool(name));\n\tsync_bool_option (name);\n}\n\nstatic void toggle_show_time ()\n{\n\tif (!strcasecmp (options_get_symb (\"ShowTime\"), \"yes\")) {\n\t\toptions_set_symb (\"ShowTime\", \"IfAvailable\");\n\t\tiface_set_status (\"ShowTime: IfAvailable\");\n\t}\n\telse if (!strcasecmp (options_get_symb (\"ShowTime\"), \"no\")) {\n\t\toptions_set_symb (\"ShowTime\", \"yes\");\n\t\tiface_update_show_time ();\n\t\task_for_tags (dir_plist, TAGS_TIME);\n\t\task_for_tags (playlist, TAGS_TIME);\n\t\tiface_set_status (\"ShowTime: yes\");\n\n\t}\n\telse { /* IfAvailable */\n\t\toptions_set_symb (\"ShowTime\", \"no\");\n\t\tiface_update_show_time ();\n\t\tiface_set_status (\"ShowTime: no\");\n\t}\n}\n\nstatic void toggle_show_format ()\n{\n\tbool show_format = !options_get_bool(\"ShowFormat\");\n\n\toptions_set_bool (\"ShowFormat\", show_format);\n\tif (show_format)\n\t\tiface_set_status (\"ShowFormat: yes\");\n\telse\n\t\tiface_set_status (\"ShowFormat: no\");\n\n\tiface_update_show_format ();\n}\n\n/* Reread the directory. */\nstatic void reread_dir ()\n{\n\tgo_to_dir (NULL, 1);\n}\n\n/* Clear the playlist on user request. */\nstatic void cmd_clear_playlist ()\n{\n\tif (options_get_bool(\"SyncPlaylist\")) {\n\t\tsend_int_to_srv (CMD_LOCK);\n\t\tsend_int_to_srv (CMD_CLI_PLIST_CLEAR);\n\t\tchange_srv_plist_serial ();\n\t\tsend_int_to_srv (CMD_UNLOCK);\n\t}\n\telse\n\t\tclear_playlist ();\n}\n\nstatic void cmd_clear_queue ()\n{\n\tsend_int_to_srv (CMD_QUEUE_CLEAR);\n}\n\nstatic void go_to_music_dir ()\n{\n\tconst char *musicdir_optn;\n\tchar music_dir[PATH_MAX] = \"/\";\n\n\tmusicdir_optn = options_get_str (\"MusicDir\");\n\n\tif (!musicdir_optn) {\n\t\terror (\"MusicDir not defined\");\n\t\treturn;\n\t}\n\n\tresolve_path (music_dir, sizeof(music_dir), musicdir_optn);\n\n\tswitch (file_type (music_dir)) {\n\tcase F_DIR:\n\t\tgo_to_dir (music_dir, 0);\n\t\tbreak;\n\tcase F_PLAYLIST:\n\t\tgo_to_playlist (music_dir, 0, false);\n\t\tbreak;\n\tdefault:\n\t\terror (\"MusicDir is neither a directory nor a playlist!\");\n\t}\n}\n\n/* Make a directory from the string resolving ~, './' and '..'.\n * Return the directory, the memory is malloc()ed.\n * Return NULL on error. */\nstatic char *make_dir (const char *str)\n{\n\tchar *dir;\n\tint add_slash = 0;\n\n\tdir = (char *)xmalloc (sizeof(char) * (PATH_MAX + 1));\n\n\tdir[PATH_MAX] = 0;\n\n\t/* If the string ends with a slash and is not just '/', add this\n\t * slash. */\n\tif (strlen(str) > 1 && str[strlen(str)-1] == '/')\n\t\tadd_slash = 1;\n\n\tif (str[0] == '~') {\n\t\tstrncpy (dir, get_home (), PATH_MAX);\n\n\t\tif (dir[PATH_MAX]) {\n\t\t\tlogit (\"Path too long!\");\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif (!strcmp(str, \"~\"))\n\t\t\tadd_slash = 1;\n\n\t\tstr++;\n\t}\n\telse if (str[0] != '/')\n\t\tstrcpy (dir, cwd);\n\telse\n\t\tstrcpy (dir, \"/\");\n\n\tresolve_path (dir, PATH_MAX, str);\n\n\tif (add_slash && strlen(dir) < PATH_MAX)\n\t\tstrcat (dir, \"/\");\n\n\treturn dir;\n}\n\nstatic void entry_key_go_dir (const struct iface_key *k)\n{\n\tif (k->type == IFACE_KEY_CHAR && k->key.ucs == '\\t') {\n\t\tchar *dir;\n\t\tchar *complete_dir;\n\t\tchar buf[PATH_MAX+1];\n\t\tchar *entry_text;\n\n\t\tentry_text = iface_entry_get_text ();\n\t\tif (!(dir = make_dir(entry_text))) {\n\t\t\tfree (entry_text);\n\t\t\treturn;\n\t\t}\n\t\tfree (entry_text);\n\n\t\tcomplete_dir = find_match_dir (dir);\n\n\t\tstrncpy (buf, complete_dir ? complete_dir : dir, sizeof(buf));\n\t\tif (complete_dir)\n\t\t\tfree (complete_dir);\n\n\t\tiface_entry_set_text (buf);\n\t\tfree (dir);\n\t}\n\telse if (k->type == IFACE_KEY_CHAR && k->key.ucs == '\\n') {\n\t\tchar *entry_text = iface_entry_get_text ();\n\n\t\tif (entry_text[0]) {\n\t\t\tchar *dir = make_dir (entry_text);\n\n\t\t\tiface_entry_history_add ();\n\n\t\t\tif (dir) {\n\t\t\t\t/* strip trailing slash */\n\t\t\t\tif (dir[strlen(dir)-1] == '/'\n\t\t\t\t\t\t&& strcmp(dir, \"/\"))\n\t\t\t\t\tdir[strlen(dir)-1] = 0;\n\t\t\t\tgo_to_dir (dir, 0);\n\t\t\t\tfree (dir);\n\t\t\t}\n\t\t}\n\n\t\tiface_entry_disable ();\n\t\tfree (entry_text);\n\t}\n\telse\n\t\tiface_entry_handle_key (k);\n}\n\n/* Request playing from the specified URL. */\nstatic void play_from_url (const char *url)\n{\n\tsend_int_to_srv (CMD_LOCK);\n\n\tchange_srv_plist_serial ();\n\tsend_int_to_srv (CMD_LIST_CLEAR);\n\tsend_int_to_srv (CMD_LIST_ADD);\n\tsend_str_to_srv (url);\n\n\tsend_int_to_srv (CMD_PLAY);\n\tsend_str_to_srv (\"\");\n\n\tsend_int_to_srv (CMD_UNLOCK);\n}\n\n/* Return malloc()ed string that is a copy of str without leading and trailing\n * white spaces. */\nstatic char *strip_white_spaces (const char *str)\n{\n\tchar *clean;\n\tint n;\n\n\tassert (str != NULL);\n\n\tn = strlen (str);\n\n\t/* Strip trailing. */\n\twhile (n > 0 && isblank(str[n-1]))\n\t\tn--;\n\n\t/* Strip leading whitespace. */\n\twhile (*str && isblank(*str)) {\n\t\tstr++;\n\t\tn--;\n\t}\n\n\tif (n > 0) {\n\t\tclean = (char *)xmalloc ((n + 1) * sizeof(char));\n\t\tstrncpy (clean, str, n);\n\t\tclean[n] = 0;\n\t}\n\telse\n\t\tclean = xstrdup (\"\");\n\n\treturn clean;\n}\n\nstatic void entry_key_go_url (const struct iface_key *k)\n{\n\tif (k->type == IFACE_KEY_CHAR && k->key.ucs == '\\n') {\n\t\tchar *entry_text = iface_entry_get_text ();\n\n\t\tif (entry_text[0]) {\n\t\t\tchar *clean_url = strip_white_spaces (entry_text);\n\n\t\t\tiface_entry_history_add ();\n\n\t\t\tif (is_url(clean_url))\n\t\t\t\tplay_from_url (clean_url);\n\t\t\telse\n\t\t\t\terror (\"Not a valid URL.\");\n\n\t\t\tfree (clean_url);\n\t\t}\n\n\t\tfree (entry_text);\n\t\tiface_entry_disable ();\n\t}\n\telse\n\t\tiface_entry_handle_key (k);\n}\n\nstatic void add_url_to_plist (const char *url)\n{\n\tassert (url != NULL);\n\n\tif (plist_find_fname(playlist, url) == -1) {\n\t\tsend_int_to_srv (CMD_LOCK);\n\n\t\tif (options_get_bool(\"SyncPlaylist\")) {\n\t\t\tstruct plist_item *item = plist_new_item ();\n\n\t\t\titem->file = xstrdup (url);\n\t\t\titem->title_file = xstrdup (url);\n\n\t\t\tsend_int_to_srv (CMD_CLI_PLIST_ADD);\n\t\t\tsend_item_to_srv (item);\n\n\t\t\tplist_free_item_fields (item);\n\t\t\tfree (item);\n\t\t}\n\t\telse {\n\t\t\tint added;\n\n\t\t\tadded = plist_add (playlist, url);\n\t\t\tmake_file_title (playlist, added, false);\n\t\t\tiface_add_to_plist (playlist, added);\n\t\t}\n\n\t\t/* Add to the server's playlist if the server has our\n\t\t * playlist. */\n\t\tif (get_server_plist_serial() == plist_get_serial(playlist)) {\n\t\t\tsend_int_to_srv (CMD_LIST_ADD);\n\t\t\tsend_str_to_srv (url);\n\t\t}\n\t\tsend_int_to_srv (CMD_UNLOCK);\n\t}\n\telse\n\t\terror (\"URL already on the playlist\");\n}\n\nstatic void entry_key_add_url (const struct iface_key *k)\n{\n\tif (k->type == IFACE_KEY_CHAR && k->key.ucs == '\\n') {\n\t\tchar *entry_text = iface_entry_get_text ();\n\n\t\tif (entry_text[0]) {\n\t\t\tchar *clean_url = strip_white_spaces (entry_text);\n\n\t\t\tiface_entry_history_add ();\n\n\t\t\tif (is_url(clean_url))\n\t\t\t\tadd_url_to_plist (clean_url);\n\t\t\telse\n\t\t\t\terror (\"Not a valid URL.\");\n\n\t\t\tfree (clean_url);\n\t\t}\n\n\t\tfree (entry_text);\n\t\tiface_entry_disable ();\n\t}\n\telse\n\t\tiface_entry_handle_key (k);\n}\n\nstatic void entry_key_search (const struct iface_key *k)\n{\n\tif (k->type == IFACE_KEY_CHAR && k->key.ucs == '\\n') {\n\t\tchar *file = iface_get_curr_file ();\n\t\tchar *text = iface_entry_get_text ();\n\n\t\tiface_entry_disable ();\n\n\t\tif (text[0]) {\n\n\t\t\tif (!strcmp(file, \"..\")) {\n\t\t\t\tfree (file);\n\t\t\t\tfile = dir_up (cwd);\n\t\t\t}\n\n\t\t\tif (is_url(file))\n\t\t\t\tplay_from_url (file);\n\t\t\telse if (file_type(file) == F_DIR)\n\t\t\t\tgo_to_dir (file, 0);\n\t\t\telse if (file_type(file) == F_PLAYLIST)\n\t\t\t\tgo_to_playlist (file, 0, false);\n\t\t\telse\n\t\t\t\tplay_it (file);\n\t\t}\n\n\t\tfree (text);\n\t\tfree (file);\n\t}\n\telse\n\t\tiface_entry_handle_key (k);\n}\n\nstatic void save_playlist (const char *file, const int save_serial)\n{\n\tiface_set_status (\"Saving the playlist...\");\n\tfill_tags (playlist, TAGS_COMMENTS | TAGS_TIME, 0);\n\tif (!user_wants_interrupt()) {\n\t\tif (plist_save (playlist, file, save_serial))\n\t\t\tinterface_message (\"Playlist saved\");\n\t}\n\telse\n\t\tiface_set_status (\"Aborted\");\n\tiface_set_status (\"\");\n}\n\nstatic void entry_key_plist_save (const struct iface_key *k)\n{\n\tif (k->type == IFACE_KEY_CHAR && k->key.ucs == '\\n') {\n\t\tchar *text = iface_entry_get_text ();\n\n\t\tiface_entry_disable ();\n\n\t\tif (text[0]) {\n\t\t\tchar *ext = ext_pos (text);\n\t\t\tchar *file;\n\n\t\t\t/* add extension if necessary */\n\t\t\tif (!ext || strcmp(ext, \"m3u\")) {\n\t\t\t\tchar *tmp = (char *)xmalloc((strlen(text) + 5) *\n\t\t\t\t\t\tsizeof(char));\n\n\t\t\t\tsprintf (tmp, \"%s.m3u\", text);\n\t\t\t\tfree (text);\n\t\t\t\ttext = tmp;\n\t\t\t}\n\n\t\t\tfile = make_dir (text);\n\n\t\t\tif (file_exists(file)) {\n\t\t\t\tiface_make_entry (ENTRY_PLIST_OVERWRITE);\n\t\t\t\tiface_entry_set_file (file);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tsave_playlist (file, 0);\n\n\t\t\t\tif (iface_in_dir_menu())\n\t\t\t\t\treread_dir ();\n\t\t\t}\n\n\t\t\tfree (file);\n\t\t}\n\n\t\tfree (text);\n\t}\n\telse\n\t\tiface_entry_handle_key (k);\n}\n\nstatic void entry_key_plist_overwrite (const struct iface_key *k)\n{\n\tif (k->type == IFACE_KEY_CHAR && toupper(k->key.ucs) == 'Y') {\n\t\tchar *file = iface_entry_get_file ();\n\n\t\tassert (file != NULL);\n\n\t\tiface_entry_disable ();\n\n\t\tsave_playlist (file, 0);\n\t\tif (iface_in_dir_menu())\n\t\t\treread_dir ();\n\n\t\tfree (file);\n\t}\n\telse if (k->type == IFACE_KEY_CHAR && toupper(k->key.ucs) == 'N') {\n\t\tiface_entry_disable ();\n\t\tiface_message (\"Not overwriting.\");\n\t}\n}\n\nstatic void entry_key_user_query (const struct iface_key *k)\n{\n\tif (k->type == IFACE_KEY_CHAR && k->key.ucs == '\\n') {\n\t\tchar *entry_text = iface_entry_get_text ();\n\t\tiface_entry_disable ();\n\t\tiface_user_reply (entry_text);\n\t\tfree (entry_text);\n\t}\n\telse\n\t\tiface_entry_handle_key (k);\n}\n\n/* Handle keys while in an entry. */\nstatic void entry_key (const struct iface_key *k)\n{\n\tswitch (iface_get_entry_type()) {\n\t\tcase ENTRY_GO_DIR:\n\t\t\tentry_key_go_dir (k);\n\t\t\tbreak;\n\t\tcase ENTRY_GO_URL:\n\t\t\tentry_key_go_url (k);\n\t\t\tbreak;\n\t\tcase ENTRY_ADD_URL:\n\t\t\tentry_key_add_url (k);\n\t\t\tbreak;\n\t\tcase ENTRY_SEARCH:\n\t\t\tentry_key_search (k);\n\t\t\tbreak;\n\t\tcase ENTRY_PLIST_SAVE:\n\t\t\tentry_key_plist_save (k);\n\t\t\tbreak;\n\t\tcase ENTRY_PLIST_OVERWRITE:\n\t\t\tentry_key_plist_overwrite (k);\n\t\t\tbreak;\n\t\tcase ENTRY_USER_QUERY:\n\t\t\tentry_key_user_query (k);\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tabort (); /* BUG */\n\t}\n}\n\n/* Update items in the menu for all items on the playlist. */\nstatic void update_iface_menu (const enum iface_menu menu,\n\t\tconst struct plist *plist)\n{\n\tint i;\n\n\tassert (plist != NULL);\n\n\tfor (i = 0; i < plist->num; i++)\n\t\tif (!plist_deleted(plist, i))\n\t\t\tiface_update_item (menu, plist, i);\n}\n\n/* Switch ReadTags options and update the menu. */\nstatic void switch_read_tags ()\n{\n\tif (options_get_bool (\"ReadTags\")) {\n\t\toptions_set_bool (\"ReadTags\", false);\n\t\tswitch_titles_file (dir_plist);\n\t\tswitch_titles_file (playlist);\n\t\tiface_set_status (\"ReadTags: no\");\n\t}\n\telse {\n\t\toptions_set_bool (\"ReadTags\", true);\n\t\task_for_tags (dir_plist, TAGS_COMMENTS);\n\t\task_for_tags (playlist, TAGS_COMMENTS);\n\t\tswitch_titles_tags (dir_plist);\n\t\tswitch_titles_tags (playlist);\n\t\tiface_set_status (\"ReadTags: yes\");\n\t}\n\n\tupdate_iface_menu (IFACE_MENU_DIR, dir_plist);\n\tupdate_iface_menu (IFACE_MENU_PLIST, playlist);\n}\n\nstatic void seek (const int sec)\n{\n\tsend_int_to_srv (CMD_SEEK);\n\tsend_int_to_srv (sec);\n}\n\nstatic void jump_to (const int sec)\n{\n\tsend_int_to_srv (CMD_JUMP_TO);\n\tsend_int_to_srv (sec);\n}\n\nstatic void delete_item ()\n{\n\tchar *file;\n\n\tif (!iface_in_plist_menu()) {\n\t\terror (\"You can only delete an item from the playlist.\");\n\t\treturn;\n\t}\n\n\tassert (plist_count(playlist) > 0);\n\n\tfile = iface_get_curr_file ();\n\n\tsend_int_to_srv (CMD_LOCK);\n\tremove_file_from_playlist (file);\n\tsend_int_to_srv (CMD_UNLOCK);\n\n\tfree (file);\n}\n\n/* Select the file that is currently played. */\nstatic void go_to_playing_file ()\n{\n\tif (curr_file.file && file_type(curr_file.file) == F_SOUND) {\n\t\tif (plist_find_fname(playlist, curr_file.file) != -1)\n\t\t\tiface_switch_to_plist ();\n\t\telse if (plist_find_fname(dir_plist, curr_file.file) != -1)\n\t\t\tiface_switch_to_dir ();\n\t\telse {\n\t\t\tchar *slash;\n\t\t\tchar *file = xstrdup (curr_file.file);\n\n\t\t\tslash = strrchr (file, '/');\n\t\t\tassert (slash != NULL);\n\t\t\t*slash = 0;\n\n\t\t\tif (file[0])\n\t\t\t\tgo_to_dir (file, 0);\n\t\t\telse\n\t\t\t\tgo_to_dir (\"/\", 0);\n\n\t\t\tiface_switch_to_dir ();\n\t\t\tfree (file);\n\t\t}\n\n\t\tiface_select_file (curr_file.file);\n\t}\n}\n\n/* Return the time like the standard time() function, but rounded i.e. if we\n * have 11.8 seconds, return 12 seconds. */\nstatic time_t rounded_time ()\n{\n\tstruct timespec exact_time;\n\ttime_t curr_time;\n\n\tif (get_realtime (&exact_time) == -1)\n\t\tinterface_fatal (\"get_realtime() failed: %s\", xstrerror (errno));\n\n\tcurr_time = exact_time.tv_sec;\n\tif (exact_time.tv_nsec > 500000000L)\n\t\tcurr_time += 1;\n\n\treturn curr_time;\n}\n\n/* Handle silent seek key. */\nstatic void seek_silent (const int sec)\n{\n\tif (curr_file.state == STATE_PLAY && curr_file.file\n\t\t\t&& !is_url(curr_file.file)) {\n\t\tif (silent_seek_pos == -1) {\n\t\t\tsilent_seek_pos = curr_file.curr_time + sec;\n\t\t}\n\t\telse\n\t\t\tsilent_seek_pos += sec;\n\n\t\tsilent_seek_pos = CLAMP(0, silent_seek_pos, curr_file.total_time);\n\n\t\tsilent_seek_key_last = rounded_time ();\n\t\tiface_set_curr_time (silent_seek_pos);\n\t}\n}\n\n/* Move the current playlist item (direction: 1 - up, -1 - down). */\nstatic void move_item (const int direction)\n{\n\tchar *file;\n\tint second;\n\tchar *second_file;\n\n\tif (!iface_in_plist_menu()) {\n\t\terror (\"You can move only playlist items.\");\n\t\treturn;\n\t}\n\n\tif (!(file = iface_get_curr_file()))\n\t\treturn;\n\n\tsecond = plist_find_fname (playlist, file);\n\tassert (second != -1);\n\n\tif (direction == -1)\n\t\tsecond = plist_next (playlist, second);\n\telse if (direction == 1)\n\t\tsecond = plist_prev (playlist, second);\n\telse\n\t\tabort (); /* BUG */\n\n\tif (second == -1) {\n\t\tfree (file);\n\t\treturn;\n\t}\n\n\tsecond_file = plist_get_file (playlist, second);\n\n\tsend_int_to_srv (CMD_LOCK);\n\n\tif (options_get_bool(\"SyncPlaylist\")) {\n\t\tsend_int_to_srv (CMD_CLI_PLIST_MOVE);\n\t\tsend_str_to_srv (file);\n\t\tsend_str_to_srv (second_file);\n\t}\n\telse\n\t\tswap_playlist_items (file, second_file);\n\n\t/* update the server's playlist */\n\tif (get_server_plist_serial() == plist_get_serial(playlist)) {\n\t\tsend_int_to_srv (CMD_LIST_MOVE);\n\t\tsend_str_to_srv (file);\n\t\tsend_str_to_srv (second_file);\n\t}\n\n\tsend_int_to_srv (CMD_UNLOCK);\n\n\tfree (second_file);\n\tfree (file);\n}\n\n/* Handle releasing silent seek key. */\nstatic void do_silent_seek ()\n{\n\ttime_t curr_time = time(NULL);\n\n\tif (silent_seek_pos != -1 && silent_seek_key_last < curr_time) {\n\t\tseek (silent_seek_pos - curr_file.curr_time - 1);\n\t\tsilent_seek_pos = -1;\n\t\tiface_set_curr_time (curr_file.curr_time);\n\t}\n}\n\n/* Handle the 'next' command. */\nstatic void cmd_next ()\n{\n\tif (curr_file.state != STATE_STOP)\n\t\tsend_int_to_srv (CMD_NEXT);\n\telse if (plist_count(playlist)) {\n\t\tif (plist_get_serial(playlist) != -1\n\t\t\t\t|| get_server_plist_serial()\n\t\t\t\t!= plist_get_serial(playlist)) {\n\t\t\tint serial;\n\n\t\t\tsend_int_to_srv (CMD_LOCK);\n\n\t\t\tsend_playlist (playlist, 1);\n\t\t\tserial = get_safe_serial();\n\t\t\tplist_set_serial (playlist, serial);\n\t\t\tsend_int_to_srv (CMD_PLIST_SET_SERIAL);\n\t\t\tsend_int_to_srv (plist_get_serial(playlist));\n\n\t\t\tsend_int_to_srv (CMD_UNLOCK);\n\t\t}\n\n\t\tsend_int_to_srv (CMD_PLAY);\n\t\tsend_str_to_srv (\"\");\n\t}\n}\n\n/* Add themes found in the directory to the list of theme files. */\nstatic void add_themes_to_list (lists_t_strs *themes, const char *themes_dir)\n{\n\tDIR *dir;\n\tstruct dirent *entry;\n\n\tassert (themes);\n\tassert (themes_dir);\n\n\tif (!(dir = opendir (themes_dir))) {\n\t\tchar *err = xstrerror (errno);\n\t\tlogit (\"Can't open themes directory %s: %s\", themes_dir, err);\n\t\tfree (err);\n\t\treturn;\n\t}\n\n\twhile ((entry = readdir(dir))) {\n\t\tint rc;\n\t\tchar file[PATH_MAX];\n\n\t\tif (entry->d_name[0] == '.')\n\t\t\tcontinue;\n\n\t\t/* Filter out backup files (*~). */\n\t\tif (entry->d_name[strlen(entry->d_name)-1] == '~')\n\t\t\tcontinue;\n\n\t\trc = snprintf(file, sizeof(file), \"%s/%s\", themes_dir, entry->d_name);\n\t\tif (rc >= ssizeof(file))\n\t\t\tcontinue;\n\n\t\tlists_strs_append (themes, file);\n\t}\n\n\tclosedir (dir);\n}\n\n/* Compare two pathnames based on filename. */\nstatic int themes_cmp (const void *a, const void *b)\n{\n\tint result;\n\tchar *sa = *(char **)a;\n\tchar *sb = *(char **)b;\n\n\tresult = strcoll (strrchr (sa, '/') + 1, strrchr (sb, '/') + 1);\n\tif (result == 0)\n\t\tresult = strcoll (sa, sb);\n\n\treturn result;\n}\n\n/* Add themes found in the directories to the theme selection menu.\n * Return the number of items added. */\nstatic int add_themes_to_menu (const char *user_themes,\n                               const char *system_themes)\n{\n\tint ix;\n\tlists_t_strs *themes;\n\n\tassert (user_themes);\n\tassert (system_themes);\n\n\tthemes = lists_strs_new (16);\n\tadd_themes_to_list (themes, user_themes);\n\tadd_themes_to_list (themes, system_themes);\n\tlists_strs_sort (themes, themes_cmp);\n\n\tfor (ix = 0; ix < lists_strs_size (themes); ix += 1) {\n\t\tchar *file;\n\n\t\tfile = lists_strs_at (themes, ix);\n\t\tiface_add_file (file, strrchr (file, '/') + 1, F_THEME);\n\t}\n\n\tlists_strs_free (themes);\n\n\treturn ix;\n}\n\nstatic void make_theme_menu ()\n{\n\tiface_switch_to_theme_menu ();\n\n\tif (add_themes_to_menu (create_file_name (\"themes\"),\n\t                        SYSTEM_THEMES_DIR) == 0) {\n\t\tif (!cwd[0])\n\t\t\tenter_first_dir (); /* we were at the playlist from the startup */\n\t\telse\n\t\t\tiface_switch_to_dir ();\n\n\t\terror (\"No themes found.\");\n\t}\n\n\tiface_update_theme_selection (get_current_theme ());\n\tiface_refresh ();\n}\n\n/* Use theme from the currently selected file. */\nstatic void use_theme ()\n{\n\tchar *file = iface_get_curr_file ();\n\n\tassert (iface_curritem_get_type() == F_THEME);\n\n\tif (!file)\n\t\treturn;\n\n\tthemes_switch_theme (file);\n\tiface_update_attrs ();\n\tiface_refresh ();\n\n\tfree (file);\n}\n\n/* Handle keys while in the theme menu. */\nstatic void theme_menu_key (const struct iface_key *k)\n{\n\tif (!iface_key_is_resize(k)) {\n\t\tenum key_cmd cmd = get_key_cmd (CON_MENU, k);\n\n\t\tswitch (cmd) {\n\t\t\tcase KEY_CMD_GO:\n\t\t\t\tuse_theme ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MENU_DOWN:\n\t\t\tcase KEY_CMD_MENU_UP:\n\t\t\tcase KEY_CMD_MENU_NPAGE:\n\t\t\tcase KEY_CMD_MENU_PPAGE:\n\t\t\tcase KEY_CMD_MENU_FIRST:\n\t\t\tcase KEY_CMD_MENU_LAST:\n\t\t\t\tiface_menu_key (cmd);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tiface_switch_to_dir ();\n\t\t\t\tlogit (\"Bad key\");\n\t\t}\n\t}\n}\n\n/* Make sure that we have tags and a title for this file which is in a menu. */\nstatic void make_sure_tags_exist (const char *file)\n{\n\tstruct plist *plist;\n\tint item_num;\n\n\tif (file_type(file) != F_SOUND)\n\t\treturn;\n\n\tif ((item_num = plist_find_fname(dir_plist, file)) != -1)\n\t\tplist = dir_plist;\n\telse if ((item_num = plist_find_fname(playlist, file)) != -1)\n\t\tplist = playlist;\n\telse\n\t\treturn;\n\n\tif (!plist->items[item_num].tags\n\t\t\t|| plist->items[item_num].tags->filled\n\t\t\t\t!= (TAGS_COMMENTS | TAGS_TIME)) {\n\t\tint got_it = 0;\n\n\t\tsend_tags_request (file, TAGS_COMMENTS | TAGS_TIME);\n\n\t\twhile (!got_it) {\n\t\t\tint type = get_int_from_srv ();\n\t\t\tvoid *data = get_event_data (type);\n\n\t\t\tif (type == EV_FILE_TAGS) {\n\t\t\t\tstruct tag_ev_response *ev\n\t\t\t\t\t= (struct tag_ev_response *)data;\n\n\t\t\t\tif (!strcmp(ev->file, file))\n\t\t\t\t\tgot_it = 1;\n\t\t\t}\n\n\t\t\tserver_event (type, data);\n\t\t}\n\t}\n}\n\n/* Request tags from the server for a file in the playlist or the directory\n * menu, wait until they arrive and return them (malloc()ed). */\nstatic struct file_tags *get_tags (const char *file)\n{\n\tstruct plist *plist;\n\tint item_num;\n\n\tmake_sure_tags_exist (file);\n\n\tif ((item_num = plist_find_fname(dir_plist, file)) != -1)\n\t\tplist = dir_plist;\n\telse if ((item_num = plist_find_fname(playlist, file)) != -1)\n\t\tplist = playlist;\n\telse\n\t\treturn tags_new ();\n\n\tif (file_type(file) == F_SOUND)\n\t\treturn tags_dup (plist->items[item_num].tags);\n\n\treturn tags_new ();\n}\n\n/* Get the title of a file (malloc()ed) that is present in a menu. */\nstatic char *get_title (const char *file)\n{\n\tstruct plist *plist;\n\tint item_num;\n\n\tmake_sure_tags_exist (file);\n\n\tif ((item_num = plist_find_fname(dir_plist, file)) != -1)\n\t\tplist = dir_plist;\n\telse if ((item_num = plist_find_fname(playlist, file)) != -1)\n\t\tplist = playlist;\n\telse\n\t\treturn NULL;\n\n\treturn xstrdup (plist->items[item_num].title_tags\n\t\t\t? plist->items[item_num].title_tags\n\t\t\t: plist->items[item_num].title_file);\n}\n\n/* Substitute arguments for custom command that begin with '%'.\n * The new value is returned. */\nstatic char *custom_cmd_substitute (const char *arg)\n{\n\tchar *result = NULL;\n\tchar *file = NULL;\n\tstruct file_tags *tags = NULL;\n\n\tif (strlen (arg) == 2 && arg[0] == '%') {\n\t\tswitch (arg[1]) {\n\t\tcase 'i':\n\t\t\tfile = iface_get_curr_file ();\n\t\t\tresult = get_title (file);\n\t\t\tbreak;\n\t\tcase 't':\n\t\t\tfile = iface_get_curr_file ();\n\t\t\ttags = get_tags (file);\n\t\t\tresult = xstrdup (tags->title);\n\t\t\tbreak;\n\t\tcase 'a':\n\t\t\tfile = iface_get_curr_file ();\n\t\t\ttags = get_tags (file);\n\t\t\tresult = xstrdup (tags->album);\n\t\t\tbreak;\n\t\tcase 'r':\n\t\t\tfile = iface_get_curr_file ();\n\t\t\ttags = get_tags (file);\n\t\t\tresult = xstrdup (tags->artist);\n\t\t\tbreak;\n\t\tcase 'n':\n\t\t\tfile = iface_get_curr_file ();\n\t\t\ttags = get_tags (file);\n\t\t\tresult = (char *) xmalloc (sizeof (char) * 16);\n\t\t\tsnprintf (result, 16, \"%d\", tags->track);\n\t\t\tbreak;\n\t\tcase 'm':\n\t\t\tfile = iface_get_curr_file ();\n\t\t\ttags = get_tags (file);\n\t\t\tresult = (char *) xmalloc (sizeof (char) * 16);\n\t\t\tsnprintf (result, 16, \"%d\", tags->time);\n\t\t\tbreak;\n\t\tcase 'f':\n\t\t\tresult = iface_get_curr_file ();\n\t\t\tbreak;\n\t\tcase 'I':\n\t\t\tresult = xstrdup (curr_file.title);\n\t\t\tbreak;\n\t\tcase 'T':\n\t\t\tif (curr_file.tags && curr_file.tags->title)\n\t\t\t\tresult = xstrdup (curr_file.tags->title);\n\t\t\tbreak;\n\t\tcase 'A':\n\t\t\tif (curr_file.tags && curr_file.tags->album)\n\t\t\t\tresult = xstrdup (curr_file.tags->album);\n\t\t\tbreak;\n\t\tcase 'R':\n\t\t\tif (curr_file.tags && curr_file.tags->artist)\n\t\t\t\tresult = xstrdup (curr_file.tags->artist);\n\t\t\tbreak;\n\t\tcase 'N':\n\t\t\tif (curr_file.tags && curr_file.tags->track != -1) {\n\t\t\t\tresult = (char *) xmalloc (sizeof (char) * 16);\n\t\t\t\tsnprintf (result, 16, \"%d\", curr_file.tags->track);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'M':\n\t\t\tif (curr_file.tags && curr_file.tags->time != -1) {\n\t\t\t\tresult = (char *) xmalloc (sizeof (char) * 16);\n\t\t\t\tsnprintf (result, 16, \"%d\", curr_file.tags->time);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'F':\n\t\t\tif (curr_file.file)\n\t\t\t\tresult = xstrdup (curr_file.file);\n\t\t\tbreak;\n\t\tcase 'S':\n\t\t\tif (curr_file.file && curr_file.block_file) {\n\t\t\t\tresult = (char *) xmalloc (sizeof (char) * 16);\n\t\t\t\tsnprintf (result, 16, \"%d\", curr_file.block_start);\n\t\t\t}\n\t\t\tbreak;\n\t\tcase 'E':\n\t\t\tif (curr_file.file && curr_file.block_file) {\n\t\t\t\tresult = (char *) xmalloc (sizeof (char) * 16);\n\t\t\t\tsnprintf (result, 16, \"%d\", curr_file.block_end);\n\t\t\t}\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tresult = xstrdup (arg);\n\t\t}\n\t}\n\telse\n\t\tresult = xstrdup (arg);\n\n\t/* Replace nonexisting data with an empty string. */\n\tif (!result)\n\t\tresult = xstrdup (\"\");\n\n\tfree (file);\n\tif (tags)\n\t\ttags_free (tags);\n\n\treturn result;\n}\n\nstatic void run_external_cmd (char **args, const int arg_num ASSERT_ONLY)\n{\n\tpid_t child;\n\n\tassert (args != NULL);\n\tassert (arg_num >= 1);\n\tassert (args[0] != NULL);\n\tassert (args[arg_num] == NULL);\n\n\tiface_temporary_exit ();\n\n\tchild = fork();\n\tif (child == -1)\n\t\terror_errno (\"fork() failed\", errno);\n\telse {\n\t\tint status;\n\n\t\tif (child == 0) { /* I'm a child. */\n\t\t\tchar *err;\n\n\t\t\tputchar ('\\n');\n\t\t\texecvp (args[0], args);\n\n\t\t\t/* We have an error. */\n\t\t\terr = xstrerror (errno);\n\t\t\tfprintf (stderr, \"\\nError executing %s: %s\\n\", args[0], err);\n\t\t\tfree (err);\n\t\t\txsleep (2, 1);\n\t\t\texit (EXIT_FAILURE);\n\t\t}\n\n\t\t/* parent */\n\t\twaitpid (child, &status, 0);\n\t\tif (WIFEXITED(status) && WEXITSTATUS(status) != 0) {\n\t\t\tfprintf (stderr, \"\\nCommand exited with error (status %d).\\n\",\n\t\t\t                 WEXITSTATUS(status));\n\t\t\txsleep (2, 1);\n\t\t}\n\t\tiface_restore ();\n\t}\n}\n\n/* Exec (execvp()) a custom command (ExecCommand[1-10] options). */\nstatic void exec_custom_command (const char *option)\n{\n\tchar *cmd, *arg;\n\tchar **args;\n\tint ix, arg_num;\n\tlists_t_strs *cmd_list, *arg_list;\n\n\tassert (option != NULL);\n\n\tcmd = options_get_str (option);\n\tif (!cmd || !cmd[0]) {\n\t\terror (\"%s is not set\", option);\n\t\treturn;\n\t}\n\n\t/* Split into arguments. */\n\tcmd_list = lists_strs_new (4);\n\targ_num = lists_strs_tokenise (cmd_list, cmd);\n\tif (arg_num == 0) {\n\t\terror (\"Malformed %s option\", option);\n\t\tlists_strs_free (cmd_list);\n\t\treturn;\n\t}\n\n\targ_list = lists_strs_new (lists_strs_size (cmd_list));\n\tfor (ix = 0; ix < arg_num; ix += 1) {\n\t\targ = custom_cmd_substitute (lists_strs_at (cmd_list, ix));\n\t\tlists_strs_push (arg_list, arg);\n\t}\n\tlists_strs_free (cmd_list);\n\n\tcmd = lists_strs_fmt (arg_list, \" %s\");\n\tlogit (\"Running command:%s\", cmd);\n\tfree (cmd);\n\n\targs = lists_strs_save (arg_list);\n\tlists_strs_free (arg_list);\n\trun_external_cmd (args, arg_num);\n\tfree (args);\n\n\tif (iface_in_dir_menu())\n\t\treread_dir ();\n}\n\nstatic void go_to_fast_dir (const int num)\n{\n\tchar option_name[20];\n\n\tassert (RANGE(1, num, 10));\n\n\tsprintf (option_name, \"FastDir%d\", num);\n\n\tif (options_get_str(option_name)) {\n\t\tchar dir[PATH_MAX] = \"/\";\n\n\t\tresolve_path (dir, sizeof(dir), options_get_str(option_name));\n\t\tgo_to_dir (dir, 0);\n\t}\n\telse\n\t\terror (\"%s is not defined\", option_name);\n}\n\nstatic void toggle_playlist_full_paths (void)\n{\n\tbool new_val = !options_get_bool (\"PlaylistFullPaths\");\n\n\toptions_set_bool (\"PlaylistFullPaths\", new_val);\n\n\tif (new_val)\n\t\tiface_set_status (\"PlaylistFullPaths: on\");\n\telse\n\t\tiface_set_status (\"PlaylistFullPaths: off\");\n\n\tupdate_iface_menu (IFACE_MENU_PLIST, playlist);\n}\n\n/* Handle key. */\nstatic void menu_key (const struct iface_key *k)\n{\n\tif (iface_in_help ())\n\t\tiface_handle_help_key (k);\n\telse if (iface_in_lyrics ())\n\t\tiface_handle_lyrics_key (k);\n\telse if (iface_in_entry ())\n\t\tentry_key (k);\n\telse if (iface_in_theme_menu ())\n\t\ttheme_menu_key (k);\n\telse if (!iface_key_is_resize (k)) {\n\t\tenum key_cmd cmd = get_key_cmd (CON_MENU, k);\n\n\t\tswitch (cmd) {\n\t\t\tcase KEY_CMD_QUIT_CLIENT:\n\t\t\t\twant_quit = QUIT_CLIENT;\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_GO:\n\t\t\t\tgo_file ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MENU_DOWN:\n\t\t\tcase KEY_CMD_MENU_UP:\n\t\t\tcase KEY_CMD_MENU_NPAGE:\n\t\t\tcase KEY_CMD_MENU_PPAGE:\n\t\t\tcase KEY_CMD_MENU_FIRST:\n\t\t\tcase KEY_CMD_MENU_LAST:\n\t\t\t\tiface_menu_key (cmd);\n\t\t\t\tlast_menu_move_time = time (NULL);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_QUIT:\n\t\t\t\twant_quit = QUIT_SERVER;\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_STOP:\n\t\t\t\tsend_int_to_srv (CMD_STOP);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_NEXT:\n\t\t\t\tcmd_next ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PREVIOUS:\n\t\t\t\tsend_int_to_srv (CMD_PREV);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PAUSE:\n\t\t\t\tswitch_pause ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_READ_TAGS:\n\t\t\t\tswitch_read_tags ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_SHUFFLE:\n\t\t\t\ttoggle_option (\"Shuffle\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_REPEAT:\n\t\t\t\ttoggle_option (\"Repeat\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_AUTO_NEXT:\n\t\t\t\ttoggle_option (\"AutoNext\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_MENU:\n\t\t\t\ttoggle_menu ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_PLAYLIST_FULL_PATHS:\n\t\t\t\ttoggle_playlist_full_paths ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PLIST_ADD_FILE:\n\t\t\t\tadd_file_plist ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PLIST_CLEAR:\n\t\t\t\tcmd_clear_playlist ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PLIST_ADD_DIR:\n\t\t\t\tadd_dir_plist ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PLIST_REMOVE_DEAD_ENTRIES:\n\t\t\t\tremove_dead_entries_plist ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MIXER_DEC_1:\n\t\t\t\tadjust_mixer (-1);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MIXER_DEC_5:\n\t\t\t\tadjust_mixer (-5);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MIXER_INC_5:\n\t\t\t\tadjust_mixer (+5);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MIXER_INC_1:\n\t\t\t\tadjust_mixer (+1);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_SEEK_BACKWARD:\n\t\t\t\tseek (-options_get_int (\"SeekTime\"));\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_SEEK_FORWARD:\n\t\t\t\tseek (options_get_int (\"SeekTime\"));\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_HELP:\n\t\t\t\tiface_switch_to_help ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_LYRICS:\n\t\t\t\tiface_switch_to_lyrics ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_HIDE_MESSAGE:\n\t\t\t\tiface_disable_message ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_REFRESH:\n\t\t\t\tiface_refresh ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_RELOAD:\n\t\t\t\tif (iface_in_dir_menu ())\n\t\t\t\t\treread_dir ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_SHOW_HIDDEN_FILES:\n\t\t\t\toptions_set_bool (\"ShowHiddenFiles\",\n\t\t\t\t                  !options_get_bool (\"ShowHiddenFiles\"));\n\t\t\t\tif (iface_in_dir_menu ())\n\t\t\t\t\treread_dir ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_GO_MUSIC_DIR:\n\t\t\t\tgo_to_music_dir ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PLIST_DEL:\n\t\t\t\tdelete_item ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MENU_SEARCH:\n\t\t\t\tiface_make_entry (ENTRY_SEARCH);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PLIST_SAVE:\n\t\t\t\tif (plist_count (playlist))\n\t\t\t\t\tiface_make_entry (ENTRY_PLIST_SAVE);\n\t\t\t\telse\n\t\t\t\t\terror (\"The playlist is empty.\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_SHOW_TIME:\n\t\t\t\ttoggle_show_time ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_SHOW_FORMAT:\n\t\t\t\ttoggle_show_format ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_GO_TO_PLAYING_FILE:\n\t\t\t\tgo_to_playing_file ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_GO_DIR:\n\t\t\t\tiface_make_entry (ENTRY_GO_DIR);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_GO_URL:\n\t\t\t\tiface_make_entry (ENTRY_GO_URL);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_GO_DIR_UP:\n\t\t\t\tgo_dir_up ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_WRONG:\n\t\t\t\terror (\"Bad command\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_SEEK_FORWARD_5:\n\t\t\t\tseek_silent (options_get_int (\"SilentSeekTime\"));\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_SEEK_BACKWARD_5:\n\t\t\t\tseek_silent (-options_get_int (\"SilentSeekTime\"));\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_VOLUME_10:\n\t\t\t\tset_mixer (10);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_VOLUME_20:\n\t\t\t\tset_mixer (20);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_VOLUME_30:\n\t\t\t\tset_mixer (30);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_VOLUME_40:\n\t\t\t\tset_mixer (40);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_VOLUME_50:\n\t\t\t\tset_mixer (50);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_VOLUME_60:\n\t\t\t\tset_mixer (60);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_VOLUME_70:\n\t\t\t\tset_mixer (70);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_VOLUME_80:\n\t\t\t\tset_mixer (80);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_VOLUME_90:\n\t\t\t\tset_mixer (90);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MARK_START:\n\t\t\t\tfile_info_block_mark (&curr_file.block_start);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MARK_END:\n\t\t\t\tfile_info_block_mark (&curr_file.block_end);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_1:\n\t\t\t\tgo_to_fast_dir (1);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_2:\n\t\t\t\tgo_to_fast_dir (2);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_3:\n\t\t\t\tgo_to_fast_dir (3);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_4:\n\t\t\t\tgo_to_fast_dir (4);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_5:\n\t\t\t\tgo_to_fast_dir (5);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_6:\n\t\t\t\tgo_to_fast_dir (6);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_7:\n\t\t\t\tgo_to_fast_dir (7);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_8:\n\t\t\t\tgo_to_fast_dir (8);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_9:\n\t\t\t\tgo_to_fast_dir (9);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_FAST_DIR_10:\n\t\t\t\tgo_to_fast_dir (10);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_MIXER:\n\t\t\t\tdebug (\"Toggle mixer.\");\n\t\t\t\tsend_int_to_srv (CMD_TOGGLE_MIXER_CHANNEL);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_SOFTMIXER:\n\t\t\t\tdebug (\"Toggle softmixer.\");\n\t\t\t\tsend_int_to_srv (CMD_TOGGLE_SOFTMIXER);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_EQUALIZER:\n\t\t\t\tdebug (\"Toggle equalizer.\");\n\t\t\t\tsend_int_to_srv (CMD_TOGGLE_EQUALIZER);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EQUALIZER_REFRESH:\n\t\t\t\tdebug (\"Equalizer Refresh.\");\n\t\t\t\tsend_int_to_srv (CMD_EQUALIZER_REFRESH);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EQUALIZER_PREV:\n\t\t\t\tdebug (\"Equalizer Prev.\");\n\t\t\t\tsend_int_to_srv (CMD_EQUALIZER_PREV);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EQUALIZER_NEXT:\n\t\t\t\tdebug (\"Equalizer Next.\");\n\t\t\t\tsend_int_to_srv (CMD_EQUALIZER_NEXT);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_MAKE_MONO:\n\t\t\t\tdebug (\"Toggle Mono-Mixing.\");\n\t\t\t\tsend_int_to_srv (CMD_TOGGLE_MAKE_MONO);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_LAYOUT:\n\t\t\t\tiface_toggle_layout ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_TOGGLE_PERCENT:\n\t\t\t\tiface_toggle_percent ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PLIST_MOVE_UP:\n\t\t\t\tmove_item (1);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_PLIST_MOVE_DOWN:\n\t\t\t\tmove_item (-1);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_ADD_STREAM:\n\t\t\t\tiface_make_entry (ENTRY_ADD_URL);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_THEME_MENU:\n\t\t\t\tmake_theme_menu ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC1:\n\t\t\t\texec_custom_command (\"ExecCommand1\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC2:\n\t\t\t\texec_custom_command (\"ExecCommand2\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC3:\n\t\t\t\texec_custom_command (\"ExecCommand3\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC4:\n\t\t\t\texec_custom_command (\"ExecCommand4\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC5:\n\t\t\t\texec_custom_command (\"ExecCommand5\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC6:\n\t\t\t\texec_custom_command (\"ExecCommand6\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC7:\n\t\t\t\texec_custom_command (\"ExecCommand7\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC8:\n\t\t\t\texec_custom_command (\"ExecCommand8\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC9:\n\t\t\t\texec_custom_command (\"ExecCommand9\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_EXEC10:\n\t\t\t\texec_custom_command (\"ExecCommand10\");\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_QUEUE_TOGGLE_FILE:\n\t\t\t\tqueue_toggle_file ();\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_QUEUE_CLEAR:\n\t\t\t\tcmd_clear_queue ();\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tabort ();\n\t\t}\n\t}\n}\n\n/* Get event from the server and handle it. */\nstatic void get_and_handle_event ()\n{\n\tint type;\n\n\tif (!get_int_from_srv_noblock(&type)) {\n\t\tdebug (\"Getting event would block.\");\n\t\treturn;\n\t}\n\n\tserver_event (type, get_event_data(type));\n}\n\n/* Handle events from the queue. */\nstatic void dequeue_events ()\n{\n\tstruct event *e;\n\n\tdebug (\"Dequeuing events...\");\n\n\twhile ((e = event_get_first(&events))) {\n\t\tserver_event (e->type, e->data);\n\t\tevent_pop (&events);\n\t}\n\n\tdebug (\"done\");\n}\n\n/* Action after CTRL-C was pressed. */\nstatic void handle_interrupt ()\n{\n\tif (iface_in_entry())\n\t\tiface_entry_disable ();\n}\n\nvoid init_interface (const int sock, const int logging, lists_t_strs *args)\n{\n\tFILE *logfp;\n\n\tlogit (\"Starting MOC Interface\");\n\n\tlogfp = NULL;\n\tif (logging) {\n\t\tlogfp = fopen (INTERFACE_LOG, \"a\");\n\t\tif (!logfp)\n\t\t\tfatal (\"Can't open client log file: %s\", xstrerror (errno));\n\t}\n\tlog_init_stream (logfp, INTERFACE_LOG);\n\n\t/* Set locale according to the environment variables. */\n\tif (!setlocale(LC_CTYPE, \"\"))\n\t\tlogit (\"Could not set locale!\");\n\n\tsrv_sock = sock;\n\n\tfile_info_reset (&curr_file);\n\tfile_info_block_init (&curr_file);\n\tinit_playlists ();\n\tevent_queue_init (&events);\n\tkeys_init ();\n\twindows_init ();\n\tget_server_options ();\n\tupdate_mixer_name ();\n\n\txsignal (SIGQUIT, sig_quit);\n\txsignal (SIGTERM, sig_quit);\n\txsignal (SIGHUP, sig_quit);\n\txsignal (SIGINT, sig_interrupt);\n\n#ifdef SIGWINCH\n\txsignal (SIGWINCH, sig_winch);\n#endif\n\n\tif (!lists_strs_empty (args)) {\n\t\tprocess_args (args);\n\n\t\tif (plist_count(playlist) == 0) {\n\t\t\tif (!options_get_bool(\"SyncPlaylist\") || !use_server_playlist())\n\t\t\t\tload_playlist ();\n\t\t\tsend_int_to_srv (CMD_SEND_PLIST_EVENTS);\n\t\t}\n\t\telse if (options_get_bool(\"SyncPlaylist\")) {\n\t\t\tstruct plist tmp_plist;\n\n\t\t\t/* We have made the playlist from command line. */\n\n\t\t\t/* The playlist should be now clear, but this will give\n\t\t\t * us the serial number of the playlist used by other\n\t\t\t * clients. */\n\t\t\tplist_init (&tmp_plist);\n\t\t\tget_server_playlist (&tmp_plist);\n\n\t\t\tsend_int_to_srv (CMD_SEND_PLIST_EVENTS);\n\n\t\t\tsend_int_to_srv (CMD_LOCK);\n\t\t\tsend_int_to_srv (CMD_CLI_PLIST_CLEAR);\n\n\t\t\tplist_set_serial (playlist,\n\t\t\t\t\tplist_get_serial(&tmp_plist));\n\t\t\tplist_free (&tmp_plist);\n\n\t\t\tchange_srv_plist_serial ();\n\n\t\t\tiface_set_status (\"Notifying clients...\");\n\t\t\tsend_items_to_clients (playlist);\n\t\t\tiface_set_status (\"\");\n\t\t\tplist_clear (playlist);\n\t\t\twaiting_for_plist_load = 1;\n\t\t\tsend_int_to_srv (CMD_UNLOCK);\n\n\t\t\t/* Now enter_first_dir() should not go to the music\n\t\t\t * directory. */\n\t\t\toptions_set_bool (\"StartInMusicDir\", false);\n\t\t}\n\t}\n\telse {\n\t\tsend_int_to_srv (CMD_SEND_PLIST_EVENTS);\n\t\tif (!options_get_bool(\"SyncPlaylist\") || !use_server_playlist())\n\t\t\tload_playlist ();\n\t\tenter_first_dir ();\n\t}\n\n\t/* Ask the server for queue. */\n\tuse_server_queue ();\n\n\tif (options_get_bool(\"SyncPlaylist\"))\n\t\tsend_int_to_srv (CMD_CAN_SEND_PLIST);\n\n\tupdate_state ();\n\n\tif (options_get_bool(\"CanStartInPlaylist\")\n\t\t\t&& curr_file.file\n\t\t\t&& plist_find_fname(playlist, curr_file.file) != -1)\n\t\tiface_switch_to_plist ();\n}\n\nvoid interface_loop ()\n{\n\tlog_circular_start ();\n\n\twhile (want_quit == NO_QUIT) {\n\t\tfd_set fds;\n\t\tint ret;\n\t\tstruct timespec timeout = { 1, 0 };\n\n\t\tFD_ZERO (&fds);\n\t\tFD_SET (srv_sock, &fds);\n\t\tFD_SET (STDIN_FILENO, &fds);\n\n\t\tdequeue_events ();\n\t\tret = pselect (srv_sock + 1, &fds, NULL, NULL, &timeout, NULL);\n\t\tif (ret == -1 && !want_quit && errno != EINTR)\n\t\t\tinterface_fatal (\"pselect() failed: %s\", xstrerror (errno));\n\n\t\tiface_tick ();\n\n\t\tif (ret == 0)\n\t\t\tdo_silent_seek ();\n\n#ifdef SIGWINCH\n\t\tif (want_resize)\n\t\t\tdo_resize ();\n#endif\n\n\t\tif (ret > 0) {\n\t\t\tif (FD_ISSET(STDIN_FILENO, &fds)) {\n\t\t\t\tstruct iface_key k;\n\n\t\t\t\tiface_get_key (&k);\n\n\t\t\t\tclear_interrupt ();\n\t\t\t\tmenu_key (&k);\n\t\t\t}\n\n\t\t\tif (!want_quit) {\n\t\t\t\tif (FD_ISSET(srv_sock, &fds))\n\t\t\t\t\tget_and_handle_event ();\n\t\t\t\tdo_silent_seek ();\n\t\t\t}\n\t\t}\n\t\telse if (user_wants_interrupt())\n\t\t\thandle_interrupt ();\n\n\t\tif (!want_quit)\n\t\t\tupdate_mixer_value ();\n\t}\n\n\tlog_circular_log ();\n\tlog_circular_stop ();\n}\n\n/* Save the current directory path to a file. */\nstatic void save_curr_dir ()\n{\n\tFILE *dir_file;\n\n\tif (!(dir_file = fopen(create_file_name(\"last_directory\"), \"w\"))) {\n\t\terror_errno (\"Can't save current directory\", errno);\n\t\treturn;\n\t}\n\n\tfprintf (dir_file, \"%s\", cwd);\n\tfclose (dir_file);\n}\n\n/* Save the playlist in .moc directory or remove the old playist if the\n * playlist is empty. */\nstatic void save_playlist_in_moc ()\n{\n\tchar *plist_file = create_file_name (PLAYLIST_FILE);\n\n\tif (plist_count(playlist) && options_get_bool(\"SavePlaylist\"))\n\t\tsave_playlist (plist_file, 1);\n\telse\n\t\tunlink (plist_file);\n}\n\nvoid interface_end ()\n{\n\tsave_curr_dir ();\n\tsave_playlist_in_moc ();\n\tif (want_quit == QUIT_SERVER)\n\t\tsend_int_to_srv (CMD_QUIT);\n\telse\n\t\tsend_int_to_srv (CMD_DISCONNECT);\n\tsrv_sock = -1;\n\n\twindows_end ();\n\tkeys_cleanup ();\n\n\tplist_free (dir_plist);\n\tplist_free (playlist);\n\tplist_free (queue);\n\tfree (dir_plist);\n\tfree (playlist);\n\tfree (queue);\n\n\tevent_queue_free (&events);\n\n\tlogit (\"Interface exited\");\n\n\tlog_close ();\n}\n\nvoid interface_fatal (const char *format, ...)\n{\n\tchar *msg;\n\tva_list va;\n\n\tva_start (va, format);\n\tmsg = format_msg_va (format, va);\n\tva_end (va);\n\n\twindows_end ();\n\tfatal (\"%s\", msg);\n}\n\nvoid interface_error (const char *msg)\n{\n\tiface_error (msg);\n}\n\nvoid interface_cmdline_clear_plist (int server_sock)\n{\n\tstruct plist plist;\n\tint serial;\n\tsrv_sock = server_sock; /* the interface is not initialized, so set it\n\t\t\t\t   here */\n\n\tplist_init (&plist);\n\n\tif (options_get_bool(\"SyncPlaylist\"))\n\t\tsend_int_to_srv (CMD_CLI_PLIST_CLEAR);\n\n\tif (recv_server_plist(&plist) && plist_get_serial(&plist)\n\t\t\t== get_server_plist_serial()) {\n\t\tsend_int_to_srv (CMD_LOCK);\n\t\tsend_int_to_srv (CMD_GET_SERIAL);\n\t\tserial = get_data_int ();\n\t\tsend_int_to_srv (CMD_PLIST_SET_SERIAL);\n\t\tsend_int_to_srv (serial);\n\t\tsend_int_to_srv (CMD_LIST_CLEAR);\n\t\tsend_int_to_srv (CMD_UNLOCK);\n\t}\n\n\tunlink (create_file_name (PLAYLIST_FILE));\n\n\tplist_free (&plist);\n}\n\nstatic void add_recursively (struct plist *plist, lists_t_strs *args)\n{\n\tint ix;\n\n\tfor (ix = 0; ix < lists_strs_size (args); ix += 1) {\n\t\tint dir;\n\t\tchar path[PATH_MAX + 1];\n\t\tconst char *arg;\n\n\t\targ = lists_strs_at (args, ix);\n\n\t\tif (arg[0] != '/' && !is_url (arg)) {\n\t\t\tstrncpy (path, cwd, sizeof (path));\n\t\t\tpath[sizeof (path) - 1] = 0;\n\t\t\tresolve_path (path, sizeof (path), arg);\n\t\t}\n\t\telse {\n\t\t\tstrncpy (path, arg, sizeof (path));\n\t\t\tpath[sizeof (path) - 1] = 0;\n\n\t\t\tif (!is_url (arg))\n\t\t\t\tresolve_path (path, sizeof (path), \"\");\n\t\t}\n\n\t\tdir = is_dir (path);\n\n\t\tif (dir == 1)\n\t\t\tread_directory_recurr (path, plist);\n\t\telse if (is_plist_file (arg))\n\t\t\tplist_load (plist, arg, cwd, 0);\n\t\telse if ((is_url (path) || is_sound_file (path))\n\t\t\t\t&& plist_find_fname (plist, path) == -1) {\n\t\t\tint added = plist_add (plist, path);\n\n\t\t\tif (is_url (path))\n\t\t\t\tmake_file_title (plist, added, false);\n\t\t}\n\t}\n}\n\nvoid interface_cmdline_append (int server_sock, lists_t_strs *args)\n{\n\tsrv_sock = server_sock; /* the interface is not initialized, so set it\n\t\t\t\t   here */\n\n\tif (options_get_bool(\"SyncPlaylist\")) {\n\t\tstruct plist clients_plist;\n\t\tstruct plist new;\n\n\t\tplist_init (&clients_plist);\n\t\tplist_init (&new);\n\n\t\tif (!getcwd(cwd, sizeof(cwd)))\n\t\t\tfatal (\"Can't get CWD: %s\", xstrerror (errno));\n\n\t\tif (recv_server_plist(&clients_plist)) {\n\t\t\tadd_recursively (&new, args);\n\t\t\tplist_sort_fname (&new);\n\n\t\t\tsend_int_to_srv (CMD_LOCK);\n\n\t\t\tplist_remove_common_items (&new, &clients_plist);\n\t\t\tsend_items_to_clients (&new);\n\n\t\t\tif (get_server_plist_serial()\n\t\t\t\t\t== plist_get_serial(&clients_plist))\n\t\t\t\tsend_playlist (&new, 0);\n\t\t\tsend_int_to_srv (CMD_UNLOCK);\n\t\t}\n\t\telse {\n\t\t\tstruct plist saved_plist;\n\n\t\t\tplist_init (&saved_plist);\n\n\t\t\t/* this checks if the file exists */\n\t\t\tif (file_type (create_file_name (PLAYLIST_FILE))\n\t\t\t\t\t\t== F_PLAYLIST)\n\t\t\t\t\tplist_load (&saved_plist,\n\t\t\t\t\t\tcreate_file_name (PLAYLIST_FILE),\n\t\t\t\t\t\tcwd, 1);\n\t\t\tadd_recursively (&new, args);\n\t\t\tplist_sort_fname (&new);\n\n\t\t\tsend_int_to_srv (CMD_LOCK);\n\t\t\tplist_remove_common_items (&new, &saved_plist);\n\t\t\tif (plist_get_serial(&saved_plist))\n\t\t\t\tplist_set_serial(&saved_plist,\n\t\t\t\t\t\tget_safe_serial());\n\t\t\tplist_set_serial(&new, plist_get_serial(&saved_plist));\n\t\t\tsend_playlist (&new, 0);\n\t\t\tsend_int_to_srv (CMD_UNLOCK);\n\n\t\t\tplist_cat (&saved_plist, &new);\n\t\t\tif (options_get_bool(\"SavePlaylist\")) {\n\t\t\t\tfill_tags (&saved_plist, TAGS_COMMENTS\n\t\t\t\t\t\t| TAGS_TIME, 1);\n\t\t\t\tplist_save (&saved_plist, create_file_name (PLAYLIST_FILE), 1);\n\t\t\t}\n\n\t\t\tplist_free (&saved_plist);\n\t\t}\n\n\t\tplist_free (&clients_plist);\n\t\tplist_free (&new);\n\t}\n}\n\nvoid interface_cmdline_play_first (int server_sock)\n{\n\tstruct plist plist;\n\n\tsrv_sock = server_sock; /* the interface is not initialized, so set it\n\t\t\t\t   here */\n\n\tif (!getcwd(cwd, sizeof(cwd)))\n\t\tfatal (\"Can't get CWD: %s\", xstrerror (errno));\n\tplist_init (&plist);\n\n\tsend_int_to_srv (CMD_GET_SERIAL);\n\tplist_set_serial (&plist, get_data_int());\n\n\t/* the second condition will checks if the file exists */\n\tif (!recv_server_plist(&plist)\n\t\t\t&& file_type (create_file_name (PLAYLIST_FILE))\n\t\t\t== F_PLAYLIST)\n\t\tplist_load (&plist, create_file_name (PLAYLIST_FILE), cwd, 1);\n\n\tsend_int_to_srv (CMD_LOCK);\n\tif (get_server_plist_serial() != plist_get_serial(&plist)) {\n\t\tsend_playlist (&plist, 1);\n\t\tsend_int_to_srv (CMD_PLIST_SET_SERIAL);\n\t\tsend_int_to_srv (plist_get_serial(&plist));\n\t}\n\n\tsend_int_to_srv (CMD_PLAY);\n\tsend_str_to_srv (\"\");\n\n\tplist_free (&plist);\n}\n\n/* Request tags from the server, wait until they arrive and return them\n * (malloc()ed). This function assumes that the interface is not initialized. */\nstatic struct file_tags *get_tags_no_iface (const char *file,\n\t\tconst int tags_sel)\n{\n\tstruct file_tags *tags = NULL;\n\n\tassert (file_type(file) == F_SOUND);\n\n\tsend_tags_request (file, tags_sel);\n\n\twhile (!tags) {\n\t\tint type = get_int_from_srv ();\n\t\tvoid *data = get_event_data (type);\n\n\t\tif (type == EV_FILE_TAGS) {\n\t\t\tstruct tag_ev_response *ev\n\t\t\t\t= (struct tag_ev_response *)data;\n\n\t\t\tif (!strcmp(ev->file, file))\n\t\t\t\ttags = tags_dup (ev->tags);\n\n\t\t\tfree_tag_ev_data (ev);\n\t\t}\n\t\telse {\n\t\t\t/* We can't handle other events, since this function\n\t\t\t * is to be invoked without the interface. */\n\t\t\tlogit (\"Server sent an event which I didn't expect!\");\n\t\t\tabort ();\n\t\t}\n\t}\n\n\treturn tags;\n}\n\nvoid interface_cmdline_file_info (const int server_sock)\n{\n\tsrv_sock = server_sock;\t/* the interface is not initialized, so set it\n\t\t\t\t   here */\n\tinit_playlists ();\n\tfile_info_reset (&curr_file);\n\tfile_info_block_init (&curr_file);\n\n\tcurr_file.state = get_state ();\n\n\tif (curr_file.state == STATE_STOP)\n\t\tputs (\"State: STOP\");\n\telse {\n\t\tint left;\n\t\tchar curr_time_str[32];\n\t\tchar time_left_str[32];\n\t\tchar time_str[32];\n\t\tchar *title;\n\n\t\tif (curr_file.state == STATE_PLAY)\n\t\t\tputs (\"State: PLAY\");\n\t\telse if (curr_file.state == STATE_PAUSE)\n\t\t\tputs (\"State: PAUSE\");\n\n\t\tcurr_file.file = get_curr_file ();\n\n\t\tif (curr_file.file[0]) {\n\n\t\t\t/* get tags */\n\t\t\tif (file_type(curr_file.file) == F_URL) {\n\t\t\t\tsend_int_to_srv (CMD_GET_TAGS);\n\t\t\t\tcurr_file.tags = get_data_tags ();\n\t\t\t}\n\t\t\telse\n\t\t\t\tcurr_file.tags = get_tags_no_iface (\n\t\t\t\t\t\tcurr_file.file,\n\t\t\t\t\t\tTAGS_COMMENTS | TAGS_TIME);\n\n\t\t\t/* get the title */\n\t\t\tif (curr_file.tags->title)\n\t\t\t\ttitle = build_title (curr_file.tags);\n\t\t\telse\n\t\t\t\ttitle = xstrdup (\"\");\n\t\t}\n\t\telse\n\t\t\ttitle = xstrdup (\"\");\n\n\t\tcurr_file.channels = get_channels ();\n\t\tcurr_file.rate = get_rate ();\n\t\tcurr_file.bitrate = get_bitrate ();\n\t\tcurr_file.curr_time = get_curr_time ();\n\t\tcurr_file.avg_bitrate = get_avg_bitrate ();\n\n\t\tif (curr_file.tags->time != -1)\n\t\t\tsec_to_min (time_str, curr_file.tags->time);\n\t\telse\n\t\t\ttime_str[0] = 0;\n\n\t\tif (curr_file.curr_time != -1) {\n\t\t\tsec_to_min (curr_time_str, curr_file.curr_time);\n\n\t\t\tif (curr_file.tags->time != -1) {\n\t\t\t\tsec_to_min (curr_time_str, curr_file.curr_time);\n\t\t\t\tleft = curr_file.tags->time -\n\t\t\t\t\tcurr_file.curr_time;\n\t\t\t\tsec_to_min (time_left_str, MAX(left, 0));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tstrcpy (curr_time_str, \"00:00\");\n\t\t\ttime_left_str[0] = 0;\n\t\t}\n\n\t\tprintf (\"File: %s\\n\", curr_file.file);\n\t\tprintf (\"Title: %s\\n\", title);\n\n\t\tif (curr_file.tags) {\n\t\t\tprintf (\"Artist: %s\\n\",\n\t\t\t\t\tcurr_file.tags->artist\n\t\t\t\t\t? curr_file.tags->artist : \"\");\n\t\t\tprintf (\"SongTitle: %s\\n\",\n\t\t\t\t\tcurr_file.tags->title\n\t\t\t\t\t? curr_file.tags->title : \"\");\n\t\t\tprintf (\"Album: %s\\n\",\n\t\t\t\t\tcurr_file.tags->album\n\t\t\t\t\t? curr_file.tags->album : \"\");\n\t\t}\n\n\t\tif (curr_file.tags->time != -1) {\n\t\t\tprintf (\"TotalTime: %s\\n\", time_str);\n\t\t\tprintf (\"TimeLeft: %s\\n\", time_left_str);\n\t\t\tprintf (\"TotalSec: %d\\n\", curr_file.tags->time);\n\t\t}\n\n\t\tprintf (\"CurrentTime: %s\\n\", curr_time_str);\n\t\tprintf (\"CurrentSec: %d\\n\", curr_file.curr_time);\n\n\t\tprintf (\"Bitrate: %dkbps\\n\", MAX(curr_file.bitrate, 0));\n\t\tprintf (\"AvgBitrate: %dkbps\\n\", MAX(curr_file.avg_bitrate, 0));\n\t\tprintf (\"Rate: %dkHz\\n\", curr_file.rate);\n\n\t\tfile_info_cleanup (&curr_file);\n\t\tfree (title);\n\t}\n\n\tplist_free (dir_plist);\n\tplist_free (playlist);\n\tplist_free (queue);\n}\n\nvoid interface_cmdline_enqueue (int server_sock, lists_t_strs *args)\n{\n\tint ix;\n\n\t/* the interface is not initialized, so set it here */\n\tsrv_sock = server_sock;\n\n\tif (!getcwd (cwd, sizeof (cwd)))\n\t\tfatal (\"Can't get CWD: %s\", xstrerror (errno));\n\n\tfor (ix = 0; ix < lists_strs_size (args); ix += 1) {\n\t\tconst char *arg;\n\n\t\targ = lists_strs_at (args, ix);\n\t\tif (is_sound_file (arg) || is_url (arg)) {\n\t\t\tchar *path = absolute_path (arg, cwd);\n\t\t\tsend_int_to_srv (CMD_QUEUE_ADD);\n\t\t\tsend_str_to_srv (path);\n\t\t\tfree (path);\n\t\t}\n\t}\n}\n\nvoid interface_cmdline_playit (int server_sock, lists_t_strs *args)\n{\n\tint ix, serial;\n\tstruct plist plist;\n\n\tsrv_sock = server_sock; /* the interface is not initialized, so set it\n\t\t\t\t   here */\n\n\tif (!getcwd(cwd, sizeof(cwd)))\n\t\tfatal (\"Can't get CWD: %s\", xstrerror (errno));\n\n\tplist_init (&plist);\n\n\tfor (ix = 0; ix < lists_strs_size (args); ix += 1) {\n\t\tconst char *arg;\n\n\t\targ = lists_strs_at (args, ix);\n\t\tif (is_url(arg) || is_sound_file(arg)) {\n\t\t\tchar *path = absolute_path (arg, cwd);\n\t\t\tplist_add (&plist, path);\n\t\t\tfree (path);\n\t\t}\n\t}\n\n\tif (plist_count (&plist) == 0)\n\t\tfatal (\"No files added - no sound files on command line!\");\n\n\tsend_int_to_srv (CMD_LOCK);\n\n\tsend_playlist (&plist, 1);\n\n\tsend_int_to_srv (CMD_GET_SERIAL);\n\tserial = get_data_int ();\n\tsend_int_to_srv (CMD_PLIST_SET_SERIAL);\n\tsend_int_to_srv (serial);\n\n\tsend_int_to_srv (CMD_UNLOCK);\n\n\tsend_int_to_srv (CMD_PLAY);\n\tsend_str_to_srv (\"\");\n\n\tplist_free (&plist);\n}\n\nvoid interface_cmdline_seek_by (int server_sock, const int seek_by)\n{\n\tsrv_sock = server_sock; /* the interface is not initialized, so set it\n\t\t\t\t   here */\n\tseek (seek_by);\n}\n\nvoid interface_cmdline_jump_to (int server_sock, const int pos)\n{\n\tsrv_sock = server_sock; /* the interface is not initialized, so set it here */\n\tjump_to (pos);\n}\n\nvoid interface_cmdline_jump_to_percent (int server_sock, const int percent)\n{\n\tsrv_sock = server_sock; /* the interface is not initialized, so set it here */\n\tcurr_file.file = get_curr_file ();\n\tint new_pos;\n\n\tif (percent >= 100) {\n\t\tfprintf (stderr, \"Can't jump beyond the end of file.\\n\");\n\t\treturn;\n\t}\n\n\tif (!curr_file.file[0]) {\n\t\tfprintf (stderr, \"Nothing is played.\\n\");\n\t\treturn;\n\t}\n\n\tif (file_type(curr_file.file) == F_URL) {\n\t\tfprintf (stderr, \"Can't seek in network stream.\\n\");\n\t\treturn;\n\t}\n\n\tcurr_file.tags = get_tags_no_iface (curr_file.file,TAGS_TIME);\n\tnew_pos = (percent*curr_file.tags->time)/100;\n\tprintf(\"Jumping to: %ds. Total time is: %ds\\n\", new_pos, curr_file.tags->time);\n\tjump_to (new_pos);\n}\n\nvoid interface_cmdline_adj_volume (int server_sock, const char *arg)\n{\n\tsrv_sock = server_sock;\n\n\tif (arg[0] == '+')\n\t\tadjust_mixer (atoi (arg + 1));\n\telse if (arg[0] == '-')\n\t\tadjust_mixer (atoi (arg)); /* atoi can handle '-' */\n\telse if (arg[0] != 0)\n\t\tset_mixer (atoi (arg));\n}\n\nvoid interface_cmdline_set (int server_sock, char *arg, const int val)\n{\n\tsrv_sock = server_sock;\n\tchar *last = NULL;\n\tchar *tok;\n\n\ttok = strtok_r (arg, \",\", &last);\n\n\twhile (tok) {\n\n\t\tif (!strcasecmp (tok, \"Shuffle\") || !strcasecmp (tok, \"s\"))\n\t\t\ttok = \"Shuffle\";\n\t\telse if (!strcasecmp (tok, \"AutoNext\") || !strcasecmp (tok, \"n\"))\n\t\t\ttok = \"AutoNext\";\n\t\telse if (!strcasecmp (tok, \"Repeat\") || !strcasecmp (tok, \"r\"))\n\t\t\ttok = \"Repeat\";\n\t\telse {\n\t\t\tfprintf (stderr, \"Unknown option '%s'\\n\", tok);\n\t\t\tbreak;\n\t\t}\n\n\t\tif (val == 2) {\n\t\t\tsend_int_to_srv (CMD_GET_OPTION);\n\t\t\tsend_str_to_srv (tok);\n\t\t\toptions_set_bool (tok, get_data_bool());\n\t\t}\n\n\t\tsend_int_to_srv (CMD_SET_OPTION);\n\t\tsend_str_to_srv (tok);\n\n\t\tif (val == 2)\n\t\t\tsend_bool_to_srv (!options_get_bool(tok));\n\t\telse\n\t\t\tsend_bool_to_srv (val);\n\n\t\ttok = strtok_r (NULL, \",\", &last);\n\t}\n}\n\n/* Print formatted info\nState       %state\nFile        %file\nTitle       %title\nArtist      %artist\nSongTitle   %song\nAlbum       %album\nTotalTime   %tt\nTimeLeft    %tl\nTotalSec    %ts\nCurrentTime %ct\nCurrentSec  %cs\nBitrate     %b\nRate        %r\n*/\nvoid interface_cmdline_formatted_info (const int server_sock,\n\t\tconst char *format_str)\n{\n\ttypedef struct {\n\t\tchar *state;\n\t\tchar *file;\n\t\tchar *title;\n\t\tchar *artist;\n\t\tchar *song;\n\t\tchar *album;\n\t\tchar *totaltime;\n\t\tchar *timeleft;\n\t\tchar *totalsec;\n\t\tchar *currenttime;\n\t\tchar *currentsec;\n\t\tchar *bitrate;\n\t\tchar *rate;\n\t} info_t;\n\n\tchar curr_time_str[32];\n\tchar time_left_str[32];\n\tchar time_str[32];\n\tchar time_sec_str[16];\n\tchar curr_time_sec_str[16];\n\tchar file_bitrate_str[16];\n\tchar file_rate_str[16];\n\n\tchar *fmt, *str;\n\tinfo_t str_info;\n\n\tsrv_sock = server_sock;\t/* the interface is not initialized, so set it\n\t\t\t\t   here */\n\tinit_playlists ();\n\tfile_info_reset (&curr_file);\n\tfile_info_block_init (&curr_file);\n\n\tcurr_file.state = get_state ();\n\n\t/* extra paranoid about struct data */\n\tmemset(&str_info, 0, sizeof(str_info));\n\tcurr_time_str[0] = time_left_str[0] = time_str[0] =\n\t  time_sec_str[0] = curr_time_sec_str[0] =\n\t  file_bitrate_str[0] = file_rate_str[0] = '\\0';\n\n\tstr_info.currenttime = curr_time_str;\n\tstr_info.timeleft = time_left_str;\n\tstr_info.totaltime = time_str;\n\tstr_info.totalsec = time_sec_str;\n\tstr_info.currentsec = curr_time_sec_str;\n\tstr_info.bitrate = file_bitrate_str;\n\tstr_info.rate = file_rate_str;\n\n\tif (curr_file.state == STATE_STOP)\n\t\tstr_info.state = \"STOP\";\n\telse {\n\t\tint left;\n\n\t\tif (curr_file.state == STATE_PLAY)\n\t\t\tstr_info.state = \"PLAY\";\n\t\telse if (curr_file.state == STATE_PAUSE)\n\t\t\tstr_info.state = \"PAUSE\";\n\n\t\tcurr_file.file = get_curr_file ();\n\n\t\tif (curr_file.file[0]) {\n\n\t\t\t/* get tags */\n\t\t\tif (file_type(curr_file.file) == F_URL) {\n\t\t\t\tsend_int_to_srv (CMD_GET_TAGS);\n\t\t\t\tcurr_file.tags = get_data_tags ();\n\t\t\t}\n\t\t\telse\n\t\t\t\tcurr_file.tags = get_tags_no_iface (\n\t\t\t\t\t\tcurr_file.file,\n\t\t\t\t\t\tTAGS_COMMENTS | TAGS_TIME);\n\n\t\t\t/* get the title */\n\t\t\tif (curr_file.tags->title)\n\t\t\t\tstr_info.title = build_title (curr_file.tags);\n\t\t\telse\n\t\t\t\tstr_info.title = xstrdup (\"\");\n\t\t}\n\t\telse\n\t\t\tstr_info.title = xstrdup (\"\");\n\n\t\tcurr_file.channels = get_channels ();\n\t\tcurr_file.rate = get_rate ();\n\t\tcurr_file.bitrate = get_bitrate ();\n\t\tcurr_file.curr_time = get_curr_time ();\n\n\t\tif (curr_file.tags->time != -1)\n\t\t\tsec_to_min (time_str, curr_file.tags->time);\n\t\telse\n\t\t\ttime_str[0] = 0;\n\n\t\tif (curr_file.curr_time != -1) {\n\t\t\tsec_to_min (curr_time_str, curr_file.curr_time);\n\n\t\t\tif (curr_file.tags->time != -1) {\n\t\t\t\tsec_to_min (curr_time_str, curr_file.curr_time);\n\t\t\t\tleft = curr_file.tags->time -\n\t\t\t\t\tcurr_file.curr_time;\n\t\t\t\tsec_to_min (time_left_str, MAX(left, 0));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tstrcpy (curr_time_str, \"00:00\");\n\t\t\ttime_left_str[0] = 0;\n\t\t}\n\n\t\tstr_info.file = curr_file.file;\n\n\t\tstr_info.artist =\n\t\t\t\t\tcurr_file.tags->artist ? curr_file.tags->artist : NULL;\n\t\tstr_info.song = curr_file.tags->title ? curr_file.tags->title : NULL;\n\t\tstr_info.album = curr_file.tags->album ? curr_file.tags->album : NULL;\n\n\t\tif (curr_file.tags->time != -1)\n\t\t\tsnprintf(time_sec_str, sizeof(file_rate_str),\n\t\t\t         \"%d\", curr_file.tags->time);\n\n\t\tsnprintf(curr_time_sec_str, sizeof(file_rate_str),\n\t\t         \"%d\", curr_file.curr_time);\n\t\tsnprintf(file_bitrate_str, sizeof(file_rate_str),\n\t\t         \"%d\", MAX(curr_file.bitrate, 0));\n\t\tsnprintf(file_rate_str, sizeof(file_rate_str), \"%d\", curr_file.rate);\n\t}\n\n\t/* string with formatting tags */\n\tfmt = xstrdup(format_str);\n\n\tfmt = str_repl(fmt, \"%state\", str_info.state);\n\tfmt = str_repl(fmt, \"%file\",\n\t\t\tstr_info.file ? str_info.file : \"\");\n\tfmt = str_repl(fmt, \"%title\",\n\t\t\tstr_info.title ? str_info.title : \"\");\n\tfmt = str_repl(fmt, \"%artist\",\n\t\t\tstr_info.artist ? str_info.artist : \"\");\n\tfmt = str_repl(fmt, \"%song\",\n\t\t\tstr_info.song ? str_info.song : \"\");\n\tfmt = str_repl(fmt, \"%album\",\n\t\t\tstr_info.album ? str_info.album : \"\");\n\tfmt = str_repl(fmt, \"%tt\",\n\t\t\tstr_info.totaltime ? str_info.totaltime : \"\");\n\tfmt = str_repl(fmt, \"%tl\",\n\t\t\tstr_info.timeleft ? str_info.timeleft : \"\");\n\tfmt = str_repl(fmt, \"%ts\",\n\t\t\tstr_info.totalsec ? str_info.totalsec : \"\");\n\tfmt = str_repl(fmt, \"%ct\",\n\t\t\tstr_info.currenttime ? str_info.currenttime : \"\");\n\tfmt = str_repl(fmt, \"%cs\",\n\t\t\tstr_info.currentsec ? str_info.currentsec : \"\");\n\tfmt = str_repl(fmt, \"%b\",\n\t\t\tstr_info.bitrate ? str_info.bitrate : \"\");\n\tfmt = str_repl(fmt, \"%r\",\n\t\t\tstr_info.rate ? str_info.rate : \"\");\n\tfmt = str_repl(fmt, \"\\\\n\", \"\\n\");\n\n\tstr = build_title_with_format (curr_file.tags, fmt);\n\n\tprintf(\"%s\\n\", str);\n\tfree(str);\n\tfree(fmt);\n\n\tif (str_info.title)\n\t\tfree(str_info.title);\n\n\tif (curr_file.state != STATE_STOP)\n\t\tfile_info_cleanup (&curr_file);\n\n\tplist_free (dir_plist);\n\tplist_free (playlist);\n\tplist_free (queue);\n}\n"
  },
  {
    "path": "interface.h",
    "content": "\n#ifndef INTERFACE_H\n#define INTERFACE_H\n\n#include \"lists.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* The desired state of the client (and server). */\nenum want_quit {\n\tNO_QUIT,\t/* don't want to quit */\n\tQUIT_CLIENT,\t/* only quit the client */\n\tQUIT_SERVER\t/* quit the client and the server */\n};\n\n/* Information about the currently played file. */\nstruct file_tags;\nstruct file_info {\n\tchar *file;\n\tstruct file_tags *tags;\n\tchar *title;\n\tint avg_bitrate;\n\tint bitrate;\n\tint rate;\n\tint curr_time;\n\tint total_time;\n\tint channels;\n\tint state; /* STATE_* */\n\tchar *block_file;\n\tint block_start;\n\tint block_end;\n};\n\nvoid init_interface (const int sock, const int logging, lists_t_strs *args);\nvoid interface_loop ();\nvoid interface_end ();\nint user_wants_interrupt ();\nvoid interface_error (const char *msg);\nvoid interface_fatal (const char *format, ...) ATTR_PRINTF(1, 2);\nvoid interface_cmdline_clear_plist (int server_sock);\nvoid interface_cmdline_append (int server_sock, lists_t_strs *args);\nvoid interface_cmdline_play_first (int server_sock);\nvoid interface_cmdline_file_info (const int server_sock);\nvoid interface_cmdline_playit (int server_sock, lists_t_strs *args);\nvoid interface_cmdline_seek_by (int server_sock, const int seek_by);\nvoid interface_cmdline_jump_to_percent (int server_sock, const int percent);\nvoid interface_cmdline_jump_to (int server_sock, const int pos);\nvoid interface_cmdline_adj_volume (int server_sock, const char *arg);\nvoid interface_cmdline_set (int server_sock, char *arg, const int val);\nvoid interface_cmdline_formatted_info (const int server_sock, const char *format_str);\nvoid interface_cmdline_enqueue (int server_sock, lists_t_strs *args);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "interface_elements.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 - 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * Other authors:\n *  - Kamil Tarkowski <kamilt@interia.pl> - sec_to_min_plist()\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdlib.h>\n#include <string.h>\n#include <strings.h>\n#include <stdio.h>\n#include <assert.h>\n#include <errno.h>\n#include <math.h>\n#include <time.h>\n#include <unistd.h>\n#include <ctype.h>\n#include <wctype.h>\n#include <wchar.h>\n\n#include \"common.h\"\n#include \"menu.h\"\n#include \"themes.h\"\n#include \"lists.h\"\n#include \"options.h\"\n#include \"interface_elements.h\"\n#include \"log.h\"\n#include \"files.h\"\n#include \"decoder.h\"\n#include \"keys.h\"\n#include \"playlist.h\"\n#include \"protocol.h\"\n#include \"interface.h\"\n#include \"utf8.h\"\n#include \"rcc.h\"\n#include \"lyrics.h\"\n\n#ifndef PACKAGE_REVISION\n#define STARTUP_MESSAGE \"Welcome to \" PACKAGE_NAME \\\n                        \" (version \" PACKAGE_VERSION \")!\"\n#else\n#define STARTUP_MESSAGE \"Welcome to \" PACKAGE_NAME \\\n                        \" (version \" PACKAGE_VERSION \\\n                        \", revision \" PACKAGE_REVISION \")!\"\n#endif\n#define HISTORY_SIZE\t50\n\n\n/* TODO: removing/adding a char to the entry may increase width of the text\n * by more than one column. */\n\n/* Type of the side menu. */\nenum side_menu_type\n{\n\tMENU_DIR,\t/* list of files in a directory */\n\tMENU_PLAYLIST,\t/* a playlist of files */\n\tMENU_THEMES,\t/* list of available themes */\n\tMENU_TREE\t/* tree of directories */\n};\n\nstruct side_menu\n{\n\tenum side_menu_type type;\n\tint visible;\t/* is it visible (are the other fields initialized) ? */\n\tWINDOW *win; \t/* window for the menu */\n\tchar *title;\t/* title of the window */\n\n\t/* Position and size of the menu in the window. */\n\tint posx;\n\tint posy;\n\tint width;\n\tint height;\n\n\tint total_time; /* total time of the files on the playlist */\n\tint total_time_for_all; /* is the total file counted for all files? */\n\n\tunion\n\t{\n\t\tstruct {\n\t\t\tstruct menu *main;    /* visible menu */\n\t\t\tstruct menu *copy;    /* copy of the menu when we display\n\t\t\t                         matching items while searching */\n\t\t} list;\n\t\t/* struct menu_tree *tree;*/\n\t} menu;\n};\n\n/* State of the side menu that can be read/restored.  It remembers the state\n * (position of the view, which file is selected, etc) of the menu. */\nstruct side_menu_state\n{\n\tstruct menu_state menu_state;\n};\n\n/* When used instead of the size parameter it means: fill to the end of the\n * window. */\n#define LAYOUT_SIZE_FILL\t(-1)\n\nstruct window_params\n{\n\tint x, y;\n\tint width, height;\n};\n\nstruct main_win_layout\n{\n\tstruct window_params menus[3];\n};\n\nstatic struct main_win\n{\n\tWINDOW *win;\n\tchar *curr_file; /* currently played file. */\n\n\tint in_help; /* are we displaying help screen? */\n\tint too_small; /* is the terminal window too small to display mocp? */\n\tint help_screen_top; /* first visible line of the help screen. */\n\tint in_lyrics; /* are we displaying lyrics screen? */\n\tint lyrics_screen_top; /* first visible line of the lyrics screen. */\n\n\tstruct side_menu menus[3];\n\tlists_t_strs *layout_fmt;\n\tint selected_menu; /* which menu is currently selected by the user */\n} main_win;\n\n/* Bar for displaying mixer state or progress. */\nstruct bar\n{\n\tint width;\t/* width in chars */\n\tfloat filled;\t/* how much is it filled in percent */\n\tchar *orig_title;\t/* optional title */\n\tchar title[512];\t/* title with the percent value */\n\tint show_val;\t/* show the title and the value? */\n\tint show_pct;\t/* show percentage in the title value? */\n\tint fill_color;\t/* color (ncurses attributes) of the filled part */\n\tint empty_color;\t/* color of the empty part */\n};\n\n/* History for entries' values. */\nstruct entry_history\n{\n\tchar *items[HISTORY_SIZE];\n\tint num;\t/* number of items */\n};\n\n/* An input area where a user can type text to enter a file name etc. */\nstruct entry\n{\n\tenum entry_type type;\n\tint width;\t\t/* width of the entry part for typing */\n\n\t/* The text the user types: */\n\twchar_t text_ucs[512];\t/* unicode */\n\twchar_t saved_ucs[512];\t/* unicode saved during history scrolling */\n\n\tchar *title;\t\t/* displayed title */\n\tchar *file;\t\t/* optional: file associated with the entry */\n\tint cur_pos;\t\t/* cursor position */\n\tint display_from;\t/* displaying from this char */\n\tstruct entry_history *history;\t/* history to use with this entry or\n\t\t\t\t\t   NULL is history is not used */\n\tint history_pos;\t/* current position in the history */\n};\n\n/* Type of message. */\nenum message_type\n{\n\tNORMAL_MSG,\n\tERROR_MSG,\n\tQUERY_MSG\n};\n\n/* Save a new message for display. */\nstruct queued_message\n{\n\tstruct queued_message *next;\n\t/* What type is this message? */\n\tenum message_type type;\n\t/* Message to be displayed instead of the file's title. */\n\tchar *msg;\n\t/* Prompt to use for user query menu. */\n\tchar *prompt;\n\t/* How many seconds does the message linger? */\n\ttime_t timeout;\n\t/* The callback function and opaque data for user replies. */\n\tt_user_reply_callback *callback;\n\tvoid *data;\n};\n\nstatic struct info_win\n{\n\tWINDOW *win;\n\n\tstruct queued_message *current_message;\t/* Message currently being displayed */\n\n\tstruct queued_message *queued_message_head;\t/* FIFO queue on which pending */\n\tstruct queued_message *queued_message_tail;\t/*          messages get saved */\n\tint queued_message_total;\t\t\t\t\t/* Total messages on queue */\n\tint queued_message_errors;\t\t\t\t\t/* Error messages on queue */\n\n\tint too_small; /* is the current window too small to display this widget? */\n\n\tstruct entry entry;\n\tint in_entry;\t\t/* are we using the entry (is the above\n\t\t\t\t   structure initialized)?  */\n\tstruct entry_history urls_history;\n\tstruct entry_history dirs_history;\n\tstruct entry_history user_history;\n\n\t/* true/false options values */\n\tbool state_stereo;\n\tbool state_shuffle;\n\tbool state_repeat;\n\tbool state_next;\n\tbool state_net;\n\n\tint bitrate;\t\t/* in kbps */\n\tint rate;\t\t/* in kHz */\n\n\tint files_in_queue;\n\n\t/* time in seconds */\n\tint curr_time;\n\tint total_time;\n\tint block_start;\n\tint block_end;\n\n\tint plist_time;\t\t/* total time of files displayed in the menu */\n\tint plist_time_for_all;\t/* is the above time for all files? */\n\n\tchar *title;\t\t/* title of the played song. */\n\tchar status_msg[26];\t/* status message */\n\tint state_play;\t\t/* STATE_(PLAY | STOP | PAUSE) */\n\n\t/* Saved user reply callback data. */\n\tt_user_reply_callback *callback;\n\tvoid *data;\n\n\tstruct bar mixer_bar;\n\tstruct bar time_bar;\n} info_win;\n\n/* Are we running on xterm? */\nstatic bool has_xterm = false;\n\n/* Are we running inside screen? */\nstatic bool has_screen = false;\n\n/* Was the interface initialized? */\nstatic int iface_initialized = 0;\n\n/* Was initscr() called? */\nstatic int screen_initialized = 0;\n\n/* Chars used to make lines (for borders etc.). */\nstatic struct\n{\n\tchtype vert;\t/* vertical */\n\tchtype horiz;\t/* horizontal */\n\tchtype ulcorn;\t/* upper left corner */\n\tchtype urcorn;\t/* upper right corner */\n\tchtype llcorn;\t/* lower left corner */\n\tchtype lrcorn;\t/* lower right corner */\n\tchtype rtee;\t/* right tee */\n\tchtype ltee;\t/* left tee */\n} lines;\n\nstatic void entry_history_init (struct entry_history *h)\n{\n\tassert (h != NULL);\n\n\th->num = 0;\n}\n\nstatic void entry_history_add (struct entry_history *h,\tconst char *text)\n{\n\tassert (h != NULL);\n\tassert (text != NULL);\n\n\tif (strlen (text) != strspn (text, \" \")) {\n\t\tif (h->num == 0 || strcmp (text, h->items[h->num - 1])) {\n\t\t\tif (h->num < HISTORY_SIZE)\n\t\t\t\th->items[h->num++] = xstrdup (text);\n\t\t\telse {\n\t\t\t\tfree (h->items[0]);\n\t\t\t\tmemmove (h->items, h->items + 1,\n\t\t\t\t\t\t(HISTORY_SIZE - 1) * sizeof (char *));\n\t\t\t\th->items[h->num - 1] = xstrdup (text);\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void entry_history_replace (struct entry_history *h, int num, const char *text)\n{\n\tassert (h != NULL);\n\tassert (LIMIT(num, h->num));\n\tassert (text != NULL);\n\n\tif (strlen (text) != strspn (text, \" \") &&\n\t    strcmp (h->items[num], text)) {\n\t\tfree (h->items[num]);\n\t\th->items[num] = xstrdup (text);\n\t}\n}\n\nstatic void entry_history_clear (struct entry_history *h)\n{\n\tint i;\n\n\tassert (h != NULL);\n\n\tfor (i = 0; i < h->num; i++)\n\t\tfree (h->items[i]);\n\n\th->num = 0;\n}\n\nstatic int entry_history_nitems (const struct entry_history *h)\n{\n\tassert (h != NULL);\n\n\treturn h->num;\n}\n\nstatic char *entry_history_get (const struct entry_history *h, const int num)\n{\n\tassert (h != NULL);\n\tassert (LIMIT(num, h->num));\n\n\treturn xstrdup (h->items[num]);\n}\n\n/* Draw the entry.  Use this function at the end of screen drawing\n * because it sets the cursor position in the right place. */\nstatic void entry_draw (const struct entry *e, WINDOW *w, const int posx,\n\t\tconst int posy)\n{\n\tchar *text;\n\twchar_t *text_ucs;\n\tint len;\n\n\tassert (e != NULL);\n\tassert (w != NULL);\n\tassert (posx >= 0);\n\tassert (posy >= 0);\n\n\twmove (w, posy, posx);\n\twattrset (w, get_color(CLR_ENTRY_TITLE));\n\txwprintw (w, \"%s\", e->title);\n\n\twattrset (w, get_color(CLR_ENTRY));\n\tlen = wcslen(e->text_ucs) - e->display_from;\n\n\ttext_ucs = (wchar_t *)xmalloc(sizeof(wchar_t) * (len + 1));\n\tmemcpy (text_ucs, e->text_ucs + e->display_from,\n\t\t\tsizeof(wchar_t) * (len + 1));\n\tif (len > e->width)\n\t\ttext_ucs[e->width] = L'\\0';\n\tlen = wcstombs (NULL, text_ucs, -1) + 1;\n\tassert (len >= 1);\n\n\ttext = (char *)xmalloc (len);\n\twcstombs (text, text_ucs, len);\n\n\txwprintw (w, \" %-*s\", e->width, text);\n\n\t/* Move the cursor */\n\twmove (w, posy, e->cur_pos - e->display_from + strwidth(e->title)\n\t\t\t+ posx + 1);\n\n\tfree (text);\n\tfree (text_ucs);\n}\n\nstatic void entry_init (struct entry *e, const enum entry_type type,\n\t\tconst int width, struct entry_history *history, const char *prompt)\n{\n\tconst char *title;\n\n\tassert (e != NULL);\n\n\tswitch (type) {\n\t\tcase ENTRY_SEARCH:\n\t\t\ttitle = \"SEARCH\";\n\t\t\tbreak;\n\t\tcase ENTRY_PLIST_SAVE:\n\t\t\ttitle = \"SAVE PLAYLIST\";\n\t\t\tbreak;\n\t\tcase ENTRY_GO_DIR:\n\t\t\ttitle = \"GO\";\n\t\t\tbreak;\n\t\tcase ENTRY_GO_URL:\n\t\t\ttitle = \"URL\";\n\t\t\tbreak;\n\t\tcase ENTRY_ADD_URL:\n\t\t\ttitle = \"ADD URL\";\n\t\t\tbreak;\n\t\tcase ENTRY_PLIST_OVERWRITE:\n\t\t\ttitle = \"File exists, overwrite?\";\n\t\t\tbreak;\n\t\tcase ENTRY_USER_QUERY:\n\t\t\ttitle = prompt;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tabort ();\n\t}\n\n\te->type = type;\n\te->text_ucs[0] = L'\\0';\n\te->saved_ucs[0] = L'\\0';\n\te->file = NULL;\n\te->title = xmalloc (strlen (title) + 2);\n\tstrcpy (e->title, title);\n\tif (e->title[strlen (e->title) - 1] != ':' &&\n\t    e->title[strlen (e->title) - 1] != '?')\n\t\tstrcat (e->title, \":\");\n\te->width = width - strwidth(title);\n\te->cur_pos = 0;\n\te->display_from = 0;\n\te->history = history;\n\n\tif (history)\n\t\te->history_pos = history->num;\n}\n\nstatic enum entry_type entry_get_type (const struct entry *e)\n{\n\tassert (e != NULL);\n\n\treturn e->type;\n}\n\n/* Set the entry text as UCS.  Move the cursor to the end. */\nstatic void entry_set_text_ucs (struct entry *e, const wchar_t *text)\n{\n\tint width, len;\n\n\tassert (e != NULL);\n\n\tlen = MIN (wcslen (text) + 1, ARRAY_SIZE (e->text_ucs));\n\twmemcpy (e->text_ucs, text, len);\n\te->text_ucs[ARRAY_SIZE (e->text_ucs) - 1] = L'\\0';\n\n\twidth = wcswidth (e->text_ucs, WIDTH_MAX);\n\te->cur_pos = wcslen (e->text_ucs);\n\n\te->display_from = 0;\n\tif (e->cur_pos > e->width)\n\t\te->display_from = width - e->width;\n}\n\n/* Set the entry text. */\nstatic void entry_set_text (struct entry *e, const char *text)\n{\n\twchar_t text_ucs[ARRAY_SIZE (e->text_ucs)];\n\n\tassert (e != NULL);\n\n\tmbstowcs (text_ucs, text, ARRAY_SIZE (e->text_ucs));\n\te->text_ucs[ARRAY_SIZE (e->text_ucs) - 1] = L'\\0';\n\n\tentry_set_text_ucs (e, text_ucs);\n}\n\n/* Add a char to the entry where the cursor is placed. */\nstatic void entry_add_char (struct entry *e, const wchar_t c)\n{\n\tsize_t len;\n\n\tassert (e != NULL);\n\n\tlen = wcslen (e->text_ucs);\n\tif (len >= ARRAY_SIZE(e->text_ucs) - sizeof(wchar_t))\n\t\treturn;\n\n\tmemmove (e->text_ucs + e->cur_pos + 1,\n\t\t\te->text_ucs + e->cur_pos,\n\t\t\t(len - e->cur_pos + 1) * sizeof(e->text_ucs[0]));\n\te->text_ucs[e->cur_pos] = c;\n\te->cur_pos++;\n\n\tif (e->cur_pos - e->display_from > e->width)\n\t\te->display_from++;\n}\n\n/* Delete 'count' chars before the cursor. */\nstatic void entry_del_chars (struct entry *e, int count)\n{\n\tassert (e != NULL);\n\tassert (e->cur_pos > 0);\n\n\tint width = wcslen (e->text_ucs);\n\tif (e->cur_pos < count)\n\t\tcount = e->cur_pos;\n\n\tmemmove (e->text_ucs + e->cur_pos - count,\n\t         e->text_ucs + e->cur_pos,\n\t         (width - e->cur_pos) * sizeof (e->text_ucs[0]));\n\twidth -= count;\n\te->text_ucs[width] = L'\\0';\n\te->cur_pos -= count;\n\n\tif (e->cur_pos < e->display_from)\n\t\te->display_from = e->cur_pos;\n\n\t/* Can we show more after deleting the chars? */\n\tif (e->display_from > 0 && width - e->display_from < e->width)\n\t\te->display_from = width - e->width;\n\tif (e->display_from < 0)\n\t\te->display_from = 0;\n}\n\n/* Delete the char before the cursor. */\nstatic void entry_back_space (struct entry *e)\n{\n\tassert (e != NULL);\n\n\tif (e->cur_pos > 0)\n\t\tentry_del_chars (e, 1);\n}\n\n/* Delete the char under the cursor. */\nstatic void entry_del_char (struct entry *e)\n{\n\tint len;\n\n\tassert (e != NULL);\n\n\tlen = wcslen (e->text_ucs);\n\tif (e->cur_pos < len) {\n\t\te->cur_pos += 1;\n\t\tentry_del_chars (e, 1);\n\t}\n}\n\n/* Delete the chars from cursor to start of line. */\nstatic void entry_del_to_start (struct entry *e)\n{\n\tassert (e != NULL);\n\n\tif (e->cur_pos > 0)\n\t\tentry_del_chars (e, e->cur_pos);\n}\n\n/* Delete the chars from cursor to end of line. */\nstatic void entry_del_to_end (struct entry *e)\n{\n\tint len;\n\n\tassert (e != NULL);\n\n\tlen = wcslen (e->text_ucs);\n\tif (e->cur_pos < len) {\n\t\tint count;\n\n\t\tcount = len - e->cur_pos;\n\t\te->cur_pos = len;\n\t\tentry_del_chars (e, count);\n\t}\n}\n\n/* Move the cursor one char left. */\nstatic void entry_curs_left (struct entry *e)\n{\n\tassert (e != NULL);\n\n\tif (e->cur_pos > 0) {\n\t\te->cur_pos--;\n\n\t\tif (e->cur_pos < e->display_from)\n\t\t\te->display_from--;\n\t}\n}\n\n/* Move the cursor one char right. */\nstatic void entry_curs_right (struct entry *e)\n{\n\tint width;\n\n\tassert (e != NULL);\n\n\twidth = wcslen (e->text_ucs);\n\n\tif (e->cur_pos < width) {\n\t\te->cur_pos++;\n\n\t\tif (e->cur_pos > e->width + e->display_from)\n\t\t\te->display_from++;\n\t}\n}\n\n/* Move the cursor to the end of the entry text. */\nstatic void entry_end (struct entry *e)\n{\n\tint width;\n\n\tassert (e != NULL);\n\n\twidth = wcslen (e->text_ucs);\n\n\te->cur_pos = width;\n\n\tif (width > e->width)\n\t\te->display_from = width - e->width;\n\telse\n\t\te->display_from = 0;\n}\n\n/* Move the cursor to the beginning of the entry field. */\nstatic void entry_home (struct entry *e)\n{\n\tassert (e != NULL);\n\n\te->display_from = 0;\n\te->cur_pos = 0;\n}\n\nstatic void entry_resize (struct entry *e, const int width)\n{\n\tassert (e != NULL);\n\tassert (width > 0);\n\n\te->width = width - strlen (e->title);\n\tentry_end (e);\n}\n\nstatic char *entry_get_text (const struct entry *e)\n{\n\tchar *text;\n\tint len;\n\n\tassert (e != NULL);\n\n\tlen = wcstombs (NULL, e->text_ucs, -1) + 1;\n\tassert (len >= 1);\n\ttext = (char *) xmalloc (sizeof (char) * len);\n\twcstombs (text, e->text_ucs, len);\n\n\treturn text;\n}\n\n/* Copy the previous history item to the entry if available, move the entry\n * history position down. */\nstatic void entry_set_history_up (struct entry *e)\n{\n\tassert (e != NULL);\n\tassert (e->history != NULL);\n\n\tif (e->history_pos > 0) {\n\t\tchar *t;\n\n\t\tif (e->history_pos == entry_history_nitems (e->history))\n\t\t\twmemcpy (e->saved_ucs, e->text_ucs, wcslen (e->text_ucs) + 1);\n\t\telse {\n\t\t\tt = entry_get_text (e);\n\t\t\tentry_history_replace (e->history, e->history_pos, t);\n\t\t\tfree (t);\n\t\t}\n\t\te->history_pos--;\n\n\t\tt = entry_history_get (e->history, e->history_pos);\n\t\tentry_set_text (e, t);\n\t\tfree (t);\n\t}\n}\n\n/* Copy the next history item to the entry if available, move the entry history\n * position down. */\nstatic void entry_set_history_down (struct entry *e)\n{\n\tassert (e != NULL);\n\tassert (e->history != NULL);\n\n\tif (e->history_pos < entry_history_nitems (e->history)) {\n\t\tchar *t;\n\n\t\tt = entry_get_text (e);\n\t\tentry_history_replace (e->history, e->history_pos, t);\n\t\tfree (t);\n\n\t\te->history_pos++;\n\t\tif (e->history_pos == entry_history_nitems (e->history))\n\t\t\tentry_set_text_ucs (e, e->saved_ucs);\n\t\telse {\n\t\t\tt = entry_history_get (e->history, e->history_pos);\n\t\t\tentry_set_text (e, t);\n\t\t\tfree (t);\n\t\t}\n\t}\n}\n\nstatic void entry_set_file (struct entry *e, const char *file)\n{\n\tassert (e != NULL);\n\tassert (file != NULL);\n\n\tif (e->file)\n\t\tfree (e->file);\n\te->file = xstrdup (file);\n}\n\nstatic char *entry_get_file (const struct entry *e)\n{\n\treturn xstrdup (e->file);\n}\n\nstatic void entry_destroy (struct entry *e)\n{\n\tassert (e != NULL);\n\n\tif (e->file)\n\t\tfree (e->file);\n\tif (e->title)\n\t\tfree (e->title);\n}\n\nstatic void entry_add_text_to_history (struct entry *e)\n{\n\tchar *text;\n\n\tassert (e != NULL);\n\tassert (e->history);\n\n\ttext = entry_get_text (e);\n\tentry_history_add (e->history, text);\n\tfree (text);\n}\n\n/* Return the list menu height inside the side menu. */\nstatic int side_menu_get_menu_height (const struct side_menu *m)\n{\n\tif (m->posy + m->height == LINES - 4)\n\t\treturn m->height - 1;\n\treturn m->height - 2;\n}\n\nstatic void side_menu_init_menu (struct side_menu *m)\n{\n\tassert (m != NULL);\n\n\tm->menu.list.main = menu_new (m->win, m->posx + 1, m->posy + 1,\n\t\t\tm->width - 2, side_menu_get_menu_height (m));\n}\n\nstatic void side_menu_init (struct side_menu *m, const enum side_menu_type type,\n\t\tWINDOW *parent_win, const struct window_params *wp)\n{\n\tassert (m != NULL);\n\tassert (parent_win != NULL);\n\tassert (wp != NULL);\n\tassert (wp->width >= 8);\n\tassert (wp->height >= 3);\n\n\tm->type = type;\n\tm->win = parent_win;\n\tm->posx = wp->x;\n\tm->posy = wp->y;\n\tm->height = wp->height;\n\tm->width = wp->width;\n\n\tm->title = NULL;\n\n\tm->total_time = 0;\n\tm->total_time_for_all = 0;\n\n\tif (type == MENU_DIR || type == MENU_PLAYLIST) {\n\t\tside_menu_init_menu (m);\n\t\tm->menu.list.copy = NULL;\n\n\t\tmenu_set_items_numbering (m->menu.list.main,\n\t\t\t\ttype == MENU_PLAYLIST\n\t\t\t\t&& options_get_bool(\"PlaylistNumbering\"));\n\t\tmenu_set_show_format (m->menu.list.main,\n\t\t\t\toptions_get_bool(\"ShowFormat\"));\n\t\tmenu_set_show_time (m->menu.list.main,\n\t\t\t\tstrcasecmp(options_get_symb(\"ShowTime\"), \"no\"));\n\t\tmenu_set_info_attr_normal (m->menu.list.main,\n\t\t\t\tget_color(CLR_MENU_ITEM_INFO));\n\t\tmenu_set_info_attr_sel (m->menu.list.main,\n\t\t\t\tget_color(CLR_MENU_ITEM_INFO_SELECTED));\n\t\tmenu_set_info_attr_marked (m->menu.list.main,\n\t\t\t\tget_color(CLR_MENU_ITEM_INFO_MARKED));\n\t\tmenu_set_info_attr_sel_marked (m->menu.list.main,\n\t\t\t\tget_color(CLR_MENU_ITEM_INFO_MARKED_SELECTED));\n\t}\n\telse if (type == MENU_THEMES) {\n\t\tside_menu_init_menu (m);\n\t\tm->menu.list.copy = NULL;\n\t}\n\telse\n\t\tabort ();\n\n\tm->visible = 1;\n}\n\nstatic void side_menu_destroy (struct side_menu *m)\n{\n\tassert (m != NULL);\n\n\tif (m->visible) {\n\t\tif (m->type == MENU_DIR || m->type == MENU_PLAYLIST\n\t\t\t\t|| m->type == MENU_THEMES) {\n\t\t\tmenu_free (m->menu.list.main);\n\t\t\tif (m->menu.list.copy)\n\t\t\t\tmenu_free (m->menu.list.copy);\n\t\t}\n\t\telse\n\t\t\tabort ();\n\n\t\tif (m->title)\n\t\t\tfree (m->title);\n\t\tm->visible = 0;\n\t}\n}\n\nstatic void side_menu_set_title (struct side_menu *m, const char *title)\n{\n\tassert (m != NULL);\n\tassert (title != NULL);\n\n\tif (m->title)\n\t\tfree (m->title);\n\tm->title = xstrdup (title);\n}\n\n/* Parse one layout coordinate from \"0,2,54%,1\" and put it in val.\n * Max is the maximum value of the field.  It's also used when processing\n * percent values.\n * Return false on error. */\nstatic bool parse_layout_coordinate (const char *fmt, int *val, const int max)\n{\n\tlong v;\n\tconst char *e = fmt;\n\n\tif (!strcasecmp (fmt, \"FILL\")) {\n\t\t*val = LAYOUT_SIZE_FILL;\n\t\treturn true;\n\t}\n\n\tv = strtol (fmt, (char **)&e, 10);\n\tif (e == fmt)\n\t\treturn false;\n\n\tif (*e == '%')\n\t\tv = lroundf (max * v / 100.0 - 0.1);\n\t*val = v;\n\n\tif (!RANGE(0, *val, max)) {\n\t\tlogit (\"Coordinate out of range - %d is not in (0, %d)\", *val, max);\n\t\treturn false;\n\t}\n\n\treturn true;\n}\n\n/* Parse the layout string. Return false on error. */\nstatic bool parse_layout (struct main_win_layout *l, lists_t_strs *fmt)\n{\n\tint ix;\n\tbool result = false;\n\tlists_t_strs *format;\n\n\tassert (l != NULL);\n\tassert (fmt != NULL);\n\n\t/* default values */\n\tl->menus[0].x = 0;\n\tl->menus[0].y = 0;\n\tl->menus[0].width = COLS;\n\tl->menus[0].height = LINES - 4;\n\tl->menus[1] = l->menus[0];\n\tl->menus[2] = l->menus[0];\n\n\tformat = lists_strs_new (6);\n\tfor (ix = 0; ix < lists_strs_size (fmt); ix += 1) {\n\t\tconst char *menu, *name;\n\t\tstruct window_params p;\n\n\t\tlists_strs_clear (format);\n\t\tmenu = lists_strs_at (fmt, ix);\n\t\tif (lists_strs_split (format, menu, \"(,)\") != 5)\n\t\t\tgoto err;\n\n\t\tname = lists_strs_at (format, 0);\n\n\t\tif (!parse_layout_coordinate (lists_strs_at (format, 1), &p.x, COLS)) {\n\t\t\tlogit (\"Coordinate parse error when parsing X\");\n\t\t\tgoto err;\n\t\t}\n\t\tif (!parse_layout_coordinate (lists_strs_at (format, 2), &p.y, LINES - 4)) {\n\t\t\tlogit (\"Coordinate parse error when parsing Y\");\n\t\t\tgoto err;\n\t\t}\n\t\tif (!parse_layout_coordinate (lists_strs_at (format, 3), &p.width, COLS)) {\n\t\t\tlogit (\"Coordinate parse error when parsing width\");\n\t\t\tgoto err;\n\t\t}\n\t\tif (!parse_layout_coordinate (lists_strs_at (format, 4), &p.height, LINES - 4)) {\n\t\t\tlogit (\"Coordinate parse error when parsing height\");\n\t\t\tgoto err;\n\t\t}\n\n\t\tif (p.width == LAYOUT_SIZE_FILL)\n\t\t\tp.width = COLS - p.x;\n\t\tif (p.height == LAYOUT_SIZE_FILL)\n\t\t\tp.height = LINES - 4 - p.y;\n\n\t\tif (p.width < 15) {\n\t\t\tlogit (\"Width is less than 15\");\n\t\t\tgoto err;\n\t\t}\n\t\tif (p.height < 2) {\n\t\t\tlogit (\"Height is less than 2\");\n\t\t\tgoto err;\n\t\t}\n\t\tif (p.x + p.width > COLS) {\n\t\t\tlogit (\"X + width is more than COLS (%d)\", COLS);\n\t\t\tgoto err;\n\t\t}\n\t\tif (p.y + p.height > LINES - 4) {\n\t\t\tlogit (\"Y + height is more than LINES - 4 (%d)\", LINES - 4);\n\t\t\tgoto err;\n\t\t}\n\n\t\tif (!strcmp(name, \"directory\"))\n\t\t\tl->menus[MENU_DIR] = p;\n\t\telse if (!strcmp(name, \"playlist\"))\n\t\t\tl->menus[MENU_PLAYLIST] = p;\n\t\telse {\n\t\t\tlogit (\"Bad subwindow name '%s'\", name);\n\t\t\tgoto err;\n\t\t}\n\t}\n\n\tresult = true;\n\nerr:\n\tlists_strs_free (format);\n\treturn result;\n}\n\nstatic void main_win_init (struct main_win *w, lists_t_strs *layout_fmt)\n{\n\tstruct main_win_layout l;\n\tbool rc ASSERT_ONLY;\n\n\tassert (w != NULL);\n\n\tw->win = newwin (LINES - 4, COLS, 0, 0);\n\twbkgd (w->win, get_color(CLR_BACKGROUND));\n\tnodelay (w->win, TRUE);\n\tkeypad (w->win, TRUE);\n\n\tw->curr_file = NULL;\n\tw->in_help = 0;\n\tw->in_lyrics = 0;\n\tw->too_small = 0;\n\tw->help_screen_top = 0;\n\tw->lyrics_screen_top = 0;\n\tw->layout_fmt = layout_fmt;\n\n\trc = parse_layout (&l, layout_fmt);\n\tassert (rc);\n\n\tside_menu_init (&w->menus[0], MENU_DIR, w->win, &l.menus[0]);\n\tside_menu_init (&w->menus[1], MENU_PLAYLIST, w->win, &l.menus[1]);\n\tside_menu_set_title (&w->menus[1], \"Playlist\");\n\tw->menus[2].visible = 0;\n\n\tw->selected_menu = 0;\n}\n\nstatic void main_win_destroy (struct main_win *w)\n{\n\tassert (w != NULL);\n\n\tside_menu_destroy (&w->menus[0]);\n\tside_menu_destroy (&w->menus[1]);\n\tside_menu_destroy (&w->menus[2]);\n\n\tif (w->win)\n\t\tdelwin (w->win);\n\tif (w->curr_file)\n\t\tfree (w->curr_file);\n}\n\n/* Make a title suitable to display in a menu from the title of a playlist item.\n * Returned memory is malloc()ed.\n * made_from tags - was the playlist title made from tags?\n * full_paths - If the title is the file name, use the full path?\n */\nstatic char *make_menu_title (const char *plist_title,\n\t\tconst int made_from_tags, const int full_path)\n{\n\tchar *title = xstrdup (plist_title);\n\n\tif (!made_from_tags) {\n\t\tif (!full_path && !is_url (title)) {\n\n\t\t\t/* Use only the file name instead of the full path. */\n\t\t\tchar *slash = strrchr (title, '/');\n\n\t\t\tif (slash && slash != title) {\n\t\t\t\tchar *old_title = title;\n\n\t\t\t\ttitle = xstrdup (slash + 1);\n\t\t\t\tfree (old_title);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn title;\n}\n\n/* Add an item from the playlist to the menu.\n * If full_paths has non-zero value, full paths will be displayed instead of\n * just file names.\n * Return a non-zero value if the added item is visible on the screen. */\nstatic int add_to_menu (struct menu *menu, const struct plist *plist,\n\t\tconst int num, const int full_paths)\n{\n\tbool made_from_tags;\n\tstruct menu_item *added;\n\tconst struct plist_item *item = &plist->items[num];\n\tchar *title;\n\tconst char *type_name;\n\n\tmade_from_tags = (options_get_bool (\"ReadTags\") && item->title_tags);\n\n\tif (made_from_tags)\n\t\ttitle = make_menu_title (item->title_tags, 1, 0);\n\telse\n\t\ttitle = make_menu_title (item->title_file, 0, full_paths);\n\tadded = menu_add (menu, title, plist_file_type (plist, num), item->file);\n\tfree (title);\n\n\tif (item->tags && item->tags->time != -1) {\n\t\tchar time_str[32];\n\n\t\tsec_to_min (time_str, item->tags->time);\n\t\tmenu_item_set_time (added, time_str);\n\t}\n\n\tmenu_item_set_attr_normal (added, get_color(CLR_MENU_ITEM_FILE));\n\tmenu_item_set_attr_sel (added, get_color(CLR_MENU_ITEM_FILE_SELECTED));\n\tmenu_item_set_attr_marked (added, get_color(CLR_MENU_ITEM_FILE_MARKED));\n\tmenu_item_set_attr_sel_marked (added,\n\t\t\tget_color(CLR_MENU_ITEM_FILE_MARKED_SELECTED));\n\n\tif (!(type_name = file_type_name(item->file)))\n\t\ttype_name = \"\";\n\tmenu_item_set_format (added, type_name);\n\tmenu_item_set_queue_pos (added, item->queue_pos);\n\n\tif (full_paths && !made_from_tags)\n\t\tmenu_item_set_align (added, MENU_ALIGN_RIGHT);\n\n\treturn menu_is_visible (menu, added);\n}\n\nstatic void side_menu_clear (struct side_menu *m)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\tassert (m->menu.list.main != NULL);\n\tassert (m->menu.list.copy == NULL);\n\n\tmenu_free (m->menu.list.main);\n\tside_menu_init_menu (m);\n\tmenu_set_items_numbering (m->menu.list.main, m->type == MENU_PLAYLIST\n\t\t\t&& options_get_bool(\"PlaylistNumbering\"));\n\n\tmenu_set_show_format (m->menu.list.main, options_get_bool(\"ShowFormat\"));\n\tmenu_set_show_time (m->menu.list.main,\n\t\t\tstrcasecmp(options_get_symb(\"ShowTime\"), \"no\"));\n\tmenu_set_info_attr_normal (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO));\n\tmenu_set_info_attr_sel (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO_SELECTED));\n\tmenu_set_info_attr_marked (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO_MARKED));\n\tmenu_set_info_attr_sel_marked (m->menu.list.main, get_color(CLR_MENU_ITEM_INFO_MARKED_SELECTED));\n}\n\n/* Fill the directory or playlist side menu with this content. */\nstatic void side_menu_make_list_content (struct side_menu *m,\n\t\tconst struct plist *files, const lists_t_strs *dirs,\n\t\tconst lists_t_strs *playlists, const int add_up_dir)\n{\n\tstruct menu_item *added;\n\tint i;\n\n\tassert (m != NULL);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\tassert (m->menu.list.main != NULL);\n\tassert (m->menu.list.copy == NULL);\n\n\tside_menu_clear (m);\n\n\tif (add_up_dir) {\n\t\tadded = menu_add (m->menu.list.main, \"../\", F_DIR, \"..\");\n\t\tmenu_item_set_attr_normal (added, get_color(CLR_MENU_ITEM_DIR));\n\t\tmenu_item_set_attr_sel (added,\n\t\t\t\tget_color(CLR_MENU_ITEM_DIR_SELECTED));\n\t}\n\n\tif (dirs)\n\t\tfor (i = 0; i < lists_strs_size (dirs) ; i++) {\n\t\t\tchar title[PATH_MAX];\n\n#ifdef HAVE_RCC\n\t\t\tchar *t_str = NULL;\n\t\t\tif (options_get_bool(\"UseRCCForFilesystem\")) {\n\t\t\t\tstrcpy (title, strrchr (lists_strs_at (dirs, i), '/') + 1);\n\t\t\t\tstrcat (title, \"/\");\n\t\t\t\tt_str = xstrdup (title);\n\t\t\t\tt_str = rcc_reencode (t_str);\n\t\t\t\tsnprintf(title, PATH_MAX, \"%s\", t_str);\n\t\t\t\tfree(t_str);\n\t\t\t}\n\t\t\telse\n#endif\n\t\t\tif (options_get_bool (\"FileNamesIconv\"))\n\t\t\t{\n\t\t\t\tchar *conv_title = files_iconv_str (\n\t\t\t\t\t\tstrrchr (lists_strs_at (dirs, i), '/') + 1);\n\n\t\t\t\tstrcpy (title, conv_title);\n\t\t\t\tstrcat (title, \"/\");\n\n\t\t\t\tfree (conv_title);\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstrcpy (title, strrchr (lists_strs_at (dirs, i), '/') + 1);\n\t\t\t\tstrcat (title, \"/\");\n\t\t\t}\n\n\t\t\tadded = menu_add (m->menu.list.main, title, F_DIR,\n\t\t\t\t\tlists_strs_at (dirs, i));\n\t\t\tmenu_item_set_attr_normal (added,\n\t\t\t\t\tget_color(CLR_MENU_ITEM_DIR));\n\t\t\tmenu_item_set_attr_sel (added,\n\t\t\t\t\tget_color(CLR_MENU_ITEM_DIR_SELECTED));\n\t\t}\n\n\tif (playlists)\n\t\tfor (i = 0; i < lists_strs_size (playlists); i++){\n\t\t\tadded = menu_add (m->menu.list.main,\n\t\t\t\t\tstrrchr (lists_strs_at (playlists, i), '/') + 1,\n\t\t\t\t\tF_PLAYLIST, lists_strs_at (playlists, i));\n\t\t\tmenu_item_set_attr_normal (added,\n\t\t\t\t\tget_color(CLR_MENU_ITEM_PLAYLIST));\n\t\t\tmenu_item_set_attr_sel (added,\n\t\t\t\t\tget_color(\n\t\t\t\t\tCLR_MENU_ITEM_PLAYLIST_SELECTED));\n\t\t}\n\n\t/* playlist items */\n\tfor (i = 0; i < files->num; i++) {\n\t\tif (!plist_deleted(files, i))\n\t\t\tadd_to_menu (m->menu.list.main, files, i,\n\t\t\t\t\tm->type == MENU_PLAYLIST\n\t\t\t\t\t&& options_get_bool(\"PlaylistFullPaths\"));\n\t}\n\n\tm->total_time = plist_total_time (files, &m->total_time_for_all);\n}\n\nstatic void clear_area (WINDOW *w, const int posx, const int posy,\n\t\tconst int width, const int height)\n{\n\tint y;\n\tchar line[512];\n\n\tassert (width < ssizeof(line));\n\n\tmemset (line, ' ', width);\n\tline[width] = 0;\n\n\twattrset (w, get_color(CLR_BACKGROUND));\n\tfor (y = posy; y < posy + height; y++) {\n\t\twmove (w, y, posx);\n\t\txwaddstr (w, line);\n\t}\n}\n\nstatic void side_menu_draw_frame (const struct side_menu *m)\n{\n\tchar *title;\n\n\tassert (m != NULL);\n\tassert (m->visible);\n\n\tif (m->title) {\n\t\tif ((int)strwidth(m->title) > m->width - 4) {\n\t\t\tchar *tail;\n\n\t\t\ttail = xstrtail (m->title, m->width - 7);\n\t\t\ttitle = (char *)xmalloc (strlen(tail) + 4);\n\t\t\tsprintf (title, \"...%s\", tail);\n\t\t\tfree (tail);\n\t\t}\n\t\telse\n\t\t\ttitle = xstrdup (m->title);\n\t}\n\telse\n\t\ttitle = NULL;\n\n\t/* Border */\n\twattrset (m->win, get_color(CLR_FRAME));\n\n\t/* upper left corner */\n\twmove (m->win, m->posy, m->posx);\n\twaddch (m->win, lines.ulcorn);\n\n\t/* upper line */\n\twhline (m->win, lines.horiz, m->width - 2);\n\n\t/* upper right corner */\n\twmove (m->win, m->posy, m->posx + m->width - 1);\n\twaddch (m->win, lines.urcorn);\n\n\t/* left line */\n\twmove (m->win, m->posy + 1, m->posx);\n\twvline (m->win, lines.vert, m->height - 1);\n\n\t/* right line */\n\twmove (m->win, m->posy + 1, m->posx + m->width - 1);\n\twvline (m->win, lines.vert, m->height - 1);\n\n\tif (m->posy + m->height < LINES - 4) {\n\n\t\t/* bottom left corner */\n\t\twmove (m->win, m->posy + m->height - 1, m->posx);\n\t\twaddch (m->win, lines.llcorn);\n\n\t\t/* bottom line */\n\t\twhline (m->win, lines.horiz, m->width - 2);\n\n\t\t/* bottom right corner */\n\t\twmove (m->win, m->posy + m->height - 1, m->posx + m->width - 1);\n\t\twaddch (m->win, lines.lrcorn);\n\t}\n\n\t/* The title */\n\tif (title) {\n\t\twmove (m->win, m->posy, m->posx + m->width / 2\n\t\t\t\t- strwidth(title) / 2 - 1);\n\n\t\twattrset (m->win, get_color(CLR_FRAME));\n\t\twaddch (m->win, lines.rtee);\n\n\t\twattrset (m->win, get_color(CLR_WIN_TITLE));\n\t\txwaddstr (m->win, title);\n\n\t\twattrset (m->win, get_color(CLR_FRAME));\n\t\twaddch (m->win, lines.ltee);\n\n\t\tfree (title);\n\t}\n}\n\nstatic void side_menu_draw (const struct side_menu *m, const int active)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\n\tclear_area (m->win, m->posx, m->posy, m->width, m->height);\n\tside_menu_draw_frame (m);\n\n\tif (m->type == MENU_DIR || m->type == MENU_PLAYLIST\n\t\t\t|| m->type == MENU_THEMES) {\n\t\tmenu_draw (m->menu.list.main, active);\n\t\tif (options_get_bool(\"UseCursorSelection\"))\n\t\t\tmenu_set_cursor (m->menu.list.main);\n\t}\n\telse\n\t\tabort ();\n}\n\nstatic void side_menu_cmd (struct side_menu *m, const enum key_cmd cmd)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\n\tif (m->type == MENU_DIR || m->type == MENU_PLAYLIST\n\t\t\t|| m->type == MENU_THEMES) {\n\t\tswitch (cmd) {\n\t\t\tcase KEY_CMD_MENU_DOWN:\n\t\t\t\tmenu_driver (m->menu.list.main, REQ_DOWN);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MENU_UP:\n\t\t\t\tmenu_driver (m->menu.list.main, REQ_UP);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MENU_NPAGE:\n\t\t\t\tmenu_driver (m->menu.list.main, REQ_PGDOWN);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MENU_PPAGE:\n\t\t\t\tmenu_driver (m->menu.list.main, REQ_PGUP);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MENU_FIRST:\n\t\t\t\tmenu_driver (m->menu.list.main, REQ_TOP);\n\t\t\t\tbreak;\n\t\t\tcase KEY_CMD_MENU_LAST:\n\t\t\t\tmenu_driver (m->menu.list.main, REQ_BOTTOM);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tabort ();\n\t\t}\n\t}\n\telse\n\t\tabort ();\n}\n\nstatic enum file_type side_menu_curritem_get_type (const struct side_menu *m)\n{\n\tstruct menu_item *mi;\n\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST\n\t\t\t|| m->type == MENU_THEMES);\n\n\tmi = menu_curritem (m->menu.list.main);\n\n\tif (mi)\n\t\treturn menu_item_get_type (mi);\n\n\treturn F_OTHER;\n}\n\nstatic char *side_menu_get_curr_file (const struct side_menu *m)\n{\n\tstruct menu_item *mi;\n\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST\n\t\t\t|| m->type == MENU_THEMES);\n\n\tmi = menu_curritem (m->menu.list.main);\n\n\tif (mi)\n\t\treturn menu_item_get_file (mi);\n\n\treturn NULL;\n}\n\nstatic struct side_menu *find_side_menu (struct main_win *w,\n\t\tconst enum side_menu_type type)\n{\n\tsize_t ix;\n\n\tassert (w != NULL);\n\n\tfor (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) {\n\t\tstruct side_menu *m = &w->menus[ix];\n\n\t\tif (m->visible && m->type == type)\n\t\t\treturn m;\n\t}\n\n\tabort (); /* menu not found - BUG */\n}\n\nstatic void side_menu_set_curr_item_title (struct side_menu *m,\n\t\tconst char *title)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (title != NULL);\n\n\tmenu_setcurritem_title (m->menu.list.main, title);\n}\n\n/* Update menu item using the playlist item. */\nstatic void update_menu_item (struct menu_item *mi,\n\t\tconst struct plist *plist,\n\t\tconst int n, const int full_path)\n{\n\tbool made_from_tags;\n\tchar *title;\n\tconst struct plist_item *item;\n\n\tassert (mi != NULL);\n\tassert (plist != NULL);\n\tassert (n >= 0);\n\n\titem = &plist->items[n];\n\n\tif (item->tags && item->tags->time != -1) {\n\t\tchar time_str[32];\n\n\t\tsec_to_min (time_str, item->tags->time);\n\t\tmenu_item_set_time (mi, time_str);\n\t}\n\telse\n\t\tmenu_item_set_time (mi, \"\");\n\n\tmade_from_tags = (options_get_bool (\"ReadTags\") && item->title_tags);\n\n\tif (made_from_tags)\n\t\ttitle = make_menu_title (item->title_tags, 1, 0);\n\telse\n\t\ttitle = make_menu_title (item->title_file, 0, full_path);\n\n\tmenu_item_set_title (mi, title);\n\n\tif (full_path && !made_from_tags)\n\t\tmenu_item_set_align (mi, MENU_ALIGN_RIGHT);\n\telse\n\t\tmenu_item_set_align (mi, MENU_ALIGN_LEFT);\n\n\tmenu_item_set_queue_pos (mi, item->queue_pos);\n\n\tfree (title);\n\n}\n\n/* Update item title and time for this item if it's present on this menu.\n * Return a non-zero value if the item is visible. */\nstatic int side_menu_update_item (struct side_menu *m,\n\t\tconst struct plist *plist, const int n)\n{\n\tstruct menu_item *mi;\n\tint visible = 0;\n\tchar *file;\n\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\tassert (plist != NULL);\n\tassert (LIMIT(n, plist->num));\n\n\tfile = plist_get_file (plist, n);\n\tassert (file != NULL);\n\n\tif ((mi = menu_find(m->menu.list.main, file))) {\n\t\tupdate_menu_item (mi, plist, n, m->type == MENU_PLAYLIST\n\t\t\t\t&& options_get_bool(\"PlaylistFullpaths\"));\n\t\tvisible = menu_is_visible (m->menu.list.main, mi);\n\t}\n\tif (m->menu.list.copy\n\t\t\t&& (mi = menu_find(m->menu.list.copy, file))) {\n\t\tupdate_menu_item (mi, plist, n, m->type == MENU_PLAYLIST\n\t\t\t\t&& options_get_bool(\"PlaylistFullpaths\"));\n\t\tvisible = visible || menu_is_visible (m->menu.list.main, mi);\n\t}\n\n\tfree (file);\n\n\tm->total_time = plist_total_time (plist, &m->total_time_for_all);\n\n\treturn visible;\n}\n\nstatic void side_menu_unmark_file (struct side_menu *m)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\n\tmenu_unmark_item (m->menu.list.main);\n\tif (m->menu.list.copy)\n\t\tmenu_unmark_item (m->menu.list.copy);\n}\n\nstatic void side_menu_mark_file (struct side_menu *m, const char *file)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\n\tmenu_mark_item (m->menu.list.main, file);\n\tif (m->menu.list.copy)\n\t\tmenu_mark_item (m->menu.list.copy, file);\n}\n\nstatic void side_menu_add_file (struct side_menu *m, const char *file,\n\t\tconst char *title, const enum file_type type)\n{\n\tstruct menu_item *added;\n\n\tadded = menu_add (m->menu.list.main, title, type, file);\n\n\tmenu_item_set_attr_normal (added, get_color(CLR_MENU_ITEM_FILE));\n\tmenu_item_set_attr_sel (added, get_color(CLR_MENU_ITEM_FILE_SELECTED));\n\tmenu_item_set_attr_marked (added, get_color(CLR_MENU_ITEM_FILE_MARKED));\n\tmenu_item_set_attr_sel_marked (added,\n\t\t\tget_color(CLR_MENU_ITEM_FILE_MARKED_SELECTED));\n}\n\nstatic int side_menu_add_plist_item (struct side_menu *m,\n\t\tconst struct plist *plist, const int num)\n{\n\tint visible;\n\n\tassert (m != NULL);\n\tassert (plist != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\n\tvisible = add_to_menu (m->menu.list.copy ? m->menu.list.copy\n\t\t\t: m->menu.list.main,\n\t\t\tplist, num,\n\t\t\tm->type == MENU_PLAYLIST\n\t\t\t&& options_get_bool(\"PlaylistFullPaths\"));\n\tm->total_time = plist_total_time (plist, &m->total_time_for_all);\n\n\treturn visible;\n}\n\nstatic int side_menu_is_time_for_all (const struct side_menu *m)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\n\treturn m->total_time_for_all;\n}\n\nstatic int side_menu_get_files_time (const struct side_menu *m)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\n\treturn m->total_time;\n}\n\nstatic void side_menu_update_show_time (struct side_menu *m)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\n\tmenu_set_show_time (m->menu.list.main,\n\t\t\t\tstrcasecmp(options_get_symb(\"ShowTime\"), \"no\"));\n}\n\nstatic void side_menu_update_show_format (struct side_menu *m)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\n\tmenu_set_show_format (m->menu.list.main, options_get_bool(\"ShowFormat\"));\n}\n\nstatic void side_menu_get_state (const struct side_menu *m,\n\t\tstruct side_menu_state *st)\n{\n\tassert (m != NULL);\n\tassert (st != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\n\tmenu_get_state (m->menu.list.main, &st->menu_state);\n}\n\nstatic void side_menu_set_state (struct side_menu *m,\n\t\tconst struct side_menu_state *st)\n{\n\tassert (m != NULL);\n\tassert (st != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\n\tmenu_set_state (m->menu.list.main, &st->menu_state);\n}\n\nstatic void side_menu_del_item (struct side_menu *m, const char *file)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\n\tmenu_del_item (m->menu.list.copy ? m->menu.list.copy : m->menu.list.main,\n\t\t\tfile);\n}\n\nstatic void side_menu_set_plist_time (struct side_menu *m, const int time,\n\t\tconst int time_for_all)\n{\n\tassert (m != NULL);\n\tassert (time >= 0);\n\tassert (m->type == MENU_DIR || m->type == MENU_PLAYLIST);\n\n\tm->total_time = time;\n\tm->total_time_for_all = time_for_all;\n}\n\n/* Replace the menu with one having only those items which contain 'pattern'.\n * If no items match, don't do anything.\n * Return the number of matching items. */\nstatic int side_menu_filter (struct side_menu *m, const char *pattern)\n{\n\tstruct menu *filtered_menu;\n\n\tassert (m != NULL);\n\tassert (pattern != NULL);\n\tassert (m->menu.list.main != NULL);\n\n\tfiltered_menu = menu_filter_pattern (m->menu.list.copy\n\t\t\t? m->menu.list.copy : m->menu.list.main, pattern);\n\n\tif (menu_nitems(filtered_menu) == 0) {\n\t\tmenu_free (filtered_menu);\n\t\treturn 0;\n\t}\n\n\tif (m->menu.list.copy)\n\t\tmenu_free (m->menu.list.main);\n\telse\n\t\tm->menu.list.copy = m->menu.list.main;\n\n\tm->menu.list.main = filtered_menu;\n\n\treturn menu_nitems (filtered_menu);\n}\n\nstatic void side_menu_use_main (struct side_menu *m)\n{\n\tassert (m != NULL);\n\tassert (m->menu.list.main != NULL);\n\n\tif (m->menu.list.copy) {\n\t\tmenu_free (m->menu.list.main);\n\t\tm->menu.list.main = m->menu.list.copy;\n\t\tm->menu.list.copy = NULL;\n\t}\n}\n\nstatic void side_menu_make_visible (struct side_menu *m, const char *file)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_PLAYLIST || m->type == MENU_DIR);\n\tassert (file != NULL);\n\n\tif (!m->menu.list.copy)\n\t\tmenu_make_visible (m->menu.list.main, file);\n}\n\nstatic void side_menu_swap_items (struct side_menu *m, const char *file1,\n\t\tconst char *file2)\n{\n\tassert (m != NULL);\n\tassert (m->visible);\n\tassert (m->type == MENU_PLAYLIST || m->type == MENU_DIR);\n\tassert (file1 != NULL);\n\tassert (file2 != NULL);\n\tassert (m->menu.list.main != NULL);\n\tassert (m->menu.list.copy == NULL);\n\n\tmenu_swap_items (m->menu.list.main, file1, file2);\n}\n\nstatic void side_menu_select_file (struct side_menu *m, const char *file)\n{\n\tassert (m != NULL);\n\tassert (file != NULL);\n\n\tif (m->type == MENU_DIR || m->type == MENU_PLAYLIST)\n\t\tmenu_setcurritem_file (m->menu.list.main, file);\n\telse\n\t\tabort ();\n}\n\nstatic void side_menu_resize (struct side_menu *m,\n\t\tconst struct window_params *wp)\n{\n\tassert (m != NULL);\n\n\tm->posx = wp->x;\n\tm->posy = wp->y;\n\tm->height = wp->height;\n\tm->width = wp->width;\n\n\tif (m->type == MENU_DIR || m->type == MENU_PLAYLIST\n\t\t\t|| m->type == MENU_THEMES) {\n\t\tmenu_update_size (m->menu.list.main, m->posx + 1, m->posy + 1,\n\t\t\t\tm->width - 2, side_menu_get_menu_height(m));\n\t\tif (m->menu.list.copy)\n\t\t\tmenu_update_size (m->menu.list.copy, m->posx + 1,\n\t\t\t\t\tm->posy + 1, m->width - 2,\n\t\t\t\t\tside_menu_get_menu_height(m));\n\t}\n\telse\n\t\tabort ();\n}\n\nstatic void main_win_draw_too_small_screen (const struct main_win *w)\n{\n\tassert (w != NULL);\n\tassert (w->too_small);\n\n\twerase (w->win);\n\twbkgd (w->win, get_color(CLR_BACKGROUND));\n\n\twmove (w->win, 0, 0);\n\twattrset (w->win, get_color(CLR_MESSAGE));\n\txmvwaddstr (w->win, LINES/2,\n\t\t\tCOLS/2 - (sizeof(\"...TERMINAL IS TOO SMALL...\")-1)/2,\n\t\t\t\"...TERMINAL TOO SMALL...\");\n}\n\nstatic void main_win_draw_help_screen (const struct main_win *w)\n{\n\tint i;\n\tint max_lines;\n\tint help_lines;\n\tchar **help;\n\n\tassert (w != NULL);\n\tassert (w->in_help);\n\n\tmax_lines = w->help_screen_top + LINES - 6;\n\n\thelp = get_keys_help (&help_lines);\n\n\twerase (w->win);\n\twbkgd (w->win, get_color(CLR_BACKGROUND));\n\n\twmove (w->win, 0, 0);\n\tif (w->help_screen_top != 0) {\n\t\twattrset (w->win, get_color(CLR_MESSAGE));\n\t\txmvwaddstr (w->win, 0, COLS/2 - (sizeof(\"...MORE...\")-1)/2,\n\t\t\t\t\"...MORE...\");\n\t}\n\twmove (w->win, 1, 0);\n\twattrset (w->win, get_color(CLR_LEGEND));\n\tfor (i = w->help_screen_top; i < max_lines && i < help_lines; i++) {\n\t\txwaddstr (w->win, help[i]);\n\t\twaddch (w->win, '\\n');\n\t}\n\tif (i != help_lines) {\n\t\twattrset (w->win, get_color(CLR_MESSAGE));\n\t\txmvwaddstr (w->win, LINES-5,\n\t\t\t\tCOLS/2 - (sizeof(\"...MORE...\")-1)/2,\n\t\t\t\t\"...MORE...\");\n\t}\n}\n\nstatic void main_win_draw_lyrics_screen (const struct main_win *w)\n{\n\tint i;\n\tint max_lines;\n\tint height, width;\n\tlists_t_strs *lyrics_array;\n\n\tassert (w != NULL);\n\tassert (w->in_lyrics);\n\n\tmax_lines = w->lyrics_screen_top + LINES - 6;\n\n\twerase (w->win);\n\twbkgd (w->win, get_color(CLR_BACKGROUND));\n\n\twmove (w->win, 0, 0);\n\tif (w->lyrics_screen_top != 0) {\n\t\twattrset (w->win, get_color(CLR_MESSAGE));\n\t\txmvwaddstr (w->win, 0, COLS/2 - (sizeof(\"...MORE...\")-1)/2,\n\t\t\t\t\"...MORE...\");\n\t}\n\twmove (w->win, 1, 0);\n\twattrset (w->win, get_color(CLR_LEGEND));\n\tgetmaxyx (w->win, height, width);\n\tlyrics_array = lyrics_format (height, width);\n\tfor (i = w->lyrics_screen_top; i < max_lines && i < lists_strs_size (lyrics_array); i++)\n\t\txwaddstr (w->win, lists_strs_at (lyrics_array, i));\n\tif (i != lists_strs_size (lyrics_array)) {\n\t\twattrset (w->win, get_color(CLR_MESSAGE));\n\t\txmvwaddstr (w->win, LINES-5,\n\t\t\t\tCOLS/2 - (sizeof(\"...MORE...\")-1)/2,\n\t\t\t\t\"...MORE...\");\n\t}\n\tlists_strs_free (lyrics_array);\n}\n\nstatic void main_win_draw (struct main_win *w)\n{\n\tsize_t ix;\n\n\tif (w->in_help)\n\t\tmain_win_draw_help_screen (w);\n\telse if (w->in_lyrics)\n\t\tmain_win_draw_lyrics_screen (w);\n\telse if (w->too_small)\n\t\tmain_win_draw_too_small_screen (w);\n\telse {\n\t\twerase (w->win);\n\n\t\t/* Draw all visible menus.  Draw the selected menu last. */\n\t\tfor (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1)\n\t\t\tif (w->menus[ix].visible && ix != (size_t)w->selected_menu)\n\t\t\t\tside_menu_draw (&w->menus[ix], 0);\n\n\t\tside_menu_draw (&w->menus[w->selected_menu], 1);\n\t}\n}\n\nstatic enum side_menu_type iface_to_side_menu (const enum iface_menu iface_menu)\n{\n\tswitch (iface_menu) {\n\t\tcase IFACE_MENU_PLIST:\n\t\t\treturn MENU_PLAYLIST;\n\t\tcase IFACE_MENU_DIR:\n\t\t\treturn MENU_DIR;\n\t\tdefault:\n\t\t\tabort (); /* BUG */\n\t}\n}\n\nstatic void main_win_set_dir_content (struct main_win *w,\n\t\tconst enum iface_menu iface_menu, const struct plist *files,\n\t\tconst lists_t_strs *dirs, const lists_t_strs *playlists)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\n\tm = find_side_menu (w, iface_to_side_menu(iface_menu));\n\n\tside_menu_make_list_content (m, files, dirs, playlists,\n\t\t\tiface_menu == IFACE_MENU_DIR);\n\tif (w->curr_file)\n\t\tside_menu_mark_file (m, w->curr_file);\n\tmain_win_draw (w);\n}\n\nstatic void main_win_set_title (struct main_win *w,\n\t\tconst enum side_menu_type type,\n\t\tconst char *title)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\tassert (title != NULL);\n\n\tm = find_side_menu (w, type);\n\tside_menu_set_title (m, title);\n\tmain_win_draw (w);\n}\n\nstatic void main_win_update_dir_content (struct main_win *w,\n\t\tconst enum iface_menu iface_menu, const struct plist *files,\n\t\tconst lists_t_strs *dirs, const lists_t_strs *playlists)\n{\n\tstruct side_menu *m;\n\tstruct side_menu_state ms;\n\n\tassert (w != NULL);\n\n\tm = find_side_menu (w, iface_menu == IFACE_MENU_DIR ? MENU_DIR\n\t\t\t: MENU_PLAYLIST);\n\n\tside_menu_get_state (m, &ms);\n\tside_menu_make_list_content (m, files, dirs, playlists, 1);\n\tside_menu_set_state (m, &ms);\n\tif (w->curr_file)\n\t\tside_menu_mark_file (m, w->curr_file);\n\tmain_win_draw (w);\n}\n\nstatic void main_win_switch_to (struct main_win *w,\n\t\tconst enum side_menu_type menu)\n{\n\tsize_t ix;\n\n\tassert (w != NULL);\n\n\tif (w->selected_menu == 2) /* if the themes menu is selected */\n\t\tside_menu_destroy (&w->menus[2]);\n\n\tfor (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1)\n\t\tif (w->menus[ix].type == menu) {\n\t\t\tw->selected_menu = ix;\n\t\t\tbreak;\n\t\t}\n\n\tassert (ix < ARRAY_SIZE(w->menus));\n\n\tmain_win_draw (w);\n}\n\nstatic void main_win_switch_to_help (struct main_win *w)\n{\n\tassert (w != NULL);\n\n\tw->in_help = 1;\n\tmain_win_draw (w);\n}\n\nstatic void main_win_switch_to_lyrics (struct main_win *w)\n{\n\tassert (w != NULL);\n\n\tw->in_lyrics = 1;\n\tmain_win_draw (w);\n}\n\nstatic void main_win_create_themes_menu (struct main_win *w)\n{\n\tstruct window_params p;\n\n\tassert (w != NULL);\n\n\tp.x = 0;\n\tp.y = 0;\n\tp.width = COLS;\n\tp.height = LINES - 4;\n\n\tside_menu_init (&w->menus[2], MENU_THEMES, w->win, &p);\n\tside_menu_set_title (&w->menus[2], \"Themes\");\n}\n\nstatic void main_win_menu_cmd (struct main_win *w, const enum key_cmd cmd)\n{\n\tassert (w != NULL);\n\n\tside_menu_cmd (&w->menus[w->selected_menu], cmd);\n\tmain_win_draw (w);\n}\n\nstatic enum file_type main_win_curritem_get_type (const struct main_win *w)\n{\n\tassert (w != NULL);\n\n\treturn side_menu_curritem_get_type (&w->menus[w->selected_menu]);\n}\n\nstatic char *main_win_get_curr_file (const struct main_win *w)\n{\n\tassert (w != NULL);\n\n\treturn side_menu_get_curr_file (&w->menus[w->selected_menu]);\n}\n\nstatic int main_win_in_dir_menu (const struct main_win *w)\n{\n\tassert (w != NULL);\n\n\treturn w->menus[w->selected_menu].type == MENU_DIR;\n}\n\nstatic int main_win_in_help (const struct main_win *w)\n{\n\tassert (w != NULL);\n\n\treturn w->in_help;\n}\n\nstatic int main_win_in_lyrics (const struct main_win *w)\n{\n\tassert (w != NULL);\n\n\treturn w->in_lyrics;\n}\n\nstatic int main_win_in_plist_menu (const struct main_win *w)\n{\n\tassert (w != NULL);\n\n\treturn w->menus[w->selected_menu].type == MENU_PLAYLIST;\n}\n\nstatic int main_win_in_theme_menu (const struct main_win *w)\n{\n\tassert (w != NULL);\n\n\treturn w->menus[w->selected_menu].type == MENU_THEMES;\n}\n\nstatic void main_win_set_curr_item_title (struct main_win *w, const char *title)\n{\n\tassert (w != NULL);\n\tassert (title != NULL);\n\n\tside_menu_set_curr_item_title (&w->menus[w->selected_menu], title);\n\tmain_win_draw (w);\n}\n\n/* Update item title and time on all menus where it's present. */\nstatic void main_win_update_item (struct main_win *w,\n\t\tconst enum iface_menu iface_menu, const struct plist *plist,\n\t\tconst int n)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\tassert (plist != NULL);\n\tassert (LIMIT(n, plist->num));\n\n\tm = find_side_menu (w, iface_to_side_menu(iface_menu));\n\n\tif (side_menu_update_item(m, plist, n))\n\t\tmain_win_draw (w);\n}\n\n/* Mark the played file on all lists of files or unmark it when file is NULL. */\nstatic void main_win_set_played_file (struct main_win *w, const char *file)\n{\n\tsize_t ix;\n\n\tassert (w != NULL);\n\n\tif (w->curr_file)\n\t\tfree (w->curr_file);\n\tw->curr_file = xstrdup (file);\n\n\tfor (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) {\n\t\tstruct side_menu *m = &w->menus[ix];\n\n\t\tif (m->visible && (m->type == MENU_DIR\n\t\t\t\t\t|| m->type == MENU_PLAYLIST)) {\n\t\t\tside_menu_unmark_file (m);\n\t\t\tif (file)\n\t\t\t\tside_menu_mark_file (m, file);\n\t\t}\n\t}\n\n\tmain_win_draw (w);\n}\n\nstatic int main_win_menu_filter (struct main_win *w, const char *pattern)\n{\n\tint num;\n\n\tassert (w != NULL);\n\tassert (pattern != NULL);\n\n\tnum = side_menu_filter (&w->menus[w->selected_menu], pattern);\n\n\tif (num)\n\t\tmain_win_draw (w);\n\n\treturn num;\n}\n\nstatic void main_win_clear_filter_menu (struct main_win *w)\n{\n\tassert (w != NULL);\n\n\tside_menu_use_main (&w->menus[w->selected_menu]);\n\tmain_win_draw (w);\n}\n\nstatic void main_win_set_plist_time (struct main_win *w, const int time,\n\t\tconst int time_for_all)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\n\tm = find_side_menu (w, MENU_PLAYLIST);\n\tside_menu_set_plist_time (m, time, time_for_all);\n}\n\nstatic void main_win_add_to_plist (struct main_win *w, const struct plist *plist,\n\t\tconst int num)\n{\n\tstruct side_menu *m;\n\tint need_redraw;\n\n\tassert (plist != NULL);\n\n\tm = find_side_menu (w, MENU_PLAYLIST);\n\tneed_redraw = side_menu_add_plist_item (m, plist, num);\n\tif (w->curr_file)\n\t\tside_menu_mark_file (m, w->curr_file);\n\tif (need_redraw)\n\t\tmain_win_draw (w);\n}\n\nstatic void main_win_add_file (struct main_win *w, const char *file,\n\t\tconst char *title, const enum file_type type)\n{\n\tassert (w != NULL);\n\tassert (file != NULL);\n\tassert (title != NULL);\n\n\tside_menu_add_file (&w->menus[w->selected_menu], file, title, type);\n\tmain_win_draw (w);\n}\n\nstatic int main_win_get_files_time (const struct main_win *w,\n\t\tconst enum iface_menu menu)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\n\tm = find_side_menu ((struct main_win *)w, iface_to_side_menu(menu));\n\n\treturn side_menu_get_files_time (m);\n}\n\nstatic int main_win_is_time_for_all (const struct main_win *w,\n\t\tconst enum iface_menu menu)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\n\tm = find_side_menu ((struct main_win *)w, iface_to_side_menu(menu));\n\n\treturn side_menu_is_time_for_all (m);\n}\n\nstatic int main_win_get_curr_files_time (const struct main_win *w)\n{\n\tassert (w != NULL);\n\n\treturn side_menu_get_files_time (&w->menus[w->selected_menu]);\n}\n\nstatic int main_win_is_curr_time_for_all (const struct main_win *w)\n{\n\tassert (w != NULL);\n\n\treturn side_menu_is_time_for_all (&w->menus[w->selected_menu]);\n}\n\nstatic void main_win_handle_help_key (struct main_win *w,\n\t\tconst struct iface_key *k)\n{\n\tint help_lines;\n\n\tassert (w != NULL);\n\tassert (w->in_help);\n\n\tget_keys_help (&help_lines);\n\n\tif ((k->type == IFACE_KEY_FUNCTION && (\n\t\t\t\t\tk->key.func == KEY_DOWN\n\t\t\t\t\t|| k->key.func == KEY_NPAGE))\n\t\t\t|| (k->key.ucs == '\\n')) {\n\t\tif (w->help_screen_top + LINES - 5 <= help_lines)\n\t\t\tw->help_screen_top++;\n\t}\n\telse {\n\t\tif (k->type == IFACE_KEY_FUNCTION && (k->key.func == KEY_UP\n\t\t\t\t\t|| k->key.func == KEY_PPAGE)) {\n\t\t\tif (w->help_screen_top > 0)\n\t\t\t\tw->help_screen_top--;\n\t\t}\n\t\telse if (k->key.func != KEY_RESIZE)\n\t\t\tw->in_help = 0;\n\t}\n\n\tmain_win_draw (w);\n}\n\nstatic void main_win_handle_lyrics_key (struct main_win *w,\n\t\tconst struct iface_key *k)\n{\n\tint height, width;\n\tlists_t_strs *lyrics_array;\n\n\tassert (w != NULL);\n\tassert (w->in_lyrics);\n\n\tif ((k->type == IFACE_KEY_FUNCTION && (\n\t\t\t\t\tk->key.func == KEY_DOWN\n\t\t\t\t\t|| k->key.func == KEY_NPAGE))\n\t\t\t|| (k->key.ucs == '\\n')) {\n\t\tgetmaxyx (w->win, height, width);\n\t\tlyrics_array = lyrics_format (height, width);\n\t\tif (w->lyrics_screen_top + LINES - 5 <= lists_strs_size (lyrics_array))\n\t\t\tw->lyrics_screen_top++;\n\t\tlists_strs_free (lyrics_array);\n\t}\n\telse {\n\t\tif (k->type == IFACE_KEY_FUNCTION && (k->key.func == KEY_UP\n\t\t\t\t\t|| k->key.func == KEY_PPAGE)) {\n\t\t\tif (w->lyrics_screen_top > 0)\n\t\t\t\tw->lyrics_screen_top--;\n\t\t}\n\t\telse if (k->key.func != KEY_RESIZE)\n\t\t\tw->in_lyrics = 0;\n\t}\n\n\tmain_win_draw (w);\n}\n\nstatic void main_win_swap_plist_items (struct main_win *w, const char *file1,\n\t\tconst char *file2)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\tassert (file1 != NULL);\n\tassert (file2 != NULL);\n\n\tm = find_side_menu (w, MENU_PLAYLIST);\n\tside_menu_swap_items (m, file1, file2);\n\tmain_win_draw (w);\n}\n\nstatic void main_win_use_layout (struct main_win *w, lists_t_strs *layout_fmt)\n{\n\tstruct main_win_layout l;\n\tbool rc ASSERT_ONLY;\n\n\tassert (w != NULL);\n\tassert (layout_fmt != NULL);\n\n\tw->layout_fmt = layout_fmt;\n\n\trc = parse_layout (&l, layout_fmt);\n\tassert (rc);\n\n\tside_menu_resize (&w->menus[0], &l.menus[0]);\n\tside_menu_resize (&w->menus[1], &l.menus[1]);\n\n\tmain_win_draw (w);\n}\n\nstatic void validate_layouts ()\n{\n\tstruct main_win_layout l;\n\tlists_t_strs *layout_fmt;\n\n\tlayout_fmt = options_get_list (\"Layout1\");\n\tif (lists_strs_empty (layout_fmt) || !parse_layout(&l, layout_fmt))\n\t\tinterface_fatal (\"Layout1 is malformed!\");\n\n\tlayout_fmt = options_get_list (\"Layout2\");\n\tif (!lists_strs_empty (layout_fmt) && !parse_layout(&l, layout_fmt))\n\t\tinterface_fatal (\"Layout2 is malformed!\");\n\n\tlayout_fmt = options_get_list (\"Layout3\");\n\tif (!lists_strs_empty (layout_fmt) && !parse_layout(&l, layout_fmt))\n\t\tinterface_fatal (\"Layout3 is malformed!\");\n}\n\n/* Handle terminal size change. */\nstatic void main_win_resize (struct main_win *w)\n{\n\tstruct main_win_layout l;\n\tbool rc ASSERT_ONLY;\n\n\tassert (w != NULL);\n\n\tkeypad (w->win, TRUE);\n\twresize (w->win, LINES - 4, COLS);\n\twerase (w->win);\n\n\trc = parse_layout (&l, w->layout_fmt);\n\tassert (rc);\n\n\tside_menu_resize (&w->menus[0], &l.menus[0]);\n\tside_menu_resize (&w->menus[1], &l.menus[1]);\n\n\tif (w->menus[2].visible) { /* Themes menu */\n\t\tstruct window_params p;\n\n\t\tp.x = 0;\n\t\tp.y = 0;\n\t\tp.width = COLS;\n\t\tp.height = LINES - 4;\n\n\t\tside_menu_resize (&w->menus[2], &p);\n\t}\n\n\tmain_win_draw (w);\n}\n\nstatic void main_win_make_visible (struct main_win *w,\n\t\tconst enum side_menu_type type, const char *file)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\tassert (file != NULL);\n\n\tm = find_side_menu (w, type);\n\tside_menu_make_visible (m, file);\n\tmain_win_draw (w);\n}\n\nstatic void main_win_update_show_time (struct main_win *w)\n{\n\tsize_t ix;\n\n\tassert (w != NULL);\n\n\tfor (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) {\n\t\tstruct side_menu *m = &w->menus[ix];\n\n\t\tif (m->visible && (m->type == MENU_DIR\n\t\t\t\t\t|| m->type == MENU_PLAYLIST))\n\t\t\tside_menu_update_show_time (&w->menus[ix]);\n\t}\n\n\tmain_win_draw (w);\n}\n\nstatic void main_win_select_file (struct main_win *w, const char *file)\n{\n\tassert (w != NULL);\n\tassert (file != NULL);\n\n\tside_menu_select_file (&w->menus[w->selected_menu], file);\n\tmain_win_draw (w);\n}\n\nstatic void main_win_update_show_format (struct main_win *w)\n{\n\tsize_t ix;\n\n\tassert (w != NULL);\n\n\tfor (ix = 0; ix < ARRAY_SIZE(w->menus); ix += 1) {\n\t\tstruct side_menu *m = &w->menus[ix];\n\n\t\tif (m->visible && (m->type == MENU_DIR\n\t\t\t\t\t|| m->type == MENU_PLAYLIST))\n\t\t\tside_menu_update_show_format (&w->menus[ix]);\n\t}\n\n\tmain_win_draw (w);\n}\n\nstatic void main_win_del_plist_item (struct main_win *w, const char *file)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\tassert (file != NULL);\n\n\tm = find_side_menu (w, MENU_PLAYLIST);\n\tside_menu_del_item (m, file);\n\tmain_win_draw (w);\n}\n\nstatic void main_win_clear_plist (struct main_win *w)\n{\n\tstruct side_menu *m;\n\n\tassert (w != NULL);\n\n\tm = find_side_menu (w, MENU_PLAYLIST);\n\tside_menu_clear (m);\n\tmain_win_draw (w);\n}\n\n/* Write to a file and log but otherwise ignore any error. */\nstatic void soft_write (int fd, const void *buf, size_t count)\n{\n\tssize_t rc;\n\n\trc = write (fd, buf, count);\n\tif (rc < 0)\n\t\tlog_errno (\"write() failed\", errno);\n}\n\n/* Set the has_xterm variable. */\nstatic void detect_term ()\n{\n\tchar *term;\n\n\tterm = getenv (\"TERM\");\n\tif (term) {\n\t\tlists_t_strs *xterms;\n\n\t\txterms = options_get_list (\"XTerms\");\n\t\thas_xterm = lists_strs_exists (xterms, term);\n\t}\n}\n\nstatic void xterm_set_title (const int state, const char *title)\n{\n\tif (has_xterm && options_get_bool(\"SetXtermTitle\")) {\n\t\tsoft_write (1, \"\\033]0;\", sizeof(\"\\033]0;\")-1);\n\t\tsoft_write (1, \"MOC \", sizeof(\"MOC \")-1);\n\n\t\tswitch (state) {\n\t\t\tcase STATE_PLAY:\n\t\t\t\tsoft_write (1, \">\", sizeof(\">\")-1);\n\t\t\t\tbreak;\n\t\t\tcase STATE_STOP:\n\t\t\t\tsoft_write (1, \"[]\", sizeof(\"[]\")-1);\n\t\t\t\tbreak;\n\t\t\tcase STATE_PAUSE:\n\t\t\t\tsoft_write (1, \"||\", sizeof(\"||\")-1);\n\t\t\t\tbreak;\n\t\t}\n\n        if (title)\n        {\n            soft_write (1, \" - \", sizeof(\" - \")-1);\n            if (options_get_bool (\"NonUTFXterm\"))\n            {\n                char *iconv_title = xterm_iconv_str (title);\n                soft_write (1, iconv_title, strlen(iconv_title));\n                free (iconv_title);\n            }\n            else\n            {\n                soft_write (1, title, strlen(title));\n            }\n        }\n\n        soft_write (1, \"\\007\", 1);\n    }\n}\n\n\nstatic void xterm_clear_title ()\n{\n\tif (has_xterm && options_get_bool(\"SetXtermTitle\"))\n\t\tsoft_write (1, \"\\033]2;\\007\", sizeof(\"\\033]2;\\007\")-1);\n}\n\n/* Set the has_screen variable. */\nstatic void detect_screen ()\n{\n\tchar *term, *window;\n\n\tterm = getenv (\"TERM\");\n\twindow = getenv (\"WINDOW\");\n\tif (term && window && isdigit (*window)) {\n\t\tlists_t_strs *screen_terms;\n\n\t\tscreen_terms = options_get_list (\"ScreenTerms\");\n\t\thas_screen = lists_strs_exists (screen_terms, term);\n\t}\n}\n\n#define SCREEN_TITLE_START \"\\033k\"\n#define SCREEN_TITLE_END \"\\033\\\\\"\nstatic void screen_set_title (const int state, const char *title)\n{\n\tif (has_screen && options_get_bool(\"SetScreenTitle\")) {\n\t\tsoft_write (1, SCREEN_TITLE_START, sizeof(SCREEN_TITLE_START)-1);\n\t\tsoft_write (1, \"MOC \", sizeof(\"MOC \")-1);\n\n\t\tswitch (state) {\n\t\t\tcase STATE_PLAY:\n\t\t\t\tsoft_write (1, \">\", sizeof(\">\")-1);\n\t\t\t\tbreak;\n\t\t\tcase STATE_STOP:\n\t\t\t\tsoft_write (1, \"[]\", sizeof(\"[]\")-1);\n\t\t\t\tbreak;\n\t\t\tcase STATE_PAUSE:\n\t\t\t\tsoft_write (1, \"||\", sizeof(\"||\")-1);\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (title) {\n\t\t\tsoft_write (1, \" - \", sizeof(\" - \")-1);\n\t\t\tsoft_write (1, title, strlen(title));\n\t\t}\n\n\t\tsoft_write (1, SCREEN_TITLE_END, sizeof(SCREEN_TITLE_END)-1);\n\t}\n}\n\nstatic void screen_clear_title ()\n{\n\tif (has_screen && options_get_bool(\"SetScreenTitle\"))\n\t{\n\t\tsoft_write (1, SCREEN_TITLE_START, sizeof(SCREEN_TITLE_START)-1);\n\t\tsoft_write (1, SCREEN_TITLE_END, sizeof(SCREEN_TITLE_END)-1);\n\t}\n}\n\n/* Based on ASCIILines option initialize line characters with curses lines or\n * ASCII characters. */\nstatic void init_lines ()\n{\n\tif (options_get_bool(\"ASCIILines\")) {\n\t\tlines.vert = '|';\n\t\tlines.horiz = '-';\n\t\tlines.ulcorn = '+';\n\t\tlines.urcorn = '+';\n\t\tlines.llcorn = '+';\n\t\tlines.lrcorn = '+';\n\t\tlines.rtee = '|';\n\t\tlines.ltee = '|';\n\t}\n\telse {\n\t\tlines.vert = ACS_VLINE;\n\t\tlines.horiz = ACS_HLINE;\n\t\tlines.ulcorn = ACS_ULCORNER;\n\t\tlines.urcorn = ACS_URCORNER;\n\t\tlines.llcorn = ACS_LLCORNER;\n\t\tlines.lrcorn = ACS_LRCORNER;\n\t\tlines.rtee = ACS_RTEE;\n\t\tlines.ltee = ACS_LTEE;\n\t}\n}\n\n/* End the program if the terminal is too small. */\nstatic void check_term_size (struct main_win *mw, struct info_win *iw)\n{\n\tmw->too_small = iw->too_small = COLS < 59 || LINES < 7;\n}\n\n/* Update the title with the current fill. */\nstatic void bar_update_title (struct bar *b)\n{\n\tchar pct[8];\n\n\tassert (b != NULL);\n\tassert (b->show_val);\n\n\tif (!b->show_pct)\n\t\tsprintf (b->title, \"%*s\", b->width, b->orig_title);\n\telse {\n\t\tsprintf (b->title, \"%*s\", b->width - 7, b->orig_title);\n\t\tstrcpy (pct, \" 100%  \");\n\n\t\t/* The snprintf(3) below can never output 310 bytes! */\n\t\tSUPPRESS_FORMAT_TRUNCATION_WARNING\n\t\tif (b->filled < 99.99)\n\t\t\tsnprintf (pct, sizeof (pct), \"  %02.0f%%  \", b->filled);\n\t\tUNSUPPRESS_FORMAT_TRUNCATION_WARNING\n\n\t\tstrncpy (&b->title[b->width - 7], pct, strlen (pct));\n\t}\n}\n\nstatic void bar_set_title (struct bar *b, const char *title)\n{\n\tassert (b != NULL);\n\tassert (b->show_val);\n\tassert (title != NULL);\n\tassert (strlen(title) < sizeof(b->title) - 5);\n\n\tstrncpy (b->orig_title, title, b->width);\n\tb->orig_title[b->width] = 0;\n\tbar_update_title (b);\n}\n\nstatic void bar_init (struct bar *b, const int width, const char *title,\n\t\tconst int show_val, const int show_pct,\n\t\tconst int fill_color, const int empty_color)\n{\n\tassert (b != NULL);\n\tassert (width > 5 && width < ssizeof(b->title));\n\tassert (title != NULL || !show_val);\n\n\tb->width = width;\n\tb->filled = 0.0;\n\tb->show_val = show_val;\n\tb->show_pct = show_pct;\n\tb->fill_color = fill_color;\n\tb->empty_color = empty_color;\n\n\tif (show_val) {\n\t\tb->orig_title = xmalloc (b->width + 1);\n\t\tbar_set_title (b, title);\n\t} else {\n\t\tb->orig_title = NULL;\n\t\tmemset (b->title, ' ', b->width);\n\t\tb->title[b->width] = 0;\n\t}\n}\n\nstatic void bar_draw (const struct bar *b, WINDOW *win, const int pos_x,\n\t\tconst int pos_y)\n{\n\tint fill_chars; /* how many chars are \"filled\" */\n\n\tassert (b != NULL);\n\tassert (win != NULL);\n\tassert (LIMIT(pos_x, COLS - b->width));\n\tassert (LIMIT(pos_y, LINES));\n\n\tfill_chars = b->filled * b->width / 100.0;\n\n\twattrset (win, b->fill_color);\n\txmvwaddnstr (win, pos_y, pos_x, b->title, fill_chars);\n\n\twattrset (win, b->empty_color);\n\txwaddstr (win, b->title + fill_chars);\n}\n\nstatic void bar_set_fill (struct bar *b, const double fill)\n{\n\tassert (b != NULL);\n\tassert (fill >= 0.0);\n\n\tb->filled = MIN(fill, 100.0);\n\n\tif (b->show_val)\n\t\tbar_update_title (b);\n}\n\nstatic void bar_resize (struct bar *b, const int width)\n{\n\tassert (b != NULL);\n\tassert (width > 5 && width < ssizeof(b->title));\n\n\tif (b->show_val && b->width < width) {\n\t\tchar *new_title = xmalloc (width + 1);\n\t\tstrcpy (new_title, b->orig_title);\n\t\tfree (b->orig_title);\n\t\tb->orig_title = new_title;\n\t}\n\n\tb->width = width;\n\n\tif (b->show_val)\n\t\tbar_update_title (b);\n\telse {\n\t\tmemset (b->title, ' ', b->width);\n\t\tb->title[b->width] = 0;\n\t}\n}\n\nstatic struct queued_message *queued_message_create (enum message_type type)\n{\n\tstruct queued_message *result;\n\n\tresult = (struct queued_message *) xmalloc (sizeof (struct queued_message));\n\tresult->next = NULL;\n\tresult->type = type;\n\tresult->msg = NULL;\n\tresult->prompt = NULL;\n\tresult->timeout = 0;\n\tresult->callback = NULL;\n\tresult->data = NULL;\n\n\treturn result;\n}\n\nstatic void queued_message_destroy (struct queued_message *msg)\n{\n\tassert (msg != NULL);\n\n\tif (msg->msg)\n\t\tfree (msg->msg);\n\tif (msg->prompt)\n\t\tfree (msg->prompt);\n\n\tfree (msg);\n}\n\nstatic void set_startup_message (struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tw->current_message = queued_message_create (NORMAL_MSG);\n\tw->current_message->msg = xstrdup (STARTUP_MESSAGE);\n\tw->current_message->timeout = time (NULL);\n\tw->current_message->timeout += options_get_int (\"MessageLingerTime\");\n\n\tif (is_help_still_h ()) {\n\t\tstruct queued_message *msg;\n\n\t\tmsg = queued_message_create (NORMAL_MSG);\n\t\tmsg->msg = xstrdup (\"Press 'h' for the list of commands.\");\n\t\tmsg->timeout = options_get_int (\"MessageLingerTime\");\n\n\t\tw->queued_message_head = msg;\n\t\tw->queued_message_tail = msg;\n\t\tw->queued_message_total = 1;\n\t}\n}\n\nstatic void info_win_init (struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tw->win = newwin (4, COLS, LINES - 4, 0);\n\twbkgd (w->win, get_color(CLR_BACKGROUND));\n\n\tw->queued_message_head = NULL;\n\tw->queued_message_tail = NULL;\n\tw->queued_message_total = 0;\n\tw->queued_message_errors = 0;\n\n\tw->too_small = 0;\n\n\tw->state_stereo = false;\n\tw->state_shuffle = false;\n\tw->state_repeat = false;\n\tw->state_next = false;\n\tw->state_play = STATE_STOP;\n\tw->state_net = false;\n\n\tw->bitrate = -1;\n\tw->rate = -1;\n\n\tw->files_in_queue = 0;\n\n\tw->curr_time = -1;\n\tw->total_time = -1;\n\tw->block_start = -1;\n\tw->block_end = -1;\n\n\tw->title = NULL;\n\tw->status_msg[0] = 0;\n\n\tw->in_entry = 0;\n\tentry_history_init (&w->urls_history);\n\tentry_history_init (&w->dirs_history);\n\tentry_history_init (&w->user_history);\n\n\tset_startup_message (w);\n\n\tbar_init (&w->mixer_bar, 20, \"\", 1, 1,\n\t          get_color(CLR_MIXER_BAR_FILL),\n\t          get_color(CLR_MIXER_BAR_EMPTY));\n\tbar_init (&w->time_bar, COLS - 4, \"\", 1,\n\t          options_get_bool(\"ShowTimePercent\") ? 1 : 0,\n\t          get_color(CLR_TIME_BAR_FILL),\n\t          get_color(CLR_TIME_BAR_EMPTY));\n}\n\nstatic void info_win_destroy (struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (w->win)\n\t\tdelwin (w->win);\n\tif (w->in_entry)\n\t\tentry_destroy (&w->entry);\n\n\tentry_history_clear (&w->urls_history);\n\tentry_history_clear (&w->dirs_history);\n\tentry_history_clear (&w->user_history);\n}\n\n/* Set the cursor position in the right place if needed. */\nstatic void info_win_update_curs (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (w->in_entry && !w->too_small)\n\t\tentry_draw (&w->entry, w->win, 1, 0);\n}\n\nstatic void info_win_set_mixer_name (struct info_win *w, const char *name)\n{\n\tassert (w != NULL);\n\tassert (name != NULL);\n\n\tbar_set_title (&w->mixer_bar, name);\n\tif (!w->in_entry && !w->too_small) {\n\t\tbar_draw (&w->mixer_bar, w->win, COLS - 37, 0);\n\t\tinfo_win_update_curs (w);\n\t}\n}\n\nstatic void info_win_draw_status (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (!w->in_entry && !w->too_small) {\n\t\twattrset (w->win, get_color(CLR_STATUS));\n\t\twmove (w->win, 0, 6);\n\t\txwprintw (w->win, \"%-*s\", (int) sizeof(w->status_msg) - 1,\n\t\t\t\tw->status_msg);\n\t\tinfo_win_update_curs (w);\n\t}\n}\n\nstatic void info_win_set_status (struct info_win *w, const char *msg)\n{\n\tassert (w != NULL);\n\tassert (msg != NULL);\n\tassert (strlen(msg) < sizeof(w->status_msg));\n\n\tstrcpy (w->status_msg, msg);\n\tinfo_win_draw_status (w);\n}\n\nstatic void info_win_draw_files_in_queue (const struct info_win *w)\n{\n\tconst int hstart = 5 + sizeof(w->status_msg) + 2;\n\n\tassert (w != NULL);\n\n\tif(!w->in_entry && !w->too_small) {\n\t\tif (w->files_in_queue) {\n\t\t\twattrset (w->win, get_color(CLR_STATUS));\n\t\t\tmvwaddch (w->win, 0, hstart, lines.rtee);\n\t\t\txwprintw (w->win, \"Q:%3d\", w->files_in_queue);\n\t\t\twaddch (w->win, lines.ltee);\n\t\t}\n\t\telse {\n\t\t\twattrset (w->win, get_color(CLR_FRAME));\n\t\t\tmvwhline (w->win, 0, hstart, lines.horiz, 9);\n\t\t}\n\t}\n\n\tinfo_win_update_curs (w);\n}\n\nstatic void info_win_set_files_in_queue (struct info_win *w, const int num)\n{\n\tassert (w != NULL);\n\tassert (num >= 0);\n\n\tw->files_in_queue = num;\n\tinfo_win_draw_files_in_queue (w);\n}\n\nstatic void info_win_draw_state (const struct info_win *w)\n{\n\tconst char *state_symbol;\n\n\tassert (w != NULL);\n\n\tswitch (w->state_play) {\n\t\tcase STATE_PLAY:\n\t\t\tstate_symbol = \" >\";\n\t\t\tbreak;\n\t\tcase STATE_STOP:\n\t\t\tstate_symbol = \"[]\";\n\t\t\tbreak;\n\t\tcase STATE_PAUSE:\n\t\t\tstate_symbol = \"||\";\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tabort (); /* BUG */\n\t}\n\n\tif (!w->too_small) {\n\t\twattrset (w->win, get_color(CLR_STATE));\n\t\txmvwaddstr (w->win, 1, 1, state_symbol);\n\t}\n\n\tinfo_win_update_curs (w);\n}\n\n/* Draw the title or the message (informative or error). */\nstatic void info_win_draw_title (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (!w->too_small) {\n\t\tclear_area (w->win, 4, 1, COLS - 5, 1);\n\n\t\tif (w->current_message &&\n\t\t    w->current_message->msg &&\n\t\t    w->current_message->timeout >= time (NULL))\n\t\t{\n\t\t\twattrset (w->win, w->current_message->type == ERROR_MSG\n\t\t\t                  ? get_color (CLR_ERROR)\n\t\t\t                  : get_color (CLR_MESSAGE));\n\t\t\txmvwaddnstr (w->win, 1, 4, w->current_message->msg, COLS - 5);\n\t\t}\n\t\telse\n\t\t{\n\t\t\twattrset (w->win, get_color (CLR_TITLE));\n\t\t\txmvwaddnstr (w->win, 1, 4, w->title ? w->title : \"\", COLS - 5);\n\t\t}\n\t}\n\n\tinfo_win_update_curs (w);\n}\n\nstatic void info_win_set_state (struct info_win *w, const int state)\n{\n\tassert (w != NULL);\n\tassert (state == STATE_PLAY || state == STATE_STOP\n\t\t\t|| state == STATE_PAUSE);\n\n\tw->state_play = state;\n\txterm_set_title (state, w->title);\n\tscreen_set_title (state, w->title);\n\tinfo_win_draw_state (w);\n}\n\nstatic void info_win_draw_time (const struct info_win *w)\n{\n\tchar time_str[32];\n\n\tassert (w != NULL);\n\n\tif (!w->too_small) {\n\t\t/* current time */\n\t\tsec_to_min (time_str, w->curr_time != -1 ? w->curr_time : 0);\n\t\twattrset (w->win, get_color(CLR_TIME_CURRENT));\n\t\txmvwaddstr (w->win, 2, 1, time_str);\n\n\t\t/* time left */\n\t\tif (w->total_time > 0 && w->curr_time >= 0\n\t\t\t\t&& w->total_time >= w->curr_time) {\n\t\t\tsec_to_min (time_str, w->total_time - w->curr_time);\n\t\t\twmove (w->win, 2, 7);\n\t\t\twattrset (w->win, get_color(CLR_TIME_LEFT));\n\t\t\txwaddstr (w->win, time_str);\n\t\t}\n\t\telse\n\t\t\txmvwaddstr (w->win, 2, 7, \"     \");\n\n\t\t/* total time */\n\t\tsec_to_min (time_str, w->total_time != -1 ? w->total_time : 0);\n\t\twmove (w->win, 2, 14);\n\t\twattrset (w->win, get_color(CLR_TIME_TOTAL));\n\t\txwaddstr (w->win, time_str);\n\n\t\tbar_draw (&w->time_bar, w->win, 2, 3);\n\t}\n\tinfo_win_update_curs (w);\n}\n\nstatic void info_win_draw_block (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (!w->too_small)\n\t\tbar_draw (&w->time_bar, w->win, 2, 3);\n\tinfo_win_update_curs (w);\n}\n\nstatic void info_win_set_curr_time (struct info_win *w, const int time)\n{\n\tassert (w != NULL);\n\tassert (time >= -1);\n\n\tw->curr_time = time;\n\tif (w->total_time > 0 && w->curr_time >= 0)\n\t\tbar_set_fill (&w->time_bar, w->curr_time * 100.0 / w->total_time);\n\telse\n\t\tbar_set_fill (&w->time_bar, 0.0);\n\n\tinfo_win_draw_time (w);\n}\n\nstatic void info_win_set_total_time (struct info_win *w, const int time)\n{\n\tassert (w != NULL);\n\tassert (time >= -1);\n\n\tw->total_time = time;\n\n\tif (w->total_time > 0 && w->curr_time >= 0)\n\t\tbar_set_fill (&w->time_bar, w->curr_time * 100.0 / w->total_time);\n\telse\n\t\tbar_set_fill (&w->time_bar, 0.0);\n\n\tinfo_win_draw_time (w);\n}\n\nstatic void info_win_set_block_title (struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (w->total_time == -1) {\n\t\tbar_set_title (&w->time_bar, \"\");\n\t} else if (w->block_start == -1 || w->block_end == -1) {\n\t\tbar_set_title (&w->time_bar, \"\");\n\t} else if (w->block_start == 0 && w->block_end == w->total_time) {\n\t\tbar_set_title (&w->time_bar, \"\");\n\t} else {\n\t\tint start_pos, end_pos;\n\t\tchar *new_title, *decorators;\n\n\t\tstart_pos = w->block_start * w->time_bar.width / w->total_time;\n\t\tif (w->block_end < w->total_time)\n\t\t\tend_pos = w->block_end * w->time_bar.width / w->total_time;\n\t\telse\n\t\t\tend_pos = w->time_bar.width - 1;\n\n\t\tnew_title = xmalloc(w->time_bar.width + 1);\n\t\tmemset(new_title, ' ', w->time_bar.width);\n\t\tdecorators = options_get_str (\"BlockDecorators\");\n\t\tif (start_pos == end_pos) {\n\t\t\tnew_title[start_pos] = decorators[1];\n\t\t} else {\n\t\t\tnew_title[start_pos] = decorators[0];\n\t\t\tnew_title[end_pos] = decorators[2];\n\t\t}\n\t\tnew_title[w->time_bar.width] = 0x00;\n\n\t\tbar_set_title (&w->time_bar, new_title);\n\t}\n}\n\nstatic void info_win_set_block (struct info_win *w, const int block_start, const int block_end)\n{\n\tassert (w != NULL);\n\tassert (block_start == -1 || RANGE(0, block_start, w->total_time));\n\tassert (block_end == -1 || RANGE(0, block_end, w->total_time));\n\n\tinfo_win.block_start = block_start;\n\tinfo_win.block_end = block_end;\n\n\tinfo_win_set_block_title (w);\n\tinfo_win_draw_block (w);\n}\n\nstatic void info_win_set_played_title (struct info_win *w, const char *title)\n{\n\tassert (w != NULL);\n\n\tif (!w->title && !title)\n\t\treturn;\n\n\tif (w->title && title && !strcmp(w->title, title))\n\t\treturn;\n\n\tif (w->title)\n\t\tfree (w->title);\n\tw->title = xstrdup (title);\n\txterm_set_title (w->state_play, title);\n\tscreen_set_title (w->state_play, title);\n\tinfo_win_draw_title (w);\n}\n\nstatic void info_win_draw_rate (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\twattrset (w->win, get_color(CLR_SOUND_PARAMS));\n\twmove (w->win, 2, 22);\n\tif (w->rate != -1)\n\t\txwprintw (w->win, \"%3d\", w->rate);\n\telse\n\t\txwaddstr (w->win, \"   \");\n}\n\nstatic void info_win_draw_bitrate (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (!w->too_small) {\n\t\twattrset (w->win, get_color(CLR_SOUND_PARAMS));\n\t\twmove (w->win, 2, 29);\n\t\tif (w->bitrate != -1)\n\t\t\txwprintw (w->win, \"%4d\", MIN(w->bitrate, 9999));\n\t\telse\n\t\t\txwaddstr (w->win, \"    \");\n\t}\n\tinfo_win_update_curs (w);\n}\n\nstatic void info_win_set_bitrate (struct info_win *w, const int bitrate)\n{\n\tassert (w != NULL);\n\tassert (bitrate >= -1);\n\n\tw->bitrate = bitrate > 0 ? bitrate : -1;\n\tinfo_win_draw_bitrate (w);\n}\n\nstatic void info_win_set_rate (struct info_win *w, const int rate)\n{\n\tassert (w != NULL);\n\tassert (rate >= -1);\n\n\tw->rate = rate > 0 ? rate : -1;\n\tinfo_win_draw_rate (w);\n}\n\nstatic void info_win_set_mixer_value (struct info_win *w, const int value)\n{\n\tassert (w != NULL);\n\n\tbar_set_fill (&w->mixer_bar, (double) value);\n\tif (!w->in_entry && !w->too_small)\n\t\tbar_draw (&w->mixer_bar, w->win, COLS - 37, 0);\n}\n\n/* Draw a switch that is turned on or off in form of [TITLE]. */\nstatic void info_win_draw_switch (const struct info_win *w, const int posx,\n\t\tconst int posy, const char *title, const bool value)\n{\n\tassert (w != NULL);\n\tassert (title != NULL);\n\n\tif (!w->too_small) {\n\t\twattrset (w->win, get_color(\n\t\t\t\t\tvalue ? CLR_INFO_ENABLED : CLR_INFO_DISABLED));\n\t\twmove (w->win, posy, posx);\n\t\txwprintw (w->win, \"[%s]\", title);\n\t}\n\tinfo_win_update_curs (w);\n}\n\nstatic void info_win_draw_options_state (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tinfo_win_draw_switch (w, 38, 2, \"STEREO\", w->state_stereo);\n\tinfo_win_draw_switch (w, 47, 2, \"NET\", w->state_net);\n\tinfo_win_draw_switch (w, 53, 2, \"SHUFFLE\", w->state_shuffle);\n\tinfo_win_draw_switch (w, 63, 2, \"REPEAT\", w->state_repeat);\n\tinfo_win_draw_switch (w, 72, 2, \"NEXT\", w->state_next);\n}\n\nstatic void info_win_make_entry (struct info_win *w, const enum entry_type type)\n{\n\tstruct entry_history *history;\n\tconst char *prompt;\n\n\tassert (w != NULL);\n\tassert (!w->in_entry);\n\n\tprompt = NULL;\n\tswitch (type) {\n\t\tcase ENTRY_GO_DIR:\n\t\t\thistory = &w->dirs_history;\n\t\t\tbreak;\n\t\tcase ENTRY_GO_URL:\n\t\t\thistory = &w->urls_history;\n\t\t\tbreak;\n\t\tcase ENTRY_ADD_URL:\n\t\t\thistory = &w->urls_history;\n\t\t\tbreak;\n\t\tcase ENTRY_USER_QUERY:\n\t\t\thistory = &w->user_history;\n\t\t\tprompt = w->current_message->prompt;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\thistory = NULL;\n\t}\n\n\tentry_init (&w->entry, type, COLS - 4, history, prompt);\n\tw->in_entry = 1;\n\tcurs_set (1);\n\tentry_draw (&w->entry, w->win, 1, 0);\n}\n\n/* Display the next queued message. */\nstatic void info_win_display_msg (struct info_win *w)\n{\n\tint msg_changed;\n\n\tassert (w != NULL);\n\n\tmsg_changed = 0;\n\tif (w->current_message && time (NULL) > w->current_message->timeout) {\n\t\tw->callback = w->current_message->callback;\n\t\tw->data = w->current_message->data;\n\t\tqueued_message_destroy (w->current_message);\n\t\tw->current_message = NULL;\n\t\tmsg_changed = 1;\n\t}\n\n\tif (!w->current_message && w->queued_message_head && !w->in_entry) {\n\t\tw->current_message = w->queued_message_head;\n\t\tw->queued_message_head = w->current_message->next;\n\t\tw->current_message->next = NULL;\n\t\tif (!w->queued_message_head)\n\t\t\tw->queued_message_tail = NULL;\n\t\tw->queued_message_total -= 1;\n\t\tif (w->current_message->type == ERROR_MSG)\n\t\t\tw->queued_message_errors -= 1;\n\n\t\tif (msg_changed &&\n\t\t    w->current_message->msg\n\t\t    && options_get_bool (\"PrefixQueuedMessages\"))\n\t\t{\n\t\t\tchar *msg, *decorator;\n\t\t\tint len;\n\n\t\t\tmsg = w->current_message->msg;\n\t\t\tdecorator = options_get_str (\"ErrorMessagesQueued\");\n\t\t\tlen = strlen (msg) + strlen (decorator) + 10;\n\t\t\tw->current_message->msg = (char *) xmalloc (len);\n\t\t\tsnprintf (w->current_message->msg, len, \"(%d%s) %s\",\n\t\t\t          w->queued_message_total,\n\t\t\t          (w->queued_message_errors ? decorator : \"\"),\n\t\t\t          msg);\n\t\t\tw->current_message->msg[len - 1] = 0x00;\n\t\t\tfree (msg);\n\t\t}\n\n\t\tif (w->current_message->type == QUERY_MSG) {\n\t\t\tinfo_win_make_entry (w, ENTRY_USER_QUERY);\n\t\t\tw->current_message->timeout = 86400;\n\t\t}\n\n\t\tw->current_message->timeout += time (NULL);\n\t\tmsg_changed = 1;\n\t}\n\n\tif (msg_changed)\n\t\tinfo_win_draw_title (w);\n}\n\n/* Force the next queued message to be displayed. */\nstatic void info_win_disable_msg (struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (w->current_message) {\n\t\tw->current_message->timeout = 0;\n\t\tinfo_win_display_msg (w);\n\t}\n}\n\n/* Clear all queued messages. */\nstatic void info_win_clear_msg (struct info_win *w)\n{\n\tassert (w != NULL);\n\n\twhile (w->queued_message_head) {\n\t\tstruct queued_message *this_msg;\n\n\t\tthis_msg = w->queued_message_head;\n\t\tw->queued_message_head = this_msg->next;\n\t\tqueued_message_destroy (this_msg);\n\t}\n\n\tw->queued_message_total = 0;\n\tw->queued_message_errors = 0;\n\tw->queued_message_tail = NULL;\n\n\tif (w->current_message) {\n\t\tqueued_message_destroy (w->current_message);\n\t\tw->current_message = NULL;\n\t}\n}\n\n/* Queue a new message for display. */\nstatic void info_win_msg (struct info_win *w, const char *msg,\n                          enum message_type msg_type, const char *prompt,\n                          t_user_reply_callback *callback, void *data)\n{\n\tstruct queued_message *this_msg;\n\n\tassert (w != NULL);\n\tassert (msg != NULL || prompt != NULL);\n\n\tthis_msg = queued_message_create (msg_type);\n\tif (msg)\n\t\tthis_msg->msg = xstrdup (msg);\n\tif (prompt)\n\t\tthis_msg->prompt = xstrdup (prompt);\n\tthis_msg->timeout = options_get_int (\"MessageLingerTime\");\n\tthis_msg->callback = callback;\n\tthis_msg->data = data;\n\n\tif (w->queued_message_head) {\n\t\tw->queued_message_tail->next = this_msg;\n\t\tw->queued_message_tail = this_msg;\n\t} else {\n\t\tw->queued_message_head = this_msg;\n\t\tw->queued_message_tail = this_msg;\n\t}\n\tw->queued_message_total += 1;\n\tif (msg_type == ERROR_MSG)\n\t\tw->queued_message_errors += 1;\n\n\tinfo_win_display_msg (w);\n}\n\nstatic void iface_win_user_reply (struct info_win *w, const char *reply)\n{\n\tassert (w != NULL);\n\n\tif (w->callback)\n\t\tw->callback (reply, w->data);\n}\n\nstatic void info_win_user_history_add (struct info_win *w, const char *text)\n{\n\tassert (w != NULL);\n\n\tentry_history_add (&w->user_history, text);\n}\n\nstatic void info_win_set_channels (struct info_win *w, const int channels)\n{\n\tassert (w != NULL);\n\tassert (channels == 1 || channels == 2);\n\n\tw->state_stereo = (channels == 2);\n\tinfo_win_draw_options_state (w);\n}\n\nstatic int info_win_in_entry (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\treturn w->in_entry;\n}\n\nstatic enum entry_type info_win_get_entry_type (const struct info_win *w)\n{\n\tassert (w != NULL);\n\tassert (w->in_entry);\n\n\treturn entry_get_type (&w->entry);\n}\n\nstatic void info_win_set_option_state (struct info_win *w, const char *name,\n\t\tconst bool value)\n{\n\tassert (w != NULL);\n\tassert (name != NULL);\n\n\tif (!strcasecmp(name, \"Shuffle\"))\n\t\tw->state_shuffle = value;\n\telse if (!strcasecmp(name, \"Repeat\"))\n\t\tw->state_repeat = value;\n\telse if (!strcasecmp(name, \"AutoNext\"))\n\t\tw->state_next = value;\n\telse if (!strcasecmp(name, \"Net\"))\n\t\tw->state_net = value;\n\telse\n\t\tabort ();\n\n\tinfo_win_draw_options_state (w);\n}\n\n/* Convert time in second to min:sec text format(for total time in playlist).\n * 'buff' must be 48 chars long. */\nstatic void sec_to_min_plist (char *buff, const int seconds)\n{\n\tassert (seconds >= 0);\n\tif (seconds < 999 * 60 * 60 - 1) {\n\n\t\t/* the time is less than 999 * 60 minutes */\n\t\tint hour, min, sec;\n\t\thour = seconds / 3600;\n\t\tmin  = (seconds / 60) % 60;\n\t\tsec  = seconds % 60;\n\n\t\tsnprintf (buff, 48, \"%03d:%02d:%02d\", hour, min, sec);\n\t}\n\telse\n\t\tstrcpy (buff, \"!!!!!!!!!\");\n}\n\nstatic void info_win_draw_files_time (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (!w->in_entry && !w->too_small) {\n\t\tchar buf[48];\n\n\t\tsec_to_min_plist (buf, w->plist_time);\n\t\twmove (w->win, 0, COLS - 12);\n\t\twattrset (w->win, get_color(CLR_PLIST_TIME));\n\t\twaddch (w->win, w->plist_time_for_all ? ' ' : '>');\n\t\txwaddstr (w->win, buf);\n\t\tinfo_win_update_curs (w);\n\t}\n}\n\n/* Set the total time for files in the displayed menu. If time_for_all\n * has a non zero value, the time is for all files. */\nstatic void info_win_set_files_time (struct info_win *w, const int time,\n\t\tconst int time_for_all)\n{\n\tassert (w != NULL);\n\n\tw->plist_time = time;\n\tw->plist_time_for_all = time_for_all;\n\n\tinfo_win_draw_files_time (w);\n}\n\n/* Update the message timeout, redraw the window if needed. */\nstatic void info_win_tick (struct info_win *w)\n{\n\tinfo_win_display_msg (w);\n}\n\n/* Draw static elements of info_win: frames, legend etc. */\nstatic void info_win_draw_static_elements (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (!w->too_small) {\n\t\t/* window frame */\n\t\twattrset (w->win, get_color(CLR_FRAME));\n\t\twborder (w->win, lines.vert, lines.vert, lines.horiz, lines.horiz,\n\t\t\t\tlines.ltee, lines.rtee, lines.llcorn, lines.lrcorn);\n\n\t\t/* mixer frame */\n\t\tmvwaddch (w->win, 0, COLS - 38, lines.rtee);\n\t\tmvwaddch (w->win, 0, COLS - 17, lines.ltee);\n\n\t\t/* playlist time frame */\n\t\tmvwaddch (w->win, 0, COLS - 13, lines.rtee);\n\t\tmvwaddch (w->win, 0, COLS - 2, lines.ltee);\n\n\t\t/* total time frames */\n\t\twattrset (w->win, get_color(CLR_TIME_TOTAL_FRAMES));\n\t\tmvwaddch (w->win, 2, 13, '[');\n\t\tmvwaddch (w->win, 2, 19, ']');\n\n\t\t/* time bar frame */\n\t\twattrset (w->win, get_color(CLR_FRAME));\n\t\tmvwaddch (w->win, 3, COLS - 2, lines.ltee);\n\t\tmvwaddch (w->win, 3, 1, lines.rtee);\n\n\t\t/* status line frame */\n\t\tmvwaddch (w->win, 0, 5, lines.rtee);\n\t\tmvwaddch (w->win, 0, 5 + sizeof(w->status_msg), lines.ltee);\n\n\t\t/* rate and bitrate units */\n\t\twmove (w->win, 2, 25);\n\t\twattrset (w->win, get_color(CLR_LEGEND));\n\t\txwaddstr (w->win, \"kHz\t kbps\");\n\t}\n\n\tinfo_win_update_curs (w);\n}\n\nstatic void info_win_draw (const struct info_win *w)\n{\n\tassert (w != NULL);\n\n\tif (!w->too_small) {\n\t\tinfo_win_draw_static_elements (w);\n\t\tinfo_win_draw_state (w);\n\t\tinfo_win_draw_time (w);\n\t\tinfo_win_draw_block (w);\n\t\tinfo_win_draw_title (w);\n\t\tinfo_win_draw_options_state (w);\n\t\tinfo_win_draw_status (w);\n\t\tinfo_win_draw_files_in_queue (w);\n\t\tinfo_win_draw_files_time (w);\n\t\tinfo_win_draw_bitrate (w);\n\t\tinfo_win_draw_rate (w);\n\n\t\tif (w->in_entry)\n\t\t\tentry_draw (&w->entry, w->win, 1, 0);\n\t\telse\n\t\t\tbar_draw (&w->mixer_bar, w->win, COLS - 37, 0);\n\n\t\tbar_draw (&w->time_bar, w->win, 2, 3);\n\t}\n\tinfo_win_update_curs (w);\n}\n\nstatic void info_win_entry_disable (struct info_win *w)\n{\n\tassert (w != NULL);\n\tassert (w->in_entry);\n\n\tentry_destroy (&w->entry);\n\tw->in_entry = 0;\n\n\tif (!options_get_bool(\"UseCursorSelection\"))\n\t\tcurs_set (0);\n\tinfo_win_draw (w);\n}\n\n/* Handle a key while in entry. main_win is used to update the menu (filter\n * only matching items) when ENTRY_SEARCH is used. */\nstatic void info_win_entry_handle_key (struct info_win *iw, struct main_win *mw,\n\t\tconst struct iface_key *k)\n{\n\tenum key_cmd cmd;\n\tenum entry_type type;\n\n\tassert (iw != NULL);\n\tassert (mw != NULL);\n\tassert (iw->in_entry);\n\n\tcmd = get_key_cmd (CON_ENTRY, k);\n\ttype = entry_get_type (&iw->entry);\n\n\tif (type == ENTRY_SEARCH) {\n\t\tchar *text;\n\n\t\tif (k->type == IFACE_KEY_CHAR) {\n\t\t\tif (iswprint(k->key.ucs)) {\n\t\t\t\tentry_add_char (&iw->entry, k->key.ucs);\n\t\t\t\ttext = entry_get_text (&iw->entry);\n\t\t\t\tif (!main_win_menu_filter(mw, text))\n\t\t\t\t\tentry_back_space (&iw->entry);\n\t\t\t\tfree (text);\n\t\t\t}\n\n\t\t}\n\t\telse if (k->key.func == KEY_BACKSPACE) {\n\t\t\tentry_back_space (&iw->entry);\n\t\t\ttext = entry_get_text (&iw->entry);\n\t\t\tmain_win_menu_filter (mw, text);\n\t\t\tfree (text);\n\t\t}\n\t\telse if (cmd == KEY_CMD_CANCEL) {\n\t\t\tmain_win_clear_filter_menu (mw);\n\t\t\tinfo_win_entry_disable (iw);\n\t\t}\n\t\telse {\n\t\t\tenum key_cmd cmd = get_key_cmd (CON_MENU, k);\n\n\t\t\tif (cmd == KEY_CMD_MENU_UP\n\t\t\t\t\t|| cmd == KEY_CMD_MENU_DOWN\n\t\t\t\t\t|| cmd == KEY_CMD_MENU_NPAGE\n\t\t\t\t\t|| cmd == KEY_CMD_MENU_PPAGE\n\t\t\t\t\t|| cmd == KEY_CMD_MENU_FIRST\n\t\t\t\t\t|| cmd == KEY_CMD_MENU_LAST)\n\t\t\t\tmain_win_menu_cmd (mw, cmd);\n\t\t}\n\t}\n\telse {\n\t\tif (k->type == IFACE_KEY_CHAR) {\n\t\t\tif (iswprint(k->key.ucs))\n\t\t\t\tentry_add_char (&iw->entry, k->key.ucs);\n\t\t}\n\t\telse if (k->key.func == KEY_LEFT)\n\t\t\tentry_curs_left (&iw->entry);\n\t\telse if (k->key.func == KEY_RIGHT)\n\t\t\tentry_curs_right (&iw->entry);\n\t\telse if (k->key.func == KEY_BACKSPACE)\n\t\t\tentry_back_space (&iw->entry);\n\t\telse if (k->key.func == KEY_DC)\n\t\t\tentry_del_char (&iw->entry);\n\t\telse if (k->key.func == KEY_HOME)\n\t\t\tentry_home (&iw->entry);\n\t\telse if (k->key.func == KEY_END)\n\t\t\tentry_end (&iw->entry);\n\t\telse if (cmd == KEY_CMD_CANCEL) {\n\t\t\tinfo_win_entry_disable (iw);\n\t\t\tif (type == ENTRY_USER_QUERY)\n\t\t\t\tiface_user_reply (NULL);\n\t\t}\n\t\telse if ((type == ENTRY_GO_DIR || type == ENTRY_GO_URL\n\t\t\t\t\t|| type == ENTRY_ADD_URL || type == ENTRY_USER_QUERY)\n\t\t\t\t\t&& cmd != KEY_CMD_WRONG) {\n\t\t\tif (cmd == KEY_CMD_HISTORY_UP)\n\t\t\t\tentry_set_history_up (&iw->entry);\n\t\t\telse if (cmd == KEY_CMD_HISTORY_DOWN)\n\t\t\t\tentry_set_history_down (&iw->entry);\n\t\t\telse if (cmd == KEY_CMD_DELETE_START)\n\t\t\t\tentry_del_to_start (&iw->entry);\n\t\t\telse if (cmd == KEY_CMD_DELETE_END)\n\t\t\t\tentry_del_to_end (&iw->entry);\n\t\t}\n\t}\n\n\tif (iw->in_entry) /* the entry could be disabled above */\n\t\tentry_draw (&iw->entry, iw->win, 1, 0);\n}\n\nstatic void info_win_entry_set_text (struct info_win *w, const char *text)\n{\n\tassert (w != NULL);\n\tassert (text != NULL);\n\tassert (w->in_entry);\n\n\tentry_set_text (&w->entry, text);\n\tentry_draw (&w->entry, w->win, 1, 0);\n}\n\nstatic char *info_win_entry_get_text (const struct info_win *w)\n{\n\tassert (w != NULL);\n\tassert (w->in_entry);\n\n\treturn entry_get_text (&w->entry);\n}\n\nstatic void info_win_entry_history_add (struct info_win *w)\n{\n\tassert (w != NULL);\n\tassert (w->in_entry);\n\n\tentry_add_text_to_history (&w->entry);\n}\n\nstatic void info_win_entry_set_file (struct info_win *w, const char *file)\n{\n\tassert (w != NULL);\n\tassert (w->in_entry);\n\tassert (file != NULL);\n\n\tentry_set_file (&w->entry, file);\n}\n\nstatic char *info_win_entry_get_file (const struct info_win *w)\n{\n\tassert (w != NULL);\n\tassert (w->in_entry);\n\n\treturn entry_get_file (&w->entry);\n}\n\n/* Handle terminal size change. */\nstatic void info_win_resize (struct info_win *w)\n{\n\tassert (w != NULL);\n\tkeypad (w->win, TRUE);\n\twresize (w->win, 4, COLS);\n\tmvwin (w->win, LINES - 4, 0);\n\twerase (w->win);\n\n\tbar_resize (&w->mixer_bar, 20);\n\tbar_resize (&w->time_bar, COLS - 4);\n\tinfo_win_set_block_title (w);\n\n\tif (w->in_entry)\n\t\tentry_resize (&w->entry, COLS - 4);\n\n\tinfo_win_draw (w);\n}\n\nvoid windows_init ()\n{\n\tif (getenv (\"ESCDELAY\") == NULL) {\n#ifdef HAVE_SET_ESCDELAY\n\t\tset_escdelay (25);\n#else\n\t\tsetenv (\"ESCDELAY\", \"25\", 0);\n#endif\n\t}\n\n\tutf8_init ();\n\tif (!initscr ())\n\t\tfatal (\"Can't initialize terminal!\");\n\tscreen_initialized = 1;\n\tvalidate_layouts ();\n\tcbreak ();\n\tnoecho ();\n\tif (!options_get_bool (\"UseCursorSelection\"))\n\t\tcurs_set (0);\n\tuse_default_colors ();\n\n\tdetect_term ();\n\tdetect_screen ();\n\tstart_color ();\n\ttheme_init (has_xterm);\n\tinit_lines ();\n\n\tmain_win_init (&main_win, options_get_list (\"Layout1\"));\n\tinfo_win_init (&info_win);\n\n\tcheck_term_size (&main_win, &info_win);\n\n\tmain_win_draw (&main_win);\n\tinfo_win_draw (&info_win);\n\n\twnoutrefresh (main_win.win);\n\twnoutrefresh (info_win.win);\n\tdoupdate ();\n\n\tiface_initialized = 1;\n}\n\nvoid windows_reset ()\n{\n\tif (screen_initialized) {\n\t\tscreen_initialized = 0;\n\n\t\t/* endwin() sometimes fails on X-terminals when we get SIGCHLD\n\t\t * at this moment.  Double invocation seems to solve this. */\n\t\tif (endwin () == ERR && endwin () == ERR)\n\t\t\tlogit (\"endwin() failed!\");\n\n\t\t/* Make sure that the next line after we exit will be \"clear\". */\n\t\tprintf (\"\\n\");\n\t\tfflush (stdout);\n\t}\n}\n\nvoid windows_end ()\n{\n\tif (iface_initialized) {\n\t\tiface_initialized = 0;\n\n\t\tmain_win_destroy (&main_win);\n\t\tinfo_win_clear_msg (&info_win);\n\t\tinfo_win_destroy (&info_win);\n\n\t\txterm_clear_title ();\n\t\tscreen_clear_title ();\n\t\tutf8_cleanup ();\n\t}\n\n\twindows_reset ();\n\n\tlyrics_cleanup ();\n}\n\nstatic void iface_refresh_screen ()\n{\n\t/* We must do it in proper order to get the right cursor position. */\n\tif (iface_in_entry ()) {\n\t\twnoutrefresh (main_win.win);\n\t\twnoutrefresh (info_win.win);\n\t}\n\telse {\n\t\twnoutrefresh (info_win.win);\n\t\twnoutrefresh (main_win.win);\n\t}\n\tdoupdate ();\n}\n\n/* Set state of the options displayed in the information window. */\nvoid iface_set_option_state (const char *name, const bool value)\n{\n\tassert (name != NULL);\n\n\tinfo_win_set_option_state (&info_win, name, value);\n\tiface_refresh_screen ();\n}\n\n/* Set the mixer name. */\nvoid iface_set_mixer_name (const char *name)\n{\n\tassert (name != NULL);\n\n\tinfo_win_set_mixer_name (&info_win, name);\n\tiface_refresh_screen ();\n}\n\n/* Set the status message in the info window. */\nvoid iface_set_status (const char *msg)\n{\n\tassert (msg != NULL);\n\n\tif (iface_initialized) {\n\t\tinfo_win_set_status (&info_win, msg);\n\t\tiface_refresh_screen ();\n\t}\n}\n\n/* Set the number of files in song queue in the info window */\nvoid iface_set_files_in_queue (const int num)\n{\n\tassert (num >= 0);\n\n\tif (iface_initialized) {\n\t\tinfo_win_set_files_in_queue (&info_win, num);\n\t\tiface_refresh_screen ();\n\t}\n}\n\nstatic void iface_show_num_files (const int num)\n{\n\tchar str[20];\n\n\tsnprintf (str, sizeof(str), \"Files: %d\", num);\n\tiface_set_status (str);\n}\n\n/* Change the content of the directory menu to these files, directories, and\n * playlists. */\nvoid iface_set_dir_content (const enum iface_menu iface_menu,\n\t\tconst struct plist *files, const lists_t_strs *dirs,\n\t\tconst lists_t_strs *playlists)\n{\n\tmain_win_set_dir_content (&main_win, iface_menu, files, dirs,\n\t\t\tplaylists);\n\tinfo_win_set_files_time (&info_win,\n\t\t\tmain_win_get_files_time(&main_win, iface_menu),\n\t\t\tmain_win_is_time_for_all(&main_win, iface_menu));\n\n\tiface_show_num_files (plist_count(files)\n\t\t\t+ (dirs ? lists_strs_size (dirs) : 0)\n\t\t\t+ (playlists ? lists_strs_size (playlists) : 0));\n\n\tiface_refresh_screen ();\n}\n\n/* Refreshes all menu structs with updated theme attributes. */\nvoid iface_update_attrs ()\n{\n\tsize_t ix;\n\n\tinfo_win.mixer_bar.fill_color = get_color (CLR_MIXER_BAR_FILL);\n\tinfo_win.mixer_bar.empty_color = get_color (CLR_MIXER_BAR_EMPTY);\n\tinfo_win.time_bar.fill_color = get_color (CLR_TIME_BAR_FILL);\n\tinfo_win.time_bar.empty_color = get_color (CLR_TIME_BAR_EMPTY);\n\n\tfor (ix = 0; ix < ARRAY_SIZE(main_win.menus); ix += 1) {\n\t\tint item_num;\n\t\tstruct side_menu *m = &main_win.menus[ix];\n\t\tstruct menu *menu = m->menu.list.main;\n\t\tstruct menu_item *mi;\n\n\t\tif (m->type == MENU_DIR || m->type == MENU_PLAYLIST) {\n\t\t\tmenu_set_info_attr_normal (menu, get_color (CLR_MENU_ITEM_INFO));\n\t\t\tmenu_set_info_attr_sel (menu, get_color (CLR_MENU_ITEM_INFO_SELECTED));\n\t\t\tmenu_set_info_attr_marked (menu, get_color (CLR_MENU_ITEM_INFO_MARKED));\n\t\t\tmenu_set_info_attr_sel_marked (menu, get_color (CLR_MENU_ITEM_INFO_MARKED_SELECTED));\n\n\t\t\tfor (mi = menu->items, item_num = 0;\n\t\t\t     mi && item_num < menu->nitems;\n\t\t\t     mi = mi->next, item_num += 1) {\n\t\t\t\tif (mi->type == F_DIR) {\n\t\t\t\t\tmenu_item_set_attr_normal (mi, get_color (CLR_MENU_ITEM_DIR));\n\t\t\t\t\tmenu_item_set_attr_sel (mi, get_color (CLR_MENU_ITEM_DIR_SELECTED));\n\t\t\t\t}\n\t\t\t\telse if (mi->type == F_PLAYLIST) {\n\t\t\t\t\tmenu_item_set_attr_normal (mi, get_color (CLR_MENU_ITEM_PLAYLIST));\n\t\t\t\t\tmenu_item_set_attr_sel (mi, get_color (CLR_MENU_ITEM_PLAYLIST_SELECTED));\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tmenu_item_set_attr_normal (mi, get_color (CLR_MENU_ITEM_FILE));\n\t\t\t\t\tmenu_item_set_attr_sel (mi, get_color (CLR_MENU_ITEM_FILE_SELECTED));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tmenu_set_info_attr_normal (menu, get_color (CLR_MENU_ITEM_FILE));\n\t\t\tmenu_set_info_attr_sel (menu, get_color (CLR_MENU_ITEM_FILE_SELECTED));\n\n\t\t\tfor (mi = menu->items, item_num = 0;\n\t\t\t     mi && item_num < menu->nitems;\n\t\t\t     mi = mi->next, item_num += 1) {\n\t\t\t\tmenu_item_set_attr_normal (mi, get_color (CLR_MENU_ITEM_FILE));\n\t\t\t\tmenu_item_set_attr_sel (mi, get_color (CLR_MENU_ITEM_FILE_SELECTED));\n\t\t\t}\n\t\t}\n\t}\n}\n\nvoid iface_update_theme_selection (const char *file)\n{\n    /* menus[2] is theme menu. */\n    assert (main_win.menus[2].menu.list.main->selected);\n\n    menu_setcurritem_file (main_win.menus[2].menu.list.main, file);\n}\n\n/* Like iface_set_dir_content(), but before replacing the menu content, save\n * the menu state (selected file, view position) and restore it after making\n * a new menu. */\nvoid iface_update_dir_content (const enum iface_menu iface_menu,\n\t\tconst struct plist *files, const lists_t_strs *dirs,\n\t\tconst lists_t_strs *playlists)\n{\n\tmain_win_update_dir_content (&main_win, iface_menu, files, dirs,\n\t\t\tplaylists);\n\tinfo_win_set_files_time (&info_win,\n\t\t\tmain_win_get_files_time(&main_win, iface_menu),\n\t\t\tmain_win_is_time_for_all(&main_win, iface_menu));\n\n\tiface_show_num_files (plist_count(files)\n\t\t\t+ (dirs ? lists_strs_size (dirs) : 0)\n\t\t\t+ (playlists ? lists_strs_size (playlists) : 0));\n\n\tiface_refresh_screen ();\n}\n\n/* Update item title and time in the menu. */\nvoid iface_update_item (const enum iface_menu menu,\n\t\tconst struct plist *plist, const int n)\n{\n\tassert (plist != NULL);\n\n\tmain_win_update_item (&main_win, menu, plist, n);\n\tinfo_win_set_files_time (&info_win,\n\t\t\tmain_win_get_curr_files_time(&main_win),\n\t\t\tmain_win_is_curr_time_for_all(&main_win));\n\tiface_refresh_screen ();\n}\n\n/* Change the current item in the directory menu to this item. */\nvoid iface_set_curr_item_title (const char *title)\n{\n\tassert (title != NULL);\n\n\tmain_win_set_curr_item_title (&main_win, title);\n\tiface_refresh_screen ();\n}\n\n/* Set the title for the directory menu. */\nvoid iface_set_title (const enum iface_menu menu, const char *title)\n{\n\n\tassert (title != NULL);\n\n    if (options_get_bool (\"FileNamesIconv\"))\n    {\n        char *conv_title = NULL;\n        conv_title = files_iconv_str (title);\n\n        main_win_set_title (&main_win,\n                menu == IFACE_MENU_DIR ? MENU_DIR : MENU_PLAYLIST,\n                conv_title);\n\n        free (conv_title);\n    }\n    else\n    {\n        main_win_set_title (&main_win,\n                menu == IFACE_MENU_DIR ? MENU_DIR : MENU_PLAYLIST,\n                title);\n    }\n\tiface_refresh_screen ();\n}\n\n/* Get the char code from the user with meta flag set if necessary. */\nvoid iface_get_key (struct iface_key *k)\n{\n\twint_t ch;\n\n\tch = wgetch (main_win.win);\n\tif (ch == (wint_t)ERR)\n\t\tinterface_fatal (\"wgetch() failed!\");\n\n\t/* Handle keypad ENTER as newline. */\n\tif (ch == KEY_ENTER)\n\t\tch = '\\n';\n\n\tif (ch < 32 && ch != '\\n' && ch != '\\t' && ch != KEY_ESCAPE) {\n\t\t/* Unprintable, generally control sequences */\n\t\tk->type = IFACE_KEY_FUNCTION;\n\t\tk->key.func = ch;\n\t}\n\telse if (ch == 0x7f) {\n\t\t/* Workaround for backspace on many terminals */\n\t\tk->type = IFACE_KEY_FUNCTION;\n\t\tk->key.func = KEY_BACKSPACE;\n\t}\n\telse if (ch < 255) {\n\t\t/* Regular char */\n\t\tint meta;\n\n#ifdef HAVE_NCURSESW\n\t\tungetch (ch);\n\t\tif (wget_wch(main_win.win, &ch) == ERR)\n\t\t\tinterface_fatal (\"wget_wch() failed!\");\n#endif\n\n\t\t/* Recognize meta sequences */\n\t\tif (ch == KEY_ESCAPE) {\n\t\t\tmeta = wgetch (main_win.win);\n\t\t\tif (meta != ERR)\n\t\t\t\tch = meta | META_KEY_FLAG;\n\t\t\tk->type = IFACE_KEY_FUNCTION;\n\t\t\tk->key.func = ch;\n\t\t}\n\t\telse {\n\t\t\tk->type = IFACE_KEY_CHAR;\n\t\t\tk->key.ucs = ch;\n\t\t}\n\t}\n\telse {\n\t\tk->type = IFACE_KEY_FUNCTION;\n\t\tk->key.func = ch;\n\t}\n}\n\n/* Return a non zero value if the key is not a real key - KEY_RESIZE. */\nint iface_key_is_resize (const struct iface_key *k)\n{\n\treturn k->type == IFACE_KEY_FUNCTION && k->key.func == KEY_RESIZE;\n}\n\n/* Handle a key command for the menu. */\nvoid iface_menu_key (const enum key_cmd cmd)\n{\n\tmain_win_menu_cmd (&main_win, cmd);\n\tiface_refresh_screen ();\n}\n\n/* Get the type of the currently selected item. */\nenum file_type iface_curritem_get_type ()\n{\n\treturn main_win_curritem_get_type (&main_win);\n}\n\n/* Return a non zero value if a directory menu is currently selected. */\nint iface_in_dir_menu ()\n{\n\treturn main_win_in_dir_menu (&main_win);\n}\n\n/* Return a non zero value if the playlist menu is currently selected. */\nint iface_in_plist_menu ()\n{\n\treturn main_win_in_plist_menu (&main_win);\n}\n\n/* Return a non zero value if the theme menu is currently selected. */\nint iface_in_theme_menu ()\n{\n\treturn main_win_in_theme_menu (&main_win);\n}\n\n/* Return the currently selected file (malloc()ed) or NULL if the menu is\n * empty. */\nchar *iface_get_curr_file ()\n{\n\treturn main_win_get_curr_file (&main_win);\n}\n\n/* Set the current time of playing. */\nvoid iface_set_curr_time (const int time)\n{\n\tinfo_win_set_curr_time (&info_win, time);\n\tiface_refresh_screen ();\n}\n\n/* Set the total time for the currently played file. */\nvoid iface_set_total_time (const int time)\n{\n\tinfo_win_set_total_time (&info_win, time);\n\tinfo_win_set_block (&info_win, -1, -1);\n\tiface_refresh_screen ();\n}\n\n/* Set the start and end marks for the currently played file. */\nvoid iface_set_block (const int block_start, const int block_end)\n{\n\tinfo_win_set_block (&info_win, block_start, block_end);\n\tiface_refresh_screen ();\n}\n\n/* Set the state (STATE_(PLAY|STOP|PAUSE)). */\nvoid iface_set_state (const int state)\n{\n\tinfo_win_set_state (&info_win, state);\n\tiface_refresh_screen ();\n}\n\n/* Set the bitrate (in kbps). 0 or -1 means no bitrate information. */\nvoid iface_set_bitrate (const int bitrate)\n{\n\tassert (bitrate >= -1);\n\n\tinfo_win_set_bitrate (&info_win, bitrate);\n\tiface_refresh_screen ();\n}\n\n/* Set the rate (in kHz). 0 or -1 means no rate information. */\nvoid iface_set_rate (const int rate)\n{\n\tassert (rate >= -1);\n\n\tinfo_win_set_rate (&info_win, rate);\n\tiface_refresh_screen ();\n}\n\n/* Set the number of channels. */\nvoid iface_set_channels (const int channels)\n{\n\tassert (channels == 1 || channels == 2);\n\n\tinfo_win_set_channels (&info_win, channels);\n\tiface_refresh_screen ();\n}\n\n/* Set the currently played file. If file is NULL, nothing is played. */\nvoid iface_set_played_file (const char *file)\n{\n\tmain_win_set_played_file (&main_win, file);\n\n\tif (!file) {\n\t\tinfo_win_set_played_title (&info_win, NULL);\n\t\tinfo_win_set_bitrate (&info_win, -1);\n\t\tinfo_win_set_rate (&info_win, -1);\n\t\tinfo_win_set_curr_time (&info_win, -1);\n\t\tinfo_win_set_total_time (&info_win, -1);\n\t\tinfo_win_set_block (&info_win, -1, -1);\n\t\tinfo_win_set_option_state (&info_win, \"Net\", 0);\n\t}\n\telse if (is_url(file)) {\n\t\tinfo_win_set_option_state (&info_win, \"Net\", 1);\n\t}\n\n\tiface_refresh_screen ();\n}\n\n/* Set the title for the currently played file. */\nvoid iface_set_played_file_title (const char *title)\n{\n\tassert (title != NULL);\n\n\tinfo_win_set_played_title (&info_win, title);\n\tiface_refresh_screen ();\n}\n\n/* Update timeouts, refresh the screen if needed. This should be called at\n * least once a second. */\nvoid iface_tick ()\n{\n\tinfo_win_tick (&info_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_set_mixer_value (const int value)\n{\n\tassert (value >= 0);\n\n\tinfo_win_set_mixer_value (&info_win, value);\n\tiface_refresh_screen ();\n}\n\n/* Switch to the playlist menu. */\nvoid iface_switch_to_plist ()\n{\n\tmain_win_switch_to (&main_win, MENU_PLAYLIST);\n\tinfo_win_set_files_time (&info_win,\n\t\t\tmain_win_get_curr_files_time(&main_win),\n\t\t\tmain_win_is_curr_time_for_all(&main_win));\n\n\tiface_refresh_screen ();\n}\n\n/* Switch to the directory menu. */\nvoid iface_switch_to_dir ()\n{\n\tmain_win_switch_to (&main_win, MENU_DIR);\n\tinfo_win_set_files_time (&info_win,\n\t\t\tmain_win_get_curr_files_time(&main_win),\n\t\t\tmain_win_is_curr_time_for_all(&main_win));\n\n\tiface_refresh_screen ();\n}\n\n/* Add the item from the playlist to the playlist menu. */\nvoid iface_add_to_plist (const struct plist *plist, const int num)\n{\n\tassert (plist != NULL);\n\n\tmain_win_add_to_plist (&main_win, plist, num);\n\tinfo_win_set_files_time (&info_win,\n\t\t\tmain_win_get_curr_files_time(&main_win),\n\t\t\tmain_win_is_curr_time_for_all(&main_win));\n\n\tiface_show_num_files (plist_count(plist));\n\n\tiface_refresh_screen ();\n}\n\n/* Display an error message. */\nvoid iface_error (const char *msg)\n{\n\tif (iface_initialized) {\n\t\tinfo_win_msg (&info_win, msg, ERROR_MSG, NULL, NULL, NULL);\n\t\tiface_refresh_screen ();\n\t}\n\telse\n\t\tfprintf (stderr, \"ERROR: %s\", msg);\n}\n\n/* Handle screen resizing. */\nvoid iface_resize ()\n{\n\tendwin ();\n\trefresh ();\n\tcheck_term_size (&main_win, &info_win);\n\tvalidate_layouts ();\n\tmain_win_resize (&main_win);\n\tinfo_win_resize (&info_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_refresh ()\n{\n\twclear (main_win.win);\n\twclear (info_win.win);\n\n\tmain_win_draw (&main_win);\n\tinfo_win_draw (&info_win);\n\n\tiface_refresh_screen ();\n}\n\nvoid iface_update_show_time ()\n{\n\tmain_win_update_show_time (&main_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_update_show_format ()\n{\n\tmain_win_update_show_format (&main_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_clear_plist ()\n{\n\tmain_win_clear_plist (&main_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_del_plist_item (const char *file)\n{\n\tassert (file != NULL);\n\n\tmain_win_del_plist_item (&main_win, file);\n\tinfo_win_set_files_time (&info_win,\n\t\t\tmain_win_get_curr_files_time(&main_win),\n\t\t\tmain_win_is_curr_time_for_all(&main_win));\n\tiface_refresh_screen ();\n}\n\nvoid iface_make_entry (const enum entry_type type)\n{\n\tinfo_win_make_entry (&info_win, type);\n\tiface_refresh_screen ();\n}\n\nenum entry_type iface_get_entry_type ()\n{\n\treturn info_win_get_entry_type (&info_win);\n}\n\nint iface_in_entry ()\n{\n\treturn info_win_in_entry (&info_win);\n}\n\nvoid iface_entry_handle_key (const struct iface_key *k)\n{\n\tinfo_win_entry_handle_key (&info_win, &main_win, k);\n\tiface_refresh_screen ();\n}\n\nvoid iface_entry_set_text (const char *text)\n{\n\tassert (text != NULL);\n\n\tinfo_win_entry_set_text (&info_win, text);\n\tiface_refresh_screen ();\n}\n\n/* Get text from the entry. Returned memory is malloc()ed. */\nchar *iface_entry_get_text ()\n{\n\treturn info_win_entry_get_text (&info_win);\n}\n\nvoid iface_entry_history_add ()\n{\n\tinfo_win_entry_history_add (&info_win);\n}\n\nvoid iface_entry_disable ()\n{\n\tif (iface_get_entry_type() == ENTRY_SEARCH)\n\t\tmain_win_clear_filter_menu (&main_win);\n\tinfo_win_entry_disable (&info_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_entry_set_file (const char *file)\n{\n\tassert (file != NULL);\n\n\tinfo_win_entry_set_file (&info_win, file);\n}\n\n/* Returned memory is malloc()ed. */\nchar *iface_entry_get_file ()\n{\n\treturn info_win_entry_get_file (&info_win);\n}\n\nvoid iface_message (const char *msg)\n{\n\tassert (msg != NULL);\n\n\tinfo_win_msg (&info_win, msg, NORMAL_MSG, NULL, NULL, NULL);\n\tiface_refresh_screen ();\n}\n\nvoid iface_disable_message ()\n{\n\tinfo_win_disable_msg (&info_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_user_query (const char *msg, const char *prompt,\n                       t_user_reply_callback *callback, void *data)\n{\n\tassert (prompt != NULL);\n\n\tinfo_win_msg (&info_win, msg, QUERY_MSG, prompt, callback, data);\n\tiface_refresh_screen ();\n}\n\nvoid iface_user_reply (const char *reply)\n{\n\tinfo_win_disable_msg (&info_win);\n\tiface_win_user_reply (&info_win, reply);\n}\n\nvoid iface_user_history_add (const char *text)\n{\n\tinfo_win_user_history_add (&info_win, text);\n}\n\nvoid iface_plist_set_total_time (const int time, const int for_all_files)\n{\n\tif (iface_in_plist_menu())\n\t\tinfo_win_set_files_time (&info_win, time, for_all_files);\n\tmain_win_set_plist_time (&main_win, time, for_all_files);\n\tiface_refresh_screen ();\n}\n\nvoid iface_select_file (const char *file)\n{\n\tassert (file != NULL);\n\n\tmain_win_select_file (&main_win, file);\n\tiface_refresh_screen ();\n}\n\nint iface_in_help ()\n{\n\treturn main_win_in_help (&main_win);\n}\n\nvoid iface_switch_to_help ()\n{\n\tmain_win_switch_to_help (&main_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_handle_help_key (const struct iface_key *k)\n{\n\tmain_win_handle_help_key (&main_win, k);\n\tiface_refresh_screen ();\n}\n\nint iface_in_lyrics ()\n{\n\treturn main_win_in_lyrics (&main_win);\n}\n\nvoid iface_switch_to_lyrics ()\n{\n\tmain_win_switch_to_lyrics (&main_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_handle_lyrics_key (const struct iface_key *k)\n{\n\tmain_win_handle_lyrics_key (&main_win, k);\n\tiface_refresh_screen ();\n}\n\nvoid iface_toggle_layout ()\n{\n\tstatic int curr_layout = 1;\n\tchar layout_option[32];\n\tlists_t_strs *layout_fmt;\n\n\tif (++curr_layout > 3)\n\t\tcurr_layout = 1;\n\n\tsprintf (layout_option, \"Layout%d\", curr_layout);\n\tlayout_fmt = options_get_list (layout_option);\n\tif (lists_strs_empty (layout_fmt)) {\n\t\tcurr_layout = 1;\n\t\tlayout_fmt = options_get_list (\"Layout1\");\n\t}\n\n\tmain_win_use_layout (&main_win, layout_fmt);\n\tiface_refresh_screen ();\n}\n\nvoid iface_toggle_percent ()\n{\n\tinfo_win.time_bar.show_pct = !info_win.time_bar.show_pct;\n\tbar_update_title (&info_win.time_bar);\n\tinfo_win_draw_block (&info_win);\n\tiface_refresh_screen ();\n}\n\nvoid iface_swap_plist_items (const char *file1, const char *file2)\n{\n\tmain_win_swap_plist_items (&main_win, file1, file2);\n\tiface_refresh_screen ();\n}\n\n/* Make sure that this file in this menu is visible. */\nvoid iface_make_visible (const enum iface_menu menu, const char *file)\n{\n\tassert (file != NULL);\n\n\tmain_win_make_visible (&main_win,\n\t\t\tmenu == IFACE_MENU_DIR ? MENU_DIR : MENU_PLAYLIST,\n\t\t\tfile);\n\tiface_refresh_screen ();\n}\n\nvoid iface_switch_to_theme_menu ()\n{\n\tmain_win_create_themes_menu (&main_win);\n\tmain_win_switch_to (&main_win, MENU_THEMES);\n\tiface_refresh_screen ();\n}\n\n/* Add a file to the current menu. */\nvoid iface_add_file (const char *file, const char *title,\n\t\tconst enum file_type type)\n{\n\tassert (file != NULL);\n\tassert (file != NULL);\n\n\tmain_win_add_file (&main_win, file, title, type);\n\tiface_refresh_screen ();\n}\n\n/* Temporary exit the interface (ncurses mode). */\nvoid iface_temporary_exit ()\n{\n\tendwin ();\n}\n\n/* Restore the interface after iface_temporary_exit(). */\nvoid iface_restore ()\n{\n\tiface_refresh ();\n\tif (!options_get_bool(\"UseCursorSelection\"))\n\t\tcurs_set (0);\n}\n\nvoid iface_load_lyrics (const char *file)\n{\n\tlyrics_cleanup ();\n\tlyrics_autoload (file);\n\tmain_win.lyrics_screen_top = 0;\n\tmain_win_draw (&main_win);\n}\n\nstatic void update_queue_position (struct plist *playlist,\n\t\tstruct plist *dir_list, const char *file, const int pos)\n{\n\tint i;\n\n\tassert (file != NULL);\n\tassert (pos >= 0);\n\n\tif (playlist && (i = plist_find_fname(playlist, file)) != -1) {\n\t\tplaylist->items[i].queue_pos = pos;\n\t\tmain_win_update_item (&main_win, IFACE_MENU_PLIST,\n\t\t\t\tplaylist, i);\n\t}\n\n\tif (dir_list && (i = plist_find_fname(dir_list, file)) != -1) {\n\t\tdir_list->items[i].queue_pos = pos;\n\t\tmain_win_update_item (&main_win, IFACE_MENU_DIR,\n\t\t\t\tdir_list, i);\n\t}\n}\n\n/* Update queue positions in the playlist and directory menus. Only those items\n * which are in the queue (and whose position has therefore changed are\n * updated. One exception is the item which was deleted from the queue --\n * this one can be passed as the deleted_file parameter */\nvoid iface_update_queue_positions (const struct plist *queue,\n\t\tstruct plist *playlist, struct plist *dir_list,\n\t\tconst char *deleted_file)\n{\n\tint i;\n\tint pos = 1;\n\n\tassert (queue != NULL);\n\n\tfor (i = 0; i < queue->num; i++) {\n\t\tif (!plist_deleted(queue,i)) {\n\t\t\tupdate_queue_position (playlist, dir_list,\n\t\t\t\t\tqueue->items[i].file, pos);\n\t\t\tpos++;\n\t\t}\n\t}\n\n\tif (deleted_file)\n\t\tupdate_queue_position (playlist, dir_list, deleted_file, 0);\n\n\tiface_refresh_screen ();\n}\n\n/* Clear the queue -- zero the queue positions in playlist and directory\n * menus. */\nvoid iface_clear_queue_positions (const struct plist *queue,\n\t\tstruct plist *playlist, struct plist *dir_list)\n{\n\tint i;\n\n\tassert (queue != NULL);\n\tassert (playlist != NULL);\n\tassert (dir_list != NULL);\n\n\tfor (i = 0; i < queue->num; i++) {\n\t\tif (!plist_deleted(queue,i)) {\n\t\t\tupdate_queue_position (playlist, dir_list,\n\t\t\t\t\tqueue->items[i].file, 0);\n\t\t}\n\t}\n\n\tiface_refresh_screen ();\n}\n\nvoid iface_update_queue_position_last (const struct plist *queue,\n\t\tstruct plist *playlist, struct plist *dir_list)\n{\n\tint i;\n\tint pos;\n\n\tassert (queue != NULL);\n\n\ti = plist_last (queue);\n\tpos = plist_get_position (queue, i);\n\tupdate_queue_position (playlist, dir_list, queue->items[i].file, pos);\n\tiface_refresh_screen ();\n}\n"
  },
  {
    "path": "interface_elements.h",
    "content": "#ifndef INTERFACE_ELEMENTS_H\n#define INTERFACE_ELEMENTS_H\n\n#if defined HAVE_NCURSESW_CURSES_H\n# include <ncursesw/curses.h>\n#elif defined HAVE_NCURSESW_H\n# include <ncursesw.h>\n#elif defined HAVE_NCURSES_CURSES_H\n# include <ncurses/curses.h>\n#elif defined HAVE_NCURSES_H\n# include <ncurses.h>\n#elif defined HAVE_CURSES_H\n# include <curses.h>\n#endif\n\n#include <wctype.h>\n#include <wchar.h>\n\n#include \"lists.h\"\n#include \"files.h\"\n#include \"keys.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* Interface's menus */\nenum iface_menu\n{\n\tIFACE_MENU_PLIST,\n\tIFACE_MENU_DIR\n};\n\ntypedef void t_user_reply_callback (const char *reply, void *data);\n\nenum entry_type\n{\n\tENTRY_SEARCH,\n\tENTRY_PLIST_SAVE,\n\tENTRY_GO_DIR,\n\tENTRY_GO_URL,\n\tENTRY_ADD_URL,\n\tENTRY_PLIST_OVERWRITE,\n\tENTRY_USER_QUERY\n};\n\nstruct iface_key\n{\n\t/* Type of the key */\n\tenum\n\t{\n\t\tIFACE_KEY_CHAR,\t    /* Regular char */\n\t\tIFACE_KEY_FUNCTION  /* Function key (arrow, F12, etc.) */\n\t} type;\n\n\tunion {\n\t\twchar_t ucs;        /* IFACE_KEY_CHAR */\n\t\tint func;           /* IFACE_KEY_FUNCTION */\n\t} key;\n};\n\nvoid windows_init ();\nvoid windows_reset ();\nvoid windows_end ();\nvoid iface_set_option_state (const char *name, const bool value);\nvoid iface_set_mixer_name (const char *name);\nvoid iface_set_status (const char *msg);\nvoid iface_set_dir_content (const enum iface_menu iface_menu,\n\t\tconst struct plist *files,\n\t\tconst lists_t_strs *dirs,\n\t\tconst lists_t_strs *playlists);\nvoid iface_update_dir_content (const enum iface_menu iface_menu,\n\t\tconst struct plist *files,\n\t\tconst lists_t_strs *dirs,\n\t\tconst lists_t_strs *playlists);\nvoid iface_set_curr_item_title (const char *title);\nvoid iface_get_key (struct iface_key *k);\nint iface_key_is_resize (const struct iface_key *k);\nvoid iface_menu_key (const enum key_cmd cmd);\nenum file_type iface_curritem_get_type ();\nint iface_in_dir_menu ();\nint iface_in_plist_menu ();\nint iface_in_theme_menu ();\nchar *iface_get_curr_file ();\nvoid iface_update_item (const enum iface_menu menu, const struct plist *plist,\n\t\tconst int n);\nvoid iface_set_curr_time (const int time);\nvoid iface_set_total_time (const int time);\nvoid iface_set_block (const int start_time, const int end_time);\nvoid iface_set_state (const int state);\nvoid iface_set_bitrate (const int bitrate);\nvoid iface_set_rate (const int rate);\nvoid iface_set_channels (const int channels);\nvoid iface_set_played_file (const char *file);\nvoid iface_set_played_file_title (const char *title);\nvoid iface_set_mixer_value (const int value);\nvoid iface_set_files_in_queue (const int num);\nvoid iface_tick ();\nvoid iface_switch_to_plist ();\nvoid iface_switch_to_dir ();\nvoid iface_add_to_plist (const struct plist *plist, const int num);\nvoid iface_error (const char *msg);\nvoid iface_resize ();\nvoid iface_refresh ();\nvoid iface_update_show_time ();\nvoid iface_update_show_format ();\nvoid iface_clear_plist ();\nvoid iface_del_plist_item (const char *file);\nenum entry_type iface_get_entry_type ();\nint iface_in_entry ();\nvoid iface_make_entry (const enum entry_type type);\nvoid iface_entry_handle_key (const struct iface_key *k);\nvoid iface_entry_set_text (const char *text);\nchar *iface_entry_get_text ();\nvoid iface_entry_history_add ();\nvoid iface_entry_disable ();\nvoid iface_entry_set_file (const char *file);\nchar *iface_entry_get_file ();\nvoid iface_message (const char *msg);\nvoid iface_disable_message ();\nvoid iface_user_query (const char *msg, const char *prompt, t_user_reply_callback *callback, void *data);\nvoid iface_user_reply (const char *reply);\nvoid iface_user_history_add (const char *text);\nvoid iface_plist_set_total_time (const int time, const int for_all_files);\nvoid iface_set_title (const enum iface_menu menu, const char *title);\nvoid iface_select_file (const char *file);\nint iface_in_help ();\nvoid iface_switch_to_help ();\nvoid iface_handle_help_key (const struct iface_key *k);\nint iface_in_lyrics ();\nvoid iface_switch_to_lyrics ();\nvoid iface_handle_lyrics_key (const struct iface_key *k);\nvoid iface_toggle_layout ();\nvoid iface_toggle_percent ();\nvoid iface_swap_plist_items (const char *file1, const char *file2);\nvoid iface_make_visible (const enum iface_menu menu, const char *file);\nvoid iface_switch_to_theme_menu ();\nvoid iface_add_file (const char *file, const char *title,\n\t\tconst enum file_type type);\nvoid iface_temporary_exit ();\nvoid iface_restore ();\nvoid iface_load_lyrics (const char *file);\nvoid iface_update_queue_positions (const struct plist *queue,\n\t\tstruct plist *playlist, struct plist *dir_list,\n\t\tconst char *deleted_file);\nvoid iface_clear_queue_positions (const struct plist *queue,\n\t\tstruct plist *playlist, struct plist *dir_list);\nvoid iface_update_queue_position_last (const struct plist *queue,\n\t\tstruct plist *playlist, struct plist *dir_list);\nvoid iface_update_attrs ();\nvoid iface_update_theme_selection (const char *file);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "io.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* TODO:\n * - handle SIGBUS (mmap() read error)\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdlib.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <string.h>\n#include <strings.h>\n#include <assert.h>\n#include <pthread.h>\n#include <inttypes.h>\n\n#ifdef HAVE_MMAP\n# include <sys/mman.h>\n#endif\n\n/*#define DEBUG*/\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"io.h\"\n#include \"options.h\"\n#include \"files.h\"\n#ifdef HAVE_CURL\n# include \"io_curl.h\"\n#endif\n\n#ifdef HAVE_CURL\n# define CURL_ONLY\n#else\n# define CURL_ONLY ATTR_UNUSED\n#endif\n\n#ifdef HAVE_MMAP\nstatic void *io_mmap_file (const struct io_stream *s)\n{\n\tvoid *result = NULL;\n\n\tdo {\n\t\tif (s->size < 1 || (uint64_t)s->size > SIZE_MAX) {\n\t\t\tlogit (\"File size unsuitable for mmap()\");\n\t\t\tbreak;\n\t\t}\n\n\t\tconst size_t sz = (size_t)s->size;\n\n\t\tresult = mmap (0, sz, PROT_READ, MAP_SHARED, s->fd, 0);\n\t\tif (result == MAP_FAILED) {\n\t\t\tlog_errno (\"mmap() failed\", errno);\n\t\t\tresult = NULL;\n\t\t\tbreak;\n\t\t}\n\n\t\tlogit (\"mmap()ed %zu bytes\", sz);\n\t} while (0);\n\n\treturn result;\n}\n#endif\n\n#ifdef HAVE_MMAP\nstatic ssize_t io_read_mmap (struct io_stream *s, const int dont_move,\n\t\tvoid *buf, size_t count)\n{\n\tstruct stat file_stat;\n\tsize_t to_read;\n\n\tassert (s->mem != NULL);\n\n\tif (fstat (s->fd, &file_stat) == -1) {\n\t\tlog_errno (\"fstat() failed\", errno);\n\t\treturn -1;\n\t}\n\n\tif (s->size != file_stat.st_size) {\n\t\tlogit (\"File size has changed\");\n\n\t\tif (munmap (s->mem, (size_t)s->size)) {\n\t\t\tlog_errno (\"munmap() failed\", errno);\n\t\t\treturn -1;\n\t\t}\n\n\t\ts->size = file_stat.st_size;\n\t\ts->mem = io_mmap_file (s);\n\t\tif (!s->mem)\n\t\t\treturn -1;\n\n\t\tif (s->mem_pos > s->size)\n\t\t\tlogit (\"File shrunk\");\n\t}\n\n\tif (s->mem_pos >= s->size)\n\t\treturn 0;\n\n\tto_read = MIN(count, (size_t) (s->size - s->mem_pos));\n\tmemcpy (buf, (char *)s->mem + s->mem_pos, to_read);\n\n\tif (!dont_move)\n\t\ts->mem_pos += to_read;\n\n\treturn to_read;\n}\n#endif\n\nstatic ssize_t io_read_fd (struct io_stream *s, const int dont_move, void *buf,\n\t\tsize_t count)\n{\n\tssize_t res;\n\n\tres = read (s->fd, buf, count);\n\n\tif (res < 0)\n\t\treturn -1;\n\n\tif (dont_move && lseek(s->fd, -res, SEEK_CUR) < 0)\n\t\treturn -1;\n\n\treturn res;\n}\n\n/* Read the data from the stream resource.  If dont_move was set, the stream\n * position is unchanged. */\nstatic ssize_t io_internal_read (struct io_stream *s, const int dont_move,\n\t\tchar *buf, size_t count)\n{\n\tssize_t res = 0;\n\n\tassert (s != NULL);\n\tassert (buf != NULL);\n\n\tswitch (s->source) {\n\tcase IO_SOURCE_FD:\n\t\tres = io_read_fd (s, dont_move, buf, count);\n\t\tbreak;\n#ifdef HAVE_MMAP\n\tcase IO_SOURCE_MMAP:\n\t\tres = io_read_mmap (s, dont_move, buf, count);\n\t\tbreak;\n#endif\n#ifdef HAVE_CURL\n\tcase IO_SOURCE_CURL:\n\t\tif (dont_move)\n\t\t\tfatal (\"You can't peek data directly from CURL!\");\n\t\tres = io_curl_read (s, buf, count);\n\t\tbreak;\n#endif\n\tdefault:\n\t\tfatal (\"Unknown io_stream->source: %d\", s->source);\n\t}\n\n\treturn res;\n}\n\n#ifdef HAVE_MMAP\nstatic off_t io_seek_mmap (struct io_stream *s, const off_t where)\n{\n\treturn (s->mem_pos = where);\n}\n#endif\n\nstatic off_t io_seek_fd (struct io_stream *s, const off_t where)\n{\n\treturn lseek (s->fd, where, SEEK_SET);\n}\n\nstatic off_t io_seek_buffered (struct io_stream *s, const off_t where)\n{\n\toff_t res = -1;\n\n\tassert (s->source != IO_SOURCE_CURL);\n\n\tlogit (\"Seeking...\");\n\n\tswitch (s->source) {\n\tcase IO_SOURCE_FD:\n\t\tres = io_seek_fd (s, where);\n\t\tbreak;\n#ifdef HAVE_MMAP\n\tcase IO_SOURCE_MMAP:\n\t\tres = io_seek_mmap (s, where);\n\t\tbreak;\n#endif\n\tdefault:\n\t\tfatal (\"Unknown io_stream->source: %d\", s->source);\n\t}\n\n\tLOCK (s->buf_mtx);\n\tfifo_buf_clear (s->buf);\n\tpthread_cond_signal (&s->buf_free_cond);\n\ts->after_seek = 1;\n\ts->eof = 0;\n\tUNLOCK (s->buf_mtx);\n\n\treturn res;\n}\n\nstatic off_t io_seek_unbuffered (struct io_stream *s, const off_t where)\n{\n\toff_t res = -1;\n\n\tassert (s->source != IO_SOURCE_CURL);\n\n\tswitch (s->source) {\n#ifdef HAVE_MMAP\n\tcase IO_SOURCE_MMAP:\n\t\tres = io_seek_mmap (s, where);\n\t\tbreak;\n#endif\n\tcase IO_SOURCE_FD:\n\t\tres = io_seek_fd (s, where);\n\t\tbreak;\n\tdefault:\n\t\tfatal (\"Unknown io_stream->source: %d\", s->source);\n\t}\n\n\treturn res;\n}\n\noff_t io_seek (struct io_stream *s, off_t offset, int whence)\n{\n\toff_t res, new_pos = 0;\n\n\tassert (s != NULL);\n\tassert (s->opened);\n\n\tif (s->source == IO_SOURCE_CURL || !io_ok(s))\n\t\treturn -1;\n\n\tLOCK (s->io_mtx);\n\tswitch (whence) {\n\tcase SEEK_SET:\n\t\tnew_pos = offset;\n\t\tbreak;\n\tcase SEEK_CUR:\n\t\tnew_pos = s->pos + offset;\n\t\tbreak;\n\tcase SEEK_END:\n\t\tnew_pos = s->size + offset;\n\t\tbreak;\n\tdefault:\n\t\tfatal (\"Bad whence value: %d\", whence);\n\t}\n\n\tnew_pos = CLAMP(0, new_pos, s->size);\n\n\tif (s->buffered)\n\t\tres = io_seek_buffered (s, new_pos);\n\telse\n\t\tres = io_seek_unbuffered (s, new_pos);\n\n\tif (res != -1)\n\t\ts->pos = res;\n\tUNLOCK (s->io_mtx);\n\n\tif (res != -1)\n\t\tdebug (\"Seek to: %\"PRId64, res);\n\telse\n\t\tlogit (\"Seek error\");\n\n\treturn res;\n}\n\n/* Wake up the IO reading thread. */\nstatic void io_wake_up (struct io_stream *s CURL_ONLY)\n{\n#ifdef HAVE_CURL\n\tif (s->source == IO_SOURCE_CURL)\n\t\tio_curl_wake_up (s);\n#endif\n}\n\n/* Abort an IO operation from another thread. */\nvoid io_abort (struct io_stream *s)\n{\n\tassert (s != NULL);\n\n\tif (s->buffered && !s->stop_read_thread) {\n\t\tlogit (\"Aborting...\");\n\t\tLOCK (s->buf_mtx);\n\t\ts->stop_read_thread = 1;\n\t\tio_wake_up (s);\n\t\tpthread_cond_broadcast (&s->buf_fill_cond);\n\t\tpthread_cond_broadcast (&s->buf_free_cond);\n\t\tUNLOCK (s->buf_mtx);\n\t\tlogit (\"done\");\n\t}\n}\n\n/* Close the stream and free all resources associated with it. */\nvoid io_close (struct io_stream *s)\n{\n\tint rc;\n\n\tassert (s != NULL);\n\n\tlogit (\"Closing stream...\");\n\n\tif (s->opened) {\n\t\tif (s->buffered) {\n\t\t\tio_abort (s);\n\n\t\t\tlogit (\"Waiting for io_read_thread()...\");\n\t\t\tpthread_join (s->read_thread, NULL);\n\t\t\tlogit (\"IO read thread exited\");\n\t\t}\n\n\t\tswitch (s->source) {\n\t\tcase IO_SOURCE_FD:\n\t\t\tclose (s->fd);\n\t\t\tbreak;\n#ifdef HAVE_MMAP\n\t\tcase IO_SOURCE_MMAP:\n\t\t\tif (s->mem && munmap (s->mem, (size_t)s->size))\n\t\t\t\tlog_errno (\"munmap() failed\", errno);\n\t\t\tclose (s->fd);\n\t\t\tbreak;\n#endif\n#ifdef HAVE_CURL\n\t\tcase IO_SOURCE_CURL:\n\t\t\tio_curl_close (s);\n\t\t\tbreak;\n#endif\n\t\tdefault:\n\t\t\tfatal (\"Unknown io_stream->source: %d\", s->source);\n\t\t}\n\n\t\ts->opened = 0;\n\n\t\tif (s->buffered) {\n\t\t\tfifo_buf_free (s->buf);\n\t\t\ts->buf = NULL;\n\t\t\trc = pthread_cond_destroy (&s->buf_free_cond);\n\t\t\tif (rc != 0)\n\t\t\t\tlog_errno (\"Destroying buf_free_cond failed\", rc);\n\t\t\trc = pthread_cond_destroy (&s->buf_fill_cond);\n\t\t\tif (rc != 0)\n\t\t\t\tlog_errno (\"Destroying buf_fill_cond failed\", rc);\n\t\t}\n\n\t\tif (s->metadata.title)\n\t\t\tfree (s->metadata.title);\n\t\tif (s->metadata.url)\n\t\t\tfree (s->metadata.url);\n\t}\n\n\trc = pthread_mutex_destroy (&s->buf_mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Destroying buf_mtx failed\", rc);\n\trc = pthread_mutex_destroy (&s->io_mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Destroying io_mtx failed\", rc);\n\trc = pthread_mutex_destroy (&s->metadata.mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Destroying metadata.mtx failed\", rc);\n\n\tif (s->strerror)\n\t\tfree (s->strerror);\n\tfree (s);\n\n\tlogit (\"done\");\n}\n\nstatic void *io_read_thread (void *data)\n{\n\tstruct io_stream *s = (struct io_stream *)data;\n\n\tlogit (\"IO read thread created\");\n\n\twhile (!s->stop_read_thread) {\n\t\tchar read_buf[8096];\n\t\tint read_buf_fill = 0;\n\t\tint read_buf_pos = 0;\n\n\t\tLOCK (s->io_mtx);\n\t\tdebug (\"Reading...\");\n\n\t\tLOCK (s->buf_mtx);\n\t\ts->after_seek = 0;\n\t\tUNLOCK (s->buf_mtx);\n\n\t\tread_buf_fill = io_internal_read (s, 0, read_buf, sizeof(read_buf));\n\t\tUNLOCK (s->io_mtx);\n\t\tif (read_buf_fill > 0)\n\t\t\tdebug (\"Read %d bytes\", read_buf_fill);\n\n\t\tLOCK (s->buf_mtx);\n\n\t\tif (s->stop_read_thread) {\n\t\t\tUNLOCK (s->buf_mtx);\n\t\t\tbreak;\n\t\t}\n\n\t\tif (read_buf_fill < 0) {\n\t\t\ts->errno_val = errno;\n\t\t\ts->read_error = 1;\n\t\t\tlogit (\"Exiting due to read error.\");\n\t\t\tpthread_cond_broadcast (&s->buf_fill_cond);\n\t\t\tUNLOCK (s->buf_mtx);\n\t\t\tbreak;\n\t\t}\n\n\t\tif (read_buf_fill == 0) {\n\t\t\ts->eof = 1;\n\t\t\tdebug (\"EOF, waiting\");\n\t\t\tpthread_cond_broadcast (&s->buf_fill_cond);\n\t\t\tpthread_cond_wait (&s->buf_free_cond, &s->buf_mtx);\n\t\t\tdebug (\"Got signal\");\n\t\t\tUNLOCK (s->buf_mtx);\n\t\t\tcontinue;\n\t\t}\n\n\t\ts->eof = 0;\n\n\t\twhile (read_buf_pos < read_buf_fill && !s->after_seek) {\n\t\t\tsize_t put;\n\n\t\t\tdebug (\"Buffer fill: %zu\", fifo_buf_get_fill (s->buf));\n\n\t\t\tput = fifo_buf_put (s->buf,\n\t\t\t\t\tread_buf + read_buf_pos,\n\t\t\t\t\tread_buf_fill - read_buf_pos);\n\n\t\t\tif (s->stop_read_thread)\n\t\t\t\tbreak;\n\n\t\t\tif (put > 0) {\n\t\t\t\tdebug (\"Put %zu bytes into the buffer\", put);\n\t\t\t\tif (s->buf_fill_callback) {\n\t\t\t\t\tUNLOCK (s->buf_mtx);\n\t\t\t\t\ts->buf_fill_callback (s,\n\t\t\t\t\t\tfifo_buf_get_fill (s->buf),\n\t\t\t\t\t\tfifo_buf_get_size (s->buf),\n\t\t\t\t\t\ts->buf_fill_callback_data);\n\t\t\t\t\tLOCK (s->buf_mtx);\n\t\t\t\t}\n\t\t\t\tpthread_cond_broadcast (&s->buf_fill_cond);\n\t\t\t\tread_buf_pos += put;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tdebug (\"The buffer is full, waiting.\");\n\t\t\tpthread_cond_wait (&s->buf_free_cond, &s->buf_mtx);\n\t\t\tdebug (\"Some space in the buffer was freed\");\n\t\t}\n\n\t\tUNLOCK (s->buf_mtx);\n\t}\n\n\tif (s->stop_read_thread)\n\t\tlogit (\"Stop request\");\n\n\tlogit (\"Exiting IO read thread\");\n\n\treturn NULL;\n}\n\nstatic void io_open_file (struct io_stream *s, const char *file)\n{\n\tstruct stat file_stat;\n\n\ts->source = IO_SOURCE_FD;\n\n\tdo {\n\t\ts->fd = open (file, O_RDONLY);\n\t\tif (s->fd == -1) {\n\t\t\ts->errno_val = errno;\n\t\t\tbreak;\n\t\t}\n\n\t\tif (fstat (s->fd, &file_stat) == -1) {\n\t\t\ts->errno_val = errno;\n\t\t\tclose (s->fd);\n\t\t\tbreak;\n\t\t}\n\n\t\ts->size = file_stat.st_size;\n\t\ts->opened = 1;\n\n#ifdef HAVE_MMAP\n\t\tif (!options_get_bool (\"UseMMap\")) {\n\t\t\tlogit (\"Not using mmap()\");\n\t\t\ts->mem = NULL;\n\t\t\tbreak;\n\t\t}\n\n\t\ts->mem = io_mmap_file (s);\n\t\tif (!s->mem)\n\t\t\tbreak;\n\n\t\ts->source = IO_SOURCE_MMAP;\n\t\ts->mem_pos = 0;\n#endif\n\t} while (0);\n}\n\n/* Open the file. */\nstruct io_stream *io_open (const char *file, const int buffered)\n{\n\tint rc;\n\tstruct io_stream *s;\n\n\tassert (file != NULL);\n\n\ts = xmalloc (sizeof(struct io_stream));\n\ts->errno_val = 0;\n\ts->read_error = 0;\n\ts->strerror = NULL;\n\ts->opened = 0;\n\ts->size = -1;\n\ts->buf_fill_callback = NULL;\n\tmemset (&s->metadata, 0, sizeof(s->metadata));\n\n#ifdef HAVE_CURL\n\ts->curl.mime_type = NULL;\n\tif (is_url (file))\n\t\tio_curl_open (s, file);\n\telse\n#endif\n\tio_open_file (s, file);\n\n\tpthread_mutex_init (&s->buf_mtx, NULL);\n\tpthread_mutex_init (&s->io_mtx, NULL);\n\tpthread_mutex_init (&s->metadata.mtx, NULL);\n\n\tif (!s->opened)\n\t\treturn s;\n\n\ts->stop_read_thread = 0;\n\ts->eof = 0;\n\ts->after_seek = 0;\n\ts->buffered = buffered;\n\ts->pos = 0;\n\n\tif (buffered) {\n\t\ts->buf = fifo_buf_new (options_get_int(\"InputBuffer\") * 1024);\n\t\ts->prebuffer = options_get_int(\"Prebuffering\") * 1024;\n\n\t\tpthread_cond_init (&s->buf_free_cond, NULL);\n\t\tpthread_cond_init (&s->buf_fill_cond, NULL);\n\n\t\trc = pthread_create (&s->read_thread, NULL, io_read_thread, s);\n\t\tif (rc != 0)\n\t\t\tfatal (\"Can't create read thread: %s\", xstrerror (errno));\n\t}\n\n\treturn s;\n}\n\n/* Return non-zero if the stream was free of errors. */\nstatic int io_ok_nolock (struct io_stream *s)\n{\n\treturn !s->read_error && s->errno_val == 0;\n}\n\n/* Return non-zero if the stream was free of errors. */\nint io_ok (struct io_stream *s)\n{\n\tint res;\n\n\tLOCK (s->buf_mtx);\n\tres = io_ok_nolock (s);\n\tUNLOCK (s->buf_mtx);\n\n\treturn res;\n}\n\n/* Read data from the buffer without removing them, so stream position is\n * unchanged. You can't peek more data than the buffer size. */\nstatic ssize_t io_peek_internal (struct io_stream *s, void *buf, size_t count)\n{\n\tssize_t received = 0;\n\n\tdebug (\"Peeking data...\");\n\n\tLOCK (s->buf_mtx);\n\n\t/* Wait until enough data will be available */\n\twhile (io_ok_nolock(s) && !s->stop_read_thread\n\t\t\t&& count > fifo_buf_get_fill (s->buf)\n\t\t\t&& fifo_buf_get_space (s->buf)\n\t\t\t&& !s->eof) {\n\t\tdebug (\"waiting...\");\n\t\tpthread_cond_wait (&s->buf_fill_cond, &s->buf_mtx);\n\t}\n\n\treceived = fifo_buf_peek (s->buf, buf, count);\n\tdebug (\"Read %zd bytes\", received);\n\n\tUNLOCK (s->buf_mtx);\n\n\treturn io_ok(s) ? received : -1;\n}\n\n/* Wait until there are s->prebuffer bytes in the buffer or some event\n * occurs which prevents prebuffering. */\nvoid io_prebuffer (struct io_stream *s, const size_t to_fill)\n{\n\tlogit (\"prebuffering to %zu bytes...\", to_fill);\n\n\tLOCK (s->buf_mtx);\n\twhile (io_ok_nolock(s) && !s->stop_read_thread && !s->eof\n\t                       && to_fill > fifo_buf_get_fill(s->buf)) {\n\t\tdebug (\"waiting (buffer %zu bytes full)\", fifo_buf_get_fill (s->buf));\n\t\tpthread_cond_wait (&s->buf_fill_cond, &s->buf_mtx);\n\t}\n\tUNLOCK (s->buf_mtx);\n\n\tlogit (\"done\");\n}\n\nstatic ssize_t io_read_buffered (struct io_stream *s, void *buf, size_t count)\n{\n\tssize_t received = 0;\n\n\tLOCK (s->buf_mtx);\n\n\twhile (received < (ssize_t)count && !s->stop_read_thread\n\t\t\t&& ((!s->eof && !s->read_error)\n\t\t\t\t|| fifo_buf_get_fill(s->buf))) {\n\t\tif (fifo_buf_get_fill(s->buf)) {\n\t\t\treceived += fifo_buf_get (s->buf, (char *)buf + received,\n\t\t\t\t\tcount - received);\n\t\t\tdebug (\"Read %zd bytes so far\", received);\n\t\t\tpthread_cond_signal (&s->buf_free_cond);\n\t\t\tcontinue;\n\t\t}\n\n\t\tdebug (\"Buffer empty, waiting...\");\n\t\tpthread_cond_wait (&s->buf_fill_cond, &s->buf_mtx);\n\t}\n\n\tdebug (\"done\");\n\ts->pos += received;\n\n\tUNLOCK (s->buf_mtx);\n\n\treturn received ? received : (s->read_error ? -1 : 0);\n}\n\n/* Read data from the stream without buffering. If dont_move was set, the\n * stream position is unchanged. */\nstatic ssize_t io_read_unbuffered (struct io_stream *s, const int dont_move,\n\t\tvoid *buf, size_t count)\n{\n\tssize_t res;\n\n\tassert (!s->eof);\n\n\tres = io_internal_read (s, dont_move, buf, count);\n\n\tif (!dont_move) {\n\t\ts->pos += res;\n\t\tif (res == 0)\n\t\t\ts->eof = 1;\n\t}\n\n\treturn res;\n}\n\n/* Read data from the stream to the buffer of size count.  Return the number\n * of bytes read, 0 on EOF, < 0 on error. */\nssize_t io_read (struct io_stream *s, void *buf, size_t count)\n{\n\tssize_t received;\n\n\tassert (s != NULL);\n\tassert (buf != NULL);\n\tassert (s->opened);\n\n\tdebug (\"Reading...\");\n\n\tif (s->buffered)\n\t\treceived = io_read_buffered (s, buf, count);\n\telse if (s->eof)\n\t\treceived = 0;\n\telse\n\t\treceived = io_read_unbuffered (s, 0, buf, count);\n\n\treturn received;\n}\n\n/* Read data from the stream to the buffer of size count. The data are not\n * removed from the stream. Return the number of bytes read, 0 on EOF, < 0\n * on error. */\nssize_t io_peek (struct io_stream *s, void *buf, size_t count)\n{\n\tssize_t received;\n\n\tassert (s != NULL);\n\tassert (buf != NULL);\n\n\tdebug (\"Reading...\");\n\n\tif (s->buffered)\n\t\treceived = io_peek_internal (s, buf, count);\n\telse\n\t\treceived = io_read_unbuffered (s, 1, buf, count);\n\n\treturn io_ok(s) ? received : -1;\n}\n\n/* Get the string describing the error associated with the stream. */\nchar *io_strerror (struct io_stream *s)\n{\n\tif (s->strerror)\n\t\tfree (s->strerror);\n\n#ifdef HAVE_CURL\n\tif (s->source == IO_SOURCE_CURL)\n\t\tio_curl_strerror (s);\n\telse\n#endif\n\tif (s->errno_val)\n\t\ts->strerror = xstrerror (s->errno_val);\n\telse\n\t\ts->strerror = xstrdup (\"OK\");\n\n\treturn s->strerror;\n}\n\n/* Get the file size if available or -1. */\noff_t io_file_size (const struct io_stream *s)\n{\n\tassert (s != NULL);\n\n\treturn s->size;\n}\n\n/* Return the stream position. */\noff_t io_tell (struct io_stream *s)\n{\n\toff_t res = -1;\n\n\tassert (s != NULL);\n\n\tif (s->buffered) {\n\t\tLOCK (s->buf_mtx);\n\t\tres = s->pos;\n\t\tUNLOCK (s->buf_mtx);\n\t}\n\telse\n\t\tres = s->pos;\n\n\tdebug (\"We are at byte %\"PRId64, res);\n\n\treturn res;\n}\n\n/* Return != 0 if we are at the end of the stream. */\nint io_eof (struct io_stream *s)\n{\n\tint eof;\n\n\tassert (s != NULL);\n\n\tLOCK (s->buf_mtx);\n\teof = (s->eof && (!s->buffered || !fifo_buf_get_fill(s->buf))) ||\n\t\ts->stop_read_thread;\n\tUNLOCK (s->buf_mtx);\n\n\treturn eof;\n}\n\nvoid io_init ()\n{\n#ifdef HAVE_CURL\n\tio_curl_init ();\n#endif\n}\n\nvoid io_cleanup ()\n{\n#ifdef HAVE_CURL\n\tio_curl_cleanup ();\n#endif\n}\n\n/* Return the mime type if available or NULL.\n * The mime type is read by curl only after the first read (or peek), until\n * then it's NULL. */\nchar *io_get_mime_type (struct io_stream *s CURL_ONLY)\n{\n#ifdef HAVE_CURL\n\treturn s->curl.mime_type;\n#else\n\treturn NULL;\n#endif\n}\n\n/* Return the malloc()ed stream title if available or NULL. */\nchar *io_get_metadata_title (struct io_stream *s)\n{\n\tchar *t;\n\n\tLOCK (s->metadata.mtx);\n\tt = xstrdup (s->metadata.title);\n\tUNLOCK (s->metadata.mtx);\n\n\treturn t;\n}\n\n/* Return the malloc()ed stream url (from metadata) if available or NULL. */\nchar *io_get_metadata_url (struct io_stream *s)\n{\n\tchar *t;\n\n\tLOCK (s->metadata.mtx);\n\tt = xstrdup (s->metadata.url);\n\tUNLOCK (s->metadata.mtx);\n\n\treturn t;\n}\n\n/* Set the metadata title of the stream. */\nvoid io_set_metadata_title (struct io_stream *s, const char *title)\n{\n\tLOCK (s->metadata.mtx);\n\tif (s->metadata.title)\n\t\tfree (s->metadata.title);\n\ts->metadata.title = xstrdup (title);\n\tUNLOCK (s->metadata.mtx);\n}\n\n/* Set the metadata url for the stream. */\nvoid io_set_metadata_url (struct io_stream *s, const char *url)\n{\n\tLOCK (s->metadata.mtx);\n\tif (s->metadata.url)\n\t\tfree (s->metadata.url);\n\ts->metadata.url = xstrdup (url);\n\tUNLOCK (s->metadata.mtx);\n}\n\n/* Set the callback function to be invoked when the fill of the buffer\n * changes.  data_ptr is a pointer passed to this function along with\n * the pointer to the stream. */\nvoid io_set_buf_fill_callback (struct io_stream *s,\n\t\tbuf_fill_callback_t callback, void *data_ptr)\n{\n\tassert (s != NULL);\n\tassert (callback != NULL);\n\n\tLOCK (s->buf_mtx);\n\ts->buf_fill_callback = callback;\n\ts->buf_fill_callback_data = data_ptr;\n\tUNLOCK (s->buf_mtx);\n}\n\n/* Return a non-zero value if the stream is seekable. */\nint io_seekable (const struct io_stream *s)\n{\n\treturn s->source == IO_SOURCE_FD || s->source == IO_SOURCE_MMAP;\n}\n"
  },
  {
    "path": "io.h",
    "content": "#ifndef IO_H\n#define IO_H\n\n#include <sys/types.h>\n#include <pthread.h>\n#ifdef HAVE_CURL\n# include <sys/socket.h>     /* curl sometimes needs this */\n# include <curl/curl.h>\n#endif\n\n#include \"fifo_buf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nenum io_source\n{\n\tIO_SOURCE_FD,\n\tIO_SOURCE_MMAP,\n\tIO_SOURCE_CURL\n};\n\n#ifdef HAVE_CURL\nstruct io_stream_curl\n{\n\tCURLM *multi_handle;\t/* we use the multi interface to get the\n\t\t\t\t\t   data in pieces */\n\tCURL *handle;\t\t/* the actual used handle */\n\tCURLMcode multi_status;\t/* curl status of the last multi operation */\n\tCURLcode status;\t/* curl status of the last easy operation */\n\tchar *url;\n\tstruct curl_slist *http_headers;\t/* HTTP headers to send with\n\t\t\t\t\t\t   the request */\n\tchar *buf;\t\t/* buffer for data that curl gives us */\n\tlong buf_fill;\n\tint need_perform_loop;\t/* do we need the perform() loop? */\n\tint got_locn;\t/* received a location header */\n\tchar *mime_type;\t/* mime type of the stream */\n\tint wake_up_pipe[2];\t/* pipes used to wake up the curl read\n\t\t\t\t\t   loop that does select() */\n\tstruct curl_slist *http200_aliases; /* list of aliases for http\n\t\t\t\t\t\tresponse's status line */\n\tsize_t icy_meta_int;\t/* how often are icy metadata sent?\n\t\t\t\t   0 - disabled, in bytes */\n\tsize_t icy_meta_count;\t/* how many bytes was read from the last\n\t\t\t\t   metadata packet */\n};\n#endif\n\nstruct io_stream;\n\ntypedef void (*buf_fill_callback_t) (struct io_stream *s, size_t fill,\n\t\tsize_t buf_size, void *data_ptr);\n\nstruct io_stream\n{\n\tenum io_source source;\t/* source of the file */\n\tint fd;\n\toff_t size;\t/* size of the file */\n\tint errno_val;\t/* errno value of the last operation  - 0 if ok */\n\tint read_error; /* set to != 0 if the last read operation dailed */\n\tchar *strerror;\t/* error string */\n\tint opened;\t/* was the stream opened (open(), mmap(), etc.)? */\n\tint eof;\t/* was the end of file reached? */\n\tint after_seek;\t/* are we after seek and need to do fresh read()? */\n\tint buffered;\t/* are we using the buffer? */\n\toff_t pos;\t/* current position in the file from the user point of view */\n\tsize_t prebuffer;\t/* number of bytes left to prebuffer */\n\tpthread_mutex_t io_mtx;\t/* mutex for IO operations */\n\n#ifdef HAVE_MMAP\n\tvoid *mem;\n\toff_t mem_pos;\n#endif\n\n#ifdef HAVE_CURL\n\tstruct io_stream_curl curl;\n#endif\n\n\tstruct fifo_buf *buf;\n\tpthread_mutex_t buf_mtx;\n\tpthread_cond_t buf_free_cond; /* some space became available in the\n\t\t\t\t\t buffer */\n\tpthread_cond_t buf_fill_cond; /* the buffer was filled with some data */\n\tpthread_t read_thread;\n\tint stop_read_thread;\t\t/* request for stopping the read\n\t\t\t\t\t   thread */\n\n\tstruct stream_metadata {\n\t\tpthread_mutex_t mtx;\n\t\tchar *title;\t/* title of the stream */\n\t\tchar *url;\n\t} metadata;\n\n\t/* callbacks */\n\tbuf_fill_callback_t buf_fill_callback;\n\tvoid *buf_fill_callback_data;\n};\n\nstruct io_stream *io_open (const char *file, const int buffered);\nssize_t io_read (struct io_stream *s, void *buf, size_t count);\nssize_t io_peek (struct io_stream *s, void *buf, size_t count);\noff_t io_seek (struct io_stream *s, off_t offset, int whence);\nvoid io_close (struct io_stream *s);\nint io_ok (struct io_stream *s);\nchar *io_strerror (struct io_stream *s);\noff_t io_file_size (const struct io_stream *s);\noff_t io_tell (struct io_stream *s);\nint io_eof (struct io_stream *s);\nvoid io_init ();\nvoid io_cleanup ();\nvoid io_abort (struct io_stream *s);\nchar *io_get_mime_type (struct io_stream *s);\nchar *io_get_title (struct io_stream *s);\nchar *io_get_metadata_title (struct io_stream *s);\nchar *io_get_metadata_url (struct io_stream *s);\nvoid io_set_metadata_title (struct io_stream *s, const char *title);\nvoid io_set_metadata_url (struct io_stream *s, const char *url);\nvoid io_prebuffer (struct io_stream *s, const size_t to_fill);\nvoid io_set_buf_fill_callback (struct io_stream *s,\n\t\tbuf_fill_callback_t callback, void *data_ptr);\nint io_seekable (const struct io_stream *s);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "io_curl.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <curl/curl.h>\n#include <string.h>\n#include <strings.h>\n#include <ctype.h>\n#include <stdlib.h>\n#include <sys/select.h>\n#include <unistd.h>\n#include <errno.h>\n#include <assert.h>\n#include <stdint.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"io.h\"\n#include \"io_curl.h\"\n#include \"options.h\"\n#include \"lists.h\"\n\nstatic char user_agent[] = PACKAGE_NAME\"/\"PACKAGE_VERSION;\n\nvoid io_curl_init ()\n{\n\tchar *ptr;\n\n\tfor (ptr = user_agent; *ptr; ptr += 1) {\n\t\tif (*ptr == ' ')\n\t\t\t*ptr = '-';\n\t}\n\n\tcurl_global_init (CURL_GLOBAL_NOTHING);\n}\n\nvoid io_curl_cleanup ()\n{\n\tcurl_global_cleanup ();\n}\n\nstatic size_t write_cb (void *data, size_t size, size_t nmemb,\n\t\tvoid *stream)\n{\n\tstruct io_stream *s = (struct io_stream *)stream;\n\tsize_t buf_start = s->curl.buf_fill;\n\tsize_t data_size = size * nmemb;\n\n\ts->curl.buf_fill += data_size;\n\tdebug (\"Got %zu bytes\", data_size);\n\ts->curl.buf = (char *)xrealloc (s->curl.buf, s->curl.buf_fill);\n\tmemcpy (s->curl.buf + buf_start, data, data_size);\n\n\treturn data_size;\n}\n\nstatic size_t header_cb (void *data, size_t size, size_t nmemb,\n\t\tvoid *stream)\n{\n\tstruct io_stream *s = (struct io_stream *)stream;\n\tchar *header;\n\tsize_t header_size;\n\n\tassert (s != NULL);\n\n\tif (size * nmemb <= 2)\n\t\treturn size * nmemb;\n\n\t/* we dont need '\\r\\n', so cut it. */\n\theader_size = sizeof(char) * (size * nmemb + 1 - 2);\n\n\t/* copy the header to char* array */\n\theader = (char *)xmalloc (header_size);\n\tmemcpy (header, data, size * nmemb - 2);\n\theader[header_size-1] = 0;\n\n\tif (!strncasecmp(header, \"Location:\", sizeof(\"Location:\")-1)) {\n\t\ts->curl.got_locn = 1;\n\t}\n\telse if (!strncasecmp(header, \"Content-Type:\", sizeof(\"Content-Type:\")-1)) {\n\t\t/* If we got redirected then use the last MIME type. */\n\t\tif (s->curl.got_locn && s->curl.mime_type) {\n\t\t\tfree (s->curl.mime_type);\n\t\t\ts->curl.mime_type = NULL;\n\t\t}\n\t\tif (s->curl.mime_type)\n\t\t\tlogit (\"Another Content-Type header!\");\n\t\telse {\n\t\t\tchar *value = header + sizeof(\"Content-Type:\") - 1;\n\n\t\t\twhile (isblank(value[0]))\n\t\t\t\tvalue++;\n\n\t\t\ts->curl.mime_type = xstrdup (value);\n\t\t\tdebug (\"Mime type: '%s'\", s->curl.mime_type);\n\t\t}\n\t}\n\telse if (!strncasecmp(header, \"icy-name:\", sizeof(\"icy-name:\")-1)\n\t\t\t|| !strncasecmp(header, \"x-audiocast-name\",\n\t\t\t\tsizeof(\"x-audiocast-name\")-1)) {\n\t\tchar *value = strchr (header, ':') + 1;\n\n\t\twhile (isblank(value[0]))\n\t\t\tvalue++;\n\n\t\tio_set_metadata_title (s, value);\n\t}\n\telse if (!strncasecmp(header, \"icy-url:\", sizeof(\"icy-url:\")-1)) {\n\t\tchar *value = strchr (header, ':') + 1;\n\n\t\twhile (isblank(value[0]))\n\t\t\tvalue++;\n\n\t\tio_set_metadata_url (s, value);\n\t}\n\telse if (!strncasecmp(header, \"icy-metaint:\",\n\t\t\t\tsizeof(\"icy-metaint:\")-1)) {\n\t\tchar *end;\n\t\tchar *value = strchr (header, ':') + 1;\n\n\t\twhile (isblank(value[0]))\n\t\t\tvalue++;\n\n\t\ts->curl.icy_meta_int = strtol (value, &end, 10);\n\t\tif (*end) {\n\t\t\ts->curl.icy_meta_int = 0;\n\t\t\tlogit (\"Bad icy-metaint value\");\n\t\t}\n\t\telse\n\t\t\tdebug (\"Icy metadata interval: %zu\", s->curl.icy_meta_int);\n\t}\n\n\tfree (header);\n\n\treturn size * nmemb;\n}\n\n#if !defined(NDEBUG) && defined(DEBUG)\nstatic int debug_cb (CURL *unused1 ATTR_UNUSED, curl_infotype i,\n                     char *msg, size_t size, void *unused2 ATTR_UNUSED)\n{\n\tint ix;\n\tchar *log;\n\tconst char *type;\n\tlists_t_strs *lines;\n\n\tswitch (i) {\n\tcase CURLINFO_TEXT:\n\t\ttype = \"INFO\";\n\t\tbreak;\n\tcase CURLINFO_HEADER_IN:\n\t\ttype = \"RECV HEADER\";\n\t\tbreak;\n\tcase CURLINFO_HEADER_OUT:\n\t\ttype = \"SEND HEADER\";\n\t\tbreak;\n\tdefault:\n\t\treturn 0;\n\t}\n\n\tlog = (char *)xmalloc (size + 1);\n\tstrncpy (log, msg, size);\n\tlog[size] = 0;\n\n\tlines = lists_strs_new (8);\n\tlists_strs_split (lines, log, \"\\n\");\n\tfor (ix = 0; ix < lists_strs_size (lines); ix += 1)\n\t\tdebug (\"CURL: [%s] %s\", type, lists_strs_at (lines, ix));\n\tlists_strs_free (lines);\n\tfree (log);\n\n\treturn 0;\n}\n#endif\n\n/* Read messages given by curl and set the stream status. Return 0 on error. */\nstatic int check_curl_stream (struct io_stream *s)\n{\n\tCURLMsg *msg;\n\tint msg_queue_num;\n\tint res = 1;\n\n\twhile ((msg = curl_multi_info_read (s->curl.multi_handle,\n\t                                    &msg_queue_num))) {\n\t\tif (msg->msg == CURLMSG_DONE) {\n\t\t\ts->curl.status = msg->data.result;\n\t\t\tif (s->curl.status != CURLE_OK) {\n\t\t\t\tdebug (\"Read error\");\n\t\t\t\tres = 0;\n\t\t\t}\n\t\t\tcurl_multi_remove_handle (s->curl.multi_handle, s->curl.handle);\n\t\t\tcurl_easy_cleanup (s->curl.handle);\n\t\t\ts->curl.handle = NULL;\n\t\t\tdebug (\"EOF\");\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn res;\n}\n\nvoid io_curl_open (struct io_stream *s, const char *url)\n{\n\ts->source = IO_SOURCE_CURL;\n\ts->curl.url = NULL;\n\ts->curl.http_headers = NULL;\n\ts->curl.buf = NULL;\n\ts->curl.buf_fill = 0;\n\ts->curl.need_perform_loop = 1;\n\ts->curl.got_locn = 0;\n\n\ts->curl.wake_up_pipe[0] = -1;\n\ts->curl.wake_up_pipe[1] = -1;\n\n\tif (!(s->curl.multi_handle = curl_multi_init())) {\n\t\tlogit (\"curl_multi_init() returned NULL\");\n\t\ts->errno_val = EINVAL;\n\t\treturn;\n\t}\n\n\tif (!(s->curl.handle = curl_easy_init())) {\n\t\tlogit (\"curl_easy_init() returned NULL\");\n\t\ts->errno_val = EINVAL;\n\t\treturn;\n\t}\n\n\ts->curl.multi_status = CURLM_OK;\n\ts->curl.status = CURLE_OK;\n\n\ts->curl.url = xstrdup (url);\n\ts->curl.icy_meta_int = 0;\n\ts->curl.icy_meta_count = 0;\n\n\ts->curl.http200_aliases = curl_slist_append (NULL, \"ICY\");\n\ts->curl.http_headers = curl_slist_append (NULL, \"Icy-MetaData: 1\");\n\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_NOPROGRESS, 1);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_WRITEFUNCTION, write_cb);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_WRITEDATA, s);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_HEADERFUNCTION, header_cb);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_WRITEHEADER, s);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_USERAGENT, user_agent);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_URL, s->curl.url);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_FOLLOWLOCATION, 1);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_FAILONERROR, 1);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_MAXREDIRS, 15);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_HTTP200ALIASES,\n\t\t\ts->curl.http200_aliases);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_HTTPHEADER,\n\t\t\ts->curl.http_headers);\n\tif (options_get_str(\"HTTPProxy\"))\n\t\tcurl_easy_setopt (s->curl.handle, CURLOPT_PROXY,\n\t\t\t\toptions_get_str(\"HTTPProxy\"));\n#if !defined(NDEBUG) && defined(DEBUG)\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_VERBOSE, 1);\n\tcurl_easy_setopt (s->curl.handle, CURLOPT_DEBUGFUNCTION, debug_cb);\n#endif\n\n\tif ((s->curl.multi_status = curl_multi_add_handle(s->curl.multi_handle,\n\t\t\t\t\ts->curl.handle)) != CURLM_OK) {\n\t\tlogit (\"curl_multi_add_handle() failed\");\n\t\ts->errno_val = EINVAL;\n\t\treturn;\n\t}\n\n\tif (pipe(s->curl.wake_up_pipe) < 0) {\n\t\tlog_errno (\"pipe() failed\", errno);\n\t\ts->errno_val = EINVAL;\n\t\treturn;\n\t}\n\n\ts->opened = 1;\n}\n\nvoid io_curl_close (struct io_stream *s)\n{\n\tassert (s != NULL);\n\tassert (s->source == IO_SOURCE_CURL);\n\n\tif (s->curl.url)\n\t\tfree (s->curl.url);\n\tif (s->curl.http_headers)\n\t\tcurl_slist_free_all (s->curl.http_headers);\n\tif (s->curl.buf)\n\t\tfree (s->curl.buf);\n\tif (s->curl.mime_type)\n\t\tfree (s->curl.mime_type);\n\n\tif (s->curl.multi_handle && s->curl.handle)\n\t\tcurl_multi_remove_handle (s->curl.multi_handle, s->curl.handle);\n\tif (s->curl.handle)\n\t\tcurl_easy_cleanup (s->curl.handle);\n\tif (s->curl.multi_handle)\n\t\tcurl_multi_cleanup (s->curl.multi_handle);\n\n\tif (s->curl.wake_up_pipe[0] != -1) {\n\t\tclose (s->curl.wake_up_pipe[0]);\n\t\tclose (s->curl.wake_up_pipe[1]);\n\t}\n\n\tif (s->curl.http200_aliases)\n\t\tcurl_slist_free_all (s->curl.http200_aliases);\n}\n\n/* Get data using curl and put them into the internal buffer.\n * Even if the internal buffer is not empty, more data will be read.\n * Return 0 on error. */\nstatic int curl_read_internal (struct io_stream *s)\n{\n\tint running = 1;\n\tlong buf_fill_before = s->curl.buf_fill;\n\n\tif (s->curl.need_perform_loop) {\n\t\tdebug (\"Starting curl...\");\n\n\t\tdo {\n\t\t\ts->curl.multi_status = curl_multi_perform (\n\t\t\t\t\ts->curl.multi_handle, &running);\n\t\t} while (s->curl.multi_status == CURLM_CALL_MULTI_PERFORM);\n\n\t\tif (!check_curl_stream(s))\n\t\t\treturn 0;\n\n\t\ts->curl.need_perform_loop = 0;\n\t}\n\n\twhile (s->opened && running && buf_fill_before == s->curl.buf_fill\n\t\t\t&& s->curl.handle\n\t\t\t&& (s->curl.multi_status == CURLM_CALL_MULTI_PERFORM\n\t\t\t\t|| s->curl.multi_status == CURLM_OK)) {\n\t\tif (s->curl.multi_status != CURLM_CALL_MULTI_PERFORM) {\n\t\t\tfd_set read_fds, write_fds, exc_fds;\n\t\t\tint max_fd, ret;\n\t\t\tlong milliseconds;\n\t\t\tstruct timespec timeout;\n\n\t\t\tlogit (\"Doing pselect()...\");\n\n\t\t\tFD_ZERO (&read_fds);\n\t\t\tFD_ZERO (&write_fds);\n\t\t\tFD_ZERO (&exc_fds);\n\n\t\t\ts->curl.multi_status = curl_multi_fdset (\n\t\t\t\t\ts->curl.multi_handle,\n\t\t\t\t\t&read_fds, &write_fds, &exc_fds,\n\t\t\t\t\t&max_fd);\n\t\t\tif (s->curl.multi_status != CURLM_OK)\n\t\t\t\tlogit (\"curl_multi_fdset() failed\");\n\n\t\t\tFD_SET (s->curl.wake_up_pipe[0], &read_fds);\n\t\t\tif (s->curl.wake_up_pipe[0] > max_fd)\n\t\t\t\tmax_fd = s->curl.wake_up_pipe[0];\n\n\t\t\tcurl_multi_timeout (s->curl.multi_handle, &milliseconds);\n\t\t\tif (milliseconds <= 0)\n\t\t\t\tmilliseconds = 1000;\n\t\t\ttimeout.tv_sec = milliseconds / 1000;\n\t\t\ttimeout.tv_nsec = (milliseconds % 1000L) * 1000000L;\n\n\t\t\tret = pselect (max_fd + 1, &read_fds, &write_fds, &exc_fds,\n\t\t\t              &timeout, NULL);\n\n\t\t\tif (ret < 0 && errno == EINTR) {\n\t\t\t\tlogit (\"Interrupted\");\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tif (ret < 0) {\n\t\t\t\ts->errno_val = errno;\n\t\t\t\tlogit (\"pselect() failed\");\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tif (s->stop_read_thread)\n\t\t\t\treturn 1;\n\n\t\t\tif (FD_ISSET(s->curl.wake_up_pipe[0], &read_fds)) {\n\t\t\t\tlogit (\"Got wake up - exiting\");\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t}\n\n\t\ts->curl.multi_status = curl_multi_perform (s->curl.multi_handle,\n\t\t\t&running);\n\n\t\tif (!check_curl_stream(s))\n\t\t\treturn 0;\n\t}\n\n\treturn 1;\n}\n\n/* Read data from the internal buffer to buf. Return the number of bytes read.\n */\nstatic size_t read_from_buffer (struct io_stream *s, char *buf, size_t count)\n{\n\tif (s->curl.buf_fill) {\n\t\tlong to_copy = MIN ((long)count, s->curl.buf_fill);\n\n\t\t/*debug (\"Copying %ld bytes\", to_copy);*/\n\n\t\tmemcpy (buf, s->curl.buf, to_copy);\n\t\ts->curl.buf_fill -= to_copy;\n\n\t\tif (s->curl.buf_fill) {\n\t\t\tmemmove (s->curl.buf, s->curl.buf + to_copy,\n\t\t\t\t\ts->curl.buf_fill);\n\t\t\ts->curl.buf = (char *)xrealloc (s->curl.buf,\n\t\t\t\t\ts->curl.buf_fill);\n\t\t}\n\t\telse {\n\t\t\tfree (s->curl.buf);\n\t\t\ts->curl.buf = NULL;\n\t\t}\n\n\t\treturn to_copy;\n\t}\n\n\treturn 0;\n}\n\n/* Parse icy string in form: StreamTitle='my music';StreamUrl='www.x.com' */\nstatic void parse_icy_string (struct io_stream *s, const char *str)\n{\n\tconst char *c = str;\n\n\tdebug (\"Got metadata string: %s\", str);\n\n\twhile (*c) {\n\t\tchar name[64];\n\t\tchar value[256];\n\t\tconst char *t;\n\n\t\t/* get the name */\n\t\tt = c;\n\t\twhile (*c && *c != '=')\n\t\t\tc++;\n\t\tif (*c != '=' || c - t >= ssizeof(name)) {\n\t\t\tlogit (\"malformed metadata\");\n\t\t\treturn;\n\t\t}\n\t\tstrncpy (name, t, c - t);\n\t\tname[c - t] = 0;\n\n\t\t/* move to a char after ' */\n\t\tc++;\n\t\tif (*c != '\\'') {\n\t\t\tlogit (\"malformed metadata\");\n\t\t\treturn;\n\t\t}\n\t\tc++;\n\n\t\t/* read the value  - it can contain a quotation mark so we\n\t\t * recognize if it ends the value by checking if there is a\n\t\t * semicolon after it or if it's the end of the string */\n\t\tt = c;\n\t\twhile (*c && (*c != '\\'' || (*(c+1) != ';' && *(c+1))))\n\t\t\tc++;\n\n\t\tif (!*c) {\n\t\t\tlogit (\"malformed metadata\");\n\t\t\treturn;\n\t\t}\n\n\t\tstrncpy (value, t, MIN(c - t, ssizeof(value) - 1));\n\t\tvalue[MIN(c - t, ssizeof(value) - 1)] = 0;\n\n\t\t/* eat ' */\n\t\tc++;\n\n\t\t/* eat semicolon */\n\t\tif (*c == ';')\n\t\t\tc++;\n\n\t\tdebug (\"METADATA name: '%s' value: '%s'\", name, value);\n\n\t\tif (!strcasecmp(name, \"StreamTitle\"))\n\t\t\tio_set_metadata_title (s, value);\n\t\telse if (!strcasecmp(name, \"StreamUrl\"))\n\t\t\tio_set_metadata_url (s, value);\n\t\telse\n\t\t\tlogit (\"Unknown metadata element '%s'\", name);\n\t}\n}\n\n/* Parse the IceCast metadata packet. */\nstatic void parse_icy_metadata (struct io_stream *s, const char *packet,\n\t\tconst int size)\n{\n\tconst char *c = packet;\n\n\twhile (c - packet < size) {\n\t\tconst char *p = c;\n\n\t\twhile (*c && c - packet < size)\n\t\t\tc++;\n\t\tif (c - packet < size && !*c)\n\t\t\tparse_icy_string (s, p);\n\n\t\t/* pass the padding */\n\t\twhile (c - packet < size && !*c)\n\t\t\tc++;\n\t}\n}\n\n/* Read icy metadata from the curl stream. The stream should be at the\n * beginning of the metadata. Return 0 on error. */\nstatic int read_icy_metadata (struct io_stream *s)\n{\n\tuint8_t size_packet;\n\tint size;\n\tchar *packet;\n\n\t/* read the packet size */\n\tif (s->curl.buf_fill == 0 && !curl_read_internal(s))\n\t\treturn 0;\n\tif (read_from_buffer(s, (char *)&size_packet, sizeof(size_packet))\n\t\t\t== 0) {\n\t\tdebug (\"Got empty metadata packet\");\n\t\treturn 1;\n\t}\n\n\tif (size_packet == 0) {\n\t\tdebug (\"Got empty metadata packet\");\n\t\treturn 1;\n\t}\n\n\tsize = size_packet * 16;\n\n\t/* make sure that the whole packet is in the buffer */\n\twhile (s->curl.buf_fill < size && s->curl.handle\n\t\t\t&& !s->stop_read_thread)\n\t\tif (!curl_read_internal(s))\n\t\t\treturn 0;\n\n\tif (s->curl.buf_fill < size) {\n\t\tlogit (\"Icy metadata packet broken\");\n\t\treturn 0;\n\t}\n\n\tpacket = (char *)xmalloc (size);\n\tread_from_buffer (s, packet, size);\n\tdebug (\"Received metadata packet %d bytes long\", size);\n\tparse_icy_metadata (s, packet, size);\n\tfree (packet);\n\n\treturn 1;\n}\n\nssize_t io_curl_read (struct io_stream *s, char *buf, size_t count)\n{\n\tsize_t nread = 0;\n\n\tassert (s != NULL);\n\tassert (s->source == IO_SOURCE_CURL);\n\tassert (s->curl.multi_handle != NULL);\n\n\tdo {\n\t\tsize_t to_read;\n\t\tsize_t res;\n\n\t\tif (s->curl.icy_meta_int && s->curl.icy_meta_count\n\t\t\t\t== s->curl.icy_meta_int) {\n\t\t\ts->curl.icy_meta_count = 0;\n\t\t\tif (!read_icy_metadata(s))\n\t\t\t\treturn -1;\n\t\t}\n\n\t\tif (s->curl.icy_meta_int)\n\t\t\tto_read = MIN (count - nread, s->curl.icy_meta_int -\n\t\t\t\t\ts->curl.icy_meta_count);\n\t\telse\n\t\t\tto_read = count - nread;\n\n\t\tres = read_from_buffer (s, buf + nread, to_read);\n\t\tif (s->curl.icy_meta_int)\n\t\t\ts->curl.icy_meta_count += res;\n\t\tnread += res;\n\t\tdebug (\"Read %zu bytes from the buffer (%zu bytes full)\", res, nread);\n\n\t\tif (nread < count && !curl_read_internal(s))\n\t\t\treturn -1;\n\t} while (nread < count && !s->stop_read_thread\n\t\t\t&& s->curl.handle); /* s->curl.handle == NULL on EOF */\n\n\treturn nread;\n}\n\n/* Set the error string for the stream. */\nvoid io_curl_strerror (struct io_stream *s)\n{\n\tconst char *err = \"OK\";\n\n\tassert (s != NULL);\n\tassert (s->source == IO_SOURCE_CURL);\n\n\tif (s->curl.multi_status != CURLM_OK)\n\t\terr = curl_multi_strerror(s->curl.multi_status);\n\telse if (s->curl.status != CURLE_OK)\n\t\terr = curl_easy_strerror(s->curl.status);\n\n\ts->strerror = xstrdup (err);\n}\n\nvoid io_curl_wake_up (struct io_stream *s)\n{\n\tint w = 1;\n\n\tif (write(s->curl.wake_up_pipe[1], &w, sizeof(w)) < 0)\n\t\tlog_errno (\"Can't wake up curl thread: write() failed\", errno);\n}\n"
  },
  {
    "path": "io_curl.h",
    "content": "#ifndef IO_CURL_H\n#define IO_CURL_H\n\n#include \"io.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid io_curl_init ();\nvoid io_curl_cleanup ();\nvoid io_curl_open (struct io_stream *s, const char *url);\nvoid io_curl_close (struct io_stream *s);\nssize_t io_curl_read (struct io_stream *s, char *buf, size_t count);\nvoid io_curl_strerror (struct io_stream *s);\nvoid io_curl_wake_up (struct io_stream *s);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "jack.c",
    "content": "/* Jack plugin for moc by Alex Norman <alex@neisis.net> 2005\n * moc by Copyright (C) 2004 Damian Pietras <daper@daper.net>\n * use at your own risk\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <unistd.h>\n#include <stdio.h>\n#include <jack/jack.h>\n#include <jack/types.h>\n#include <jack/ringbuffer.h>\n#include <string.h>\n#include <assert.h>\n#include <math.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"audio.h\"\n#include \"log.h\"\n#include \"options.h\"\n\n#define RINGBUF_SZ 32768\n\n/* the client */\nstatic jack_client_t *client;\n/* an array of output ports */\nstatic jack_port_t **output_port;\n/* the ring buffer, used to store the sound data before jack takes it */\nstatic jack_ringbuffer_t *ringbuffer[2];\n/* volume */\nstatic jack_default_audio_sample_t volume = 1.0;\n/* volume as an integer - needed to avoid cast errors on set/read */\nstatic int volume_integer = 100;\n/* indicates if we should be playing or not */\nstatic int play;\n/* current sample rate */\nstatic int rate;\n/* flag set if xrun occurred that was our fault (the ringbuffer doesn't\n * contain enough data in the process callback) */\nstatic volatile int our_xrun = 0;\n/* set to 1 if jack client thread exits */\nstatic volatile int jack_shutdown = 0;\n\n/* this is the function that jack calls to get audio samples from us */\nstatic int process_cb(jack_nframes_t nframes, void *unused ATTR_UNUSED)\n{\n\tjack_default_audio_sample_t *out[2];\n\n\tif (nframes <= 0)\n\t\treturn 0;\n\n\t/* get the jack output ports */\n\tout[0] = (jack_default_audio_sample_t *) jack_port_get_buffer (\n\t\t\toutput_port[0], nframes);\n\tout[1] = (jack_default_audio_sample_t *) jack_port_get_buffer (\n\t\t\toutput_port[1], nframes);\n\n\tif (play) {\n\t\tsize_t i;\n\n\t\t/* ringbuffer[1] is filled later, so we only need to check\n\t\t * it's space. */\n\t\tsize_t avail_data = jack_ringbuffer_read_space(ringbuffer[1]);\n\t\tsize_t avail_frames = avail_data\n\t\t\t/ sizeof(jack_default_audio_sample_t);\n\n\t\tif (avail_frames > nframes) {\n\t\t\tavail_frames = nframes;\n\t\t\tavail_data = nframes\n\t\t\t\t* sizeof(jack_default_audio_sample_t);\n\t\t}\n\n\t\tjack_ringbuffer_read (ringbuffer[0], (char *)out[0],\n\t\t\t\tavail_data);\n\t\tjack_ringbuffer_read (ringbuffer[1], (char *)out[1],\n\t\t\t\tavail_data);\n\n\n\t\t/* we must provide nframes data, so fill with silence\n\t\t * the remaining space. */\n\t\tif (avail_frames < nframes) {\n\t\t\tour_xrun = 1;\n\n\t\t\tfor (i = avail_frames; i < nframes; i++)\n\t\t\t\tout[0][i] = out[1][i] = 0.0;\n\t\t}\n\t}\n\telse {\n\t\tsize_t i;\n\t\tsize_t size;\n\n\t\t/* consume the input */\n\t\tsize = jack_ringbuffer_read_space(ringbuffer[1]);\n\t\tjack_ringbuffer_read_advance (ringbuffer[0], size);\n\t\tjack_ringbuffer_read_advance (ringbuffer[1], size);\n\n\t\tfor (i = 0; i < nframes; i++) {\n\t\t\tout[0][i] = 0.0;\n\t\t\tout[1][i] = 0.0;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/* this is called if jack changes its sample rate */\nstatic int update_sample_rate_cb(jack_nframes_t new_rate,\n\t\tvoid *unused ATTR_UNUSED)\n{\n\trate = new_rate;\n\treturn 0;\n}\n\n/* callback for jack's error messages */\nstatic void error_cb (const char *msg)\n{\n\terror (\"JACK: %s\", msg);\n}\n\nstatic void shutdown_cb (void *unused ATTR_UNUSED)\n{\n\tjack_shutdown = 1;\n}\n\nstatic int moc_jack_init (struct output_driver_caps *caps)\n{\n\tconst char *client_name;\n\n\tclient_name = options_get_str (\"JackClientName\");\n\n\tjack_set_error_function (error_cb);\n\n#ifdef HAVE_JACK_CLIENT_OPEN\n\n\tjack_status_t status;\n\tjack_options_t options;\n\n\t/* open a client connection to the JACK server */\n\toptions = JackNullOption;\n\tif (!options_get_bool (\"JackStartServer\"))\n\t\toptions |= JackNoStartServer;\n\tclient = jack_client_open (client_name, options, &status, NULL);\n\tif (client == NULL) {\n\t\terror (\"jack_client_open() failed, status = 0x%2.0x\", status);\n\t\tif (status & JackServerFailed)\n\t\t\terror (\"Unable to connect to JACK server\");\n\t\treturn 0;\n\t}\n\n\tif (status & JackServerStarted)\n\t\tprintf (\"JACK server started\\n\");\n\n#else\n\n\t/* try to become a client of the JACK server */\n\tclient = jack_client_new (client_name);\n\tif (client == NULL) {\n\t\terror (\"Cannot create client; JACK server not running?\");\n\t\treturn 0;\n\t}\n\n#endif\n\n\tjack_shutdown = 0;\n\tjack_on_shutdown (client, shutdown_cb, NULL);\n\n\t/* allocate memory for an array of 2 output ports */\n\toutput_port = xmalloc(2 * sizeof(jack_port_t *));\n\toutput_port[0] = jack_port_register (client, \"output0\", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);\n\toutput_port[1] = jack_port_register (client, \"output1\", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);\n\n\t/* create the ring buffers */\n\tringbuffer[0] = jack_ringbuffer_create(RINGBUF_SZ);\n\tringbuffer[1] = jack_ringbuffer_create(RINGBUF_SZ);\n\n\t/* set the call back functions, activate the client */\n\tjack_set_process_callback (client, process_cb, NULL);\n\tjack_set_sample_rate_callback(client, update_sample_rate_cb, NULL);\n\tif (jack_activate (client)) {\n\t\terror (\"cannot activate client\");\n\t\treturn 0;\n\t}\n\n\t/* connect ports\n\t * a value of NULL in JackOut* gives no connection\n\t * */\n\tif(strcmp(options_get_str(\"JackOutLeft\"),\"NULL\")){\n\t\tif(jack_connect(client,\"moc:output0\", options_get_str(\"JackOutLeft\")))\n\t\t\tfprintf(stderr,\"%s is not a valid Jack Client / Port\", options_get_str(\"JackOutLeft\"));\n\t}\n\tif(strcmp(options_get_str(\"JackOutRight\"),\"NULL\")){\n\t\tif(jack_connect(client,\"moc:output1\", options_get_str(\"JackOutRight\")))\n\t\t\tfprintf(stderr,\"%s is not a valid Jack Client / Port\", options_get_str(\"JackOutRight\"));\n\t}\n\n\tcaps->formats = SFMT_FLOAT;\n\trate = jack_get_sample_rate (client);\n\tcaps->max_channels = caps->min_channels = 2;\n\n\tlogit (\"jack init\");\n\n\treturn 1;\n}\n\nstatic int moc_jack_open (struct sound_params *sound_params)\n{\n\tif (sound_params->fmt != SFMT_FLOAT) {\n\t\tchar fmt_name[SFMT_STR_MAX];\n\n\t\terror (\"Unsupported sound format: %s.\",\n\t\t\t\tsfmt_str(sound_params->fmt, fmt_name, sizeof(fmt_name)));\n\t\treturn 0;\n\t}\n\tif (sound_params->channels != 2) {\n\t\terror (\"Unsupported number of channels\");\n\t\treturn 0;\n\t}\n\n\tlogit (\"jack open\");\n\tplay = 1;\n\n\treturn 1;\n}\n\nstatic void moc_jack_close ()\n{\n\tlogit (\"jack close\");\n\tplay = 0;\n}\n\nstatic int moc_jack_play (const char *buff, const size_t size)\n{\n\tsize_t remain = size;\n\tsize_t pos = 0;\n\n\tif (jack_shutdown) {\n\t\tlogit (\"Refusing to play, because there is no client thread.\");\n\t\treturn -1;\n\t}\n\n\tdebug (\"Playing %zu bytes\", size);\n\n\tif (our_xrun) {\n\t\tlogit (\"xrun\");\n\t\tour_xrun = 0;\n\t}\n\n\twhile (remain && !jack_shutdown) {\n\t\tsize_t space;\n\n\t\t/* check if some space is available only in the second\n\t\t * ringbuffer, because it is read later than the first. */\n\t\tif ((space = jack_ringbuffer_write_space(ringbuffer[1]))\n\t\t\t\t> sizeof(jack_default_audio_sample_t)) {\n\t\t\tsize_t to_write;\n\n\t\t\tspace *= 2; /* we have 2 channels */\n\t\t\tdebug (\"Space in the ringbuffer: %zu bytes\", space);\n\n\t\t\tto_write = MIN (space, remain);\n\n\t\t\tto_write /= sizeof(jack_default_audio_sample_t) * 2;\n\t\t\tremain -= to_write * sizeof(float) * 2;\n\t\t\twhile (to_write--) {\n\t\t\t\tjack_default_audio_sample_t sample;\n\n\t\t\t\tsample = *(jack_default_audio_sample_t *)\n\t\t\t\t\t(buff + pos) * volume;\n\t\t\t\tpos += sizeof (jack_default_audio_sample_t);\n\t\t\t\tjack_ringbuffer_write (ringbuffer[0],\n\t\t\t\t\t\t(char *)&sample,\n\t\t\t\t\t\tsizeof(sample));\n\n\t\t\t\tsample = *(jack_default_audio_sample_t *)\n\t\t\t\t\t(buff + pos) * volume;\n\t\t\t\tpos += sizeof (jack_default_audio_sample_t);\n\t\t\t\tjack_ringbuffer_write (ringbuffer[1],\n\t\t\t\t\t\t(char *)&sample,\n\t\t\t\t\t\tsizeof(sample));\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tdebug (\"Sleeping for %uus\", (unsigned int)(RINGBUF_SZ\n\t\t\t\t\t/ (float)(audio_get_bps()) * 100000.0));\n\t\t\txsleep (RINGBUF_SZ, audio_get_bps ());\n\t\t}\n\t}\n\n\tif (jack_shutdown)\n\t\treturn -1;\n\n\treturn size;\n}\n\nstatic int moc_jack_read_mixer ()\n{\n\treturn volume_integer;\n}\n\nstatic void moc_jack_set_mixer (int vol)\n{\n\tvolume_integer = vol;\n\tvolume = (jack_default_audio_sample_t)((exp((double)vol / 100.0) - 1)\n\t\t\t/ (M_E - 1));\n}\n\nstatic int moc_jack_get_buff_fill ()\n{\n\t/* FIXME: should we also use jack_port_get_latency() here? */\n\treturn sizeof(float) * (jack_ringbuffer_read_space(ringbuffer[0])\n\t\t\t+ jack_ringbuffer_read_space(ringbuffer[1]))\n\t\t/ sizeof(jack_default_audio_sample_t);\n}\n\nstatic int moc_jack_reset ()\n{\n\t//jack_ringbuffer_reset(ringbuffer); /*this is not threadsafe!*/\n\treturn 1;\n}\n\n/* do any cleanup that needs to be done */\nstatic void moc_jack_shutdown(){\n\tjack_port_unregister(client,output_port[0]);\n\tjack_port_unregister(client,output_port[1]);\n\tfree(output_port);\n\tjack_client_close(client);\n\tjack_ringbuffer_free(ringbuffer[0]);\n\tjack_ringbuffer_free(ringbuffer[1]);\n}\n\nstatic int moc_jack_get_rate ()\n{\n\treturn rate;\n}\n\nstatic char *moc_jack_get_mixer_channel_name ()\n{\n\treturn xstrdup (\"soft mixer\");\n}\n\nstatic void moc_jack_toggle_mixer_channel ()\n{\n}\n\nvoid moc_jack_funcs (struct hw_funcs *funcs)\n{\n\tfuncs->init = moc_jack_init;\n\tfuncs->open = moc_jack_open;\n\tfuncs->close = moc_jack_close;\n\tfuncs->play = moc_jack_play;\n\tfuncs->read_mixer = moc_jack_read_mixer;\n\tfuncs->set_mixer = moc_jack_set_mixer;\n\tfuncs->get_buff_fill = moc_jack_get_buff_fill;\n\tfuncs->reset = moc_jack_reset;\n\tfuncs->shutdown = moc_jack_shutdown;\n\tfuncs->get_rate = moc_jack_get_rate;\n\tfuncs->get_mixer_channel_name = moc_jack_get_mixer_channel_name;\n\tfuncs->toggle_mixer_channel = moc_jack_toggle_mixer_channel;\n}\n"
  },
  {
    "path": "jack.h",
    "content": "#ifndef JACK_H\n#define JACK_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid moc_jack_funcs (struct hw_funcs *funcs);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "keymap.example",
    "content": "# This is the example keymap file for MOC.  You can define your own key\n# bindings for MOC commands by creating your own keymap file and setting\n# the 'Keymap' option in ~/.moc/config.\n#\n# The format of this file is:\n#\n#     - Lines beginning with # are comments.\n#     - Blank lines are ignored.\n#     - Every other line is expected to be in one of the formats:\n#\n#           COMMAND = [KEY ...]\n#           COMMAND += KEY ...\n#\n# The KEY can be:\n#\n#     - Just a char, like i, L, \", *\n#     - CTRL-KEY sequence: ^k (CTRL-k), ^4\n#     - ALT-KEY (meta) sequence: M-j (ALT-j), M-/\n#     - Special keys: DOWN, UP\n#                     LEFT, RIGHT\n#                     HOME, END\n#                     BACKSPACE\n#                     INS, DEL\n#                     ENTER\n#                     PAGE_UP, PAGE_DOWN\n#                     SPACE, TAB\n#                     KEYPAD_CENTER\n#                     ESCAPE\n#                     F1 - F12\n#\n# Note that the use of a digit as a KEY is deprecated.\n#\n# Maximum number of KEYs for one COMMAND is 5.\n#\n# Omitting the KEY for a COMMAND will unbind all its default keys.  They\n# will also be automatically unbound when you bind new KEYs to it. Individual\n# default KEYs will be automatically unbound when they are explicitly bound\n# to some other COMMAND.\n#\n# Using the '+=' form will cause the KEYs to be appended to any existing\n# (default or explicit) bindings for the COMMAND.  Appending an existing\n# default binding for the same COMMAND will cause MOC to think of that KEY\n# as then being explicitly bound.\n#\n# Only one binding for any given COMMAND can appear in the keymap file.  One\n# exception to this is that if the default keys for a COMMAND are explicitly\n# unbound then a subsequent binding may appear for it.  A second exception\n# is that multiple appending bindings may appear.\n#\n# Meta-key detection is sensitive to the ESCDELAY environment variable (see\n# the manpage for ncurses(3)).  In its absence, MOC resets the default\n# delay to 25ms.  If you need to emulate meta-key sequences using the ESC\n# key, then you may need to set the value of ESCDELAY back to its ncurses\n# default of 1000ms (but doing so will make the response to the ESC key\n# sluggish).\n#\n# If MOC's keypresses are being filtered through some other program (in a\n# GUI environment, for example) which also does meta-key detection, then\n# MOC is at the mercy of the timings with which that program presents them.\n#\n# Default key configuration for MOC (and a list of all available commands):\n\n# MOC control keys:\nquit_client           = q\nquit                  = Q\n\n# Menu and interface control keys:\ngo                    = ENTER\nmenu_down             = DOWN\nmenu_up               = UP\nmenu_page_down        = PAGE_DOWN\nmenu_page_up          = PAGE_UP\nmenu_first_item       = HOME\nmenu_last_item        = END\nsearch_menu           = g /\ntoggle_read_tags      = f\ntoggle_show_time      = ^t\ntoggle_show_format    = ^f\ntoggle_menu           = TAB\ntoggle_layout         = l\ntoggle_hidden_files   = H\nshow_lyrics           = L\ntheme_menu            = T\nhelp                  = h ?\nrefresh               = ^r\nreload                = r\n\n# Audio playing and positioning keys:\nseek_forward          = RIGHT\nseek_backward         = LEFT\nseek_forward_fast     = ]\nseek_backward_fast    = [\npause                 = p SPACE\nstop                  = s\nnext                  = n\nprevious              = b\ntoggle_shuffle        = S\ntoggle_repeat         = R\ntoggle_auto_next      = X\ntoggle_mixer          = x\ngo_url                = o\n\n# Volume control keys:\nvolume_down_1         = <\nvolume_up_1           = >\nvolume_down_5         = ,\nvolume_up_5           = .\nvolume_10             = M-1\nvolume_20             = M-2\nvolume_30             = M-3\nvolume_40             = M-4\nvolume_50             = M-5\nvolume_60             = M-6\nvolume_70             = M-7\nvolume_80             = M-8\nvolume_90             = M-9\n\n# Directory navigation keys: defaults are Shift-number\n#                            (i.e., 'shift 1' -> '!' -> 'Fastdir1').\ngo_to_a_directory     = i\ngo_to_music_directory = m\ngo_to_fast_dir1       = !\ngo_to_fast_dir2       = @\ngo_to_fast_dir3       = #\ngo_to_fast_dir4       = $\ngo_to_fast_dir5       = %\ngo_to_fast_dir6       = ^\ngo_to_fast_dir7       = &\ngo_to_fast_dir8       = *\ngo_to_fast_dir9       = (\ngo_to_fast_dir10      = )\ngo_to_playing_file    = G\ngo_up                 = U\n\n# Playlist specific keys:\nadd_file              = a\nadd_directory         = A\nplist_add_stream      = ^u\ndelete_from_playlist  = d\nplaylist_full_paths   = P\nplist_move_up         = u\nplist_move_down       = j\nsave_playlist         = V\nremove_dead_entries   = Y\nclear_playlist        = C\n\n# Queue manipulation keys:\nenqueue_file          = z\nclear_queue           = Z\n\n# User interaction control:\nhistory_up            = UP\nhistory_down          = DOWN\ndelete_to_start       = ^u\ndelete_to_end         = ^k\ncancel                = ^x ESCAPE\nhide_message          = M\n\n# Softmixer specific keys:\ntoggle_softmixer      = w\ntoggle_make_mono      = J\n\n# Equalizer specific keys:\ntoggle_equalizer      = E\nequalizer_refresh     = e\nequalizer_prev        = K\nequalizer_next        = k\n\n# External commands:\nmark_start            = '\nmark_end              = \"\nexec_command1         = F1\nexec_command2         = F2\nexec_command3         = F3\nexec_command4         = F4\nexec_command5         = F5\nexec_command6         = F6\nexec_command7         = F7\nexec_command8         = F8\nexec_command9         = F9\nexec_command10        = F10\n\n# The following commands are available but not assigned to any keys by\n# default:\n#\n#   toggle_percent\t Switch on/off play progress bar time percentage\n#\n"
  },
  {
    "path": "keys.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 - 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <string.h>\n#include <strings.h>\n#include <assert.h>\n#include <stdio.h>\n#include <errno.h>\n#include <ctype.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"keys.h\"\n#include \"interface.h\"\n#include \"interface_elements.h\"\n#include \"options.h\"\n#include \"log.h\"\n#include \"files.h\"\n\n/* ^c version of c */\n#ifndef CTRL\n# define CTRL(c) ((c) & CTRL_KEY_CODE)\n#endif\n\nstruct command\n{\n\tenum key_cmd cmd;\t/* the command */\n\tchar *name;\t\t/* name of the command (in keymap file) */\n\tchar *help;\t\t/* help string for the command */\n\tenum key_context context; /* context - where the command isused */\n\tint keys[6];\t\t/* array of keys ended with -1 */\n\tint default_keys;\t/* number of default keys */\n};\n\n/* Array of commands - each element is a list of keys for this command. */\nstatic struct command commands[] = {\n\t{\n\t\tKEY_CMD_QUIT_CLIENT,\n\t\t\"quit_client\",\n\t\t\"Detach MOC from the server\",\n\t\tCON_MENU,\n\t\t{ 'q', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_GO,\n\t\t\"go\",\n\t\t\"Start playing at this file or go to this directory\",\n\t\tCON_MENU,\n\t\t{ '\\n',\t-1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MENU_DOWN,\n\t\t\"menu_down\",\n\t\t\"Move down in the menu\",\n\t\tCON_MENU,\n\t\t{ KEY_DOWN, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MENU_UP,\n\t\t\"menu_up\",\n\t\t\"Move up in the menu\",\n\t\tCON_MENU,\n\t\t{ KEY_UP, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MENU_NPAGE,\n\t\t\"menu_page_down\",\n\t\t\"Move one page down\",\n\t\tCON_MENU,\n\t\t{ KEY_NPAGE, -1},\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MENU_PPAGE,\n\t\t\"menu_page_up\",\n\t\t\"Move one page up\",\n\t\tCON_MENU,\n\t\t{ KEY_PPAGE, -1},\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MENU_FIRST,\n\t\t\"menu_first_item\",\n\t\t\"Move to the first item in the menu\",\n\t\tCON_MENU,\n\t\t{ KEY_HOME, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MENU_LAST,\n\t\t\"menu_last_item\",\n\t\t\"Move to the last item in the menu\",\n\t\tCON_MENU,\n\t\t{ KEY_END, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_QUIT,\n\t\t\"quit\",\n\t\t\"Quit\",\n\t\tCON_MENU,\n\t\t{ 'Q', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_STOP,\n\t\t\"stop\",\n\t\t\"Stop\",\n\t\tCON_MENU,\n\t\t{ 's', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_NEXT,\n\t\t\"next\",\n\t\t\"Play next file\",\n\t\tCON_MENU,\n\t\t{ 'n', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_PREVIOUS,\n\t\t\"previous\",\n\t\t\"Play previous file\",\n\t\tCON_MENU,\n\t\t{ 'b', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_PAUSE,\n\t\t\"pause\",\n\t\t\"Pause\",\n\t\tCON_MENU,\n\t\t{ 'p', ' ', -1 },\n\t\t2\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_READ_TAGS,\n\t\t\"toggle_read_tags\",\n\t\t\"Toggle ReadTags option\",\n\t\tCON_MENU,\n\t\t{ 'f', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_SHUFFLE,\n\t\t\"toggle_shuffle\",\n\t\t\"Toggle Shuffle\",\n\t\tCON_MENU,\n\t\t{ 'S', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_REPEAT,\n\t\t\"toggle_repeat\",\n\t\t\"Toggle Repeat\",\n\t\tCON_MENU,\n\t\t{ 'R', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_AUTO_NEXT,\n\t\t\"toggle_auto_next\",\n\t\t\"Toggle AutoNext\",\n\t\tCON_MENU,\n\t\t{ 'X', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_MENU,\n\t\t\"toggle_menu\",\n\t\t\"Switch between playlist and file list\",\n\t\tCON_MENU,\n\t\t{ '\\t', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_LAYOUT,\n\t\t\"toggle_layout\",\n\t\t\"Switch between layouts\",\n\t\tCON_MENU,\n\t\t{ 'l', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_PERCENT,\n\t\t\"toggle_percent\",\n\t\t\"Switch on/off play time percentage\",\n\t\tCON_MENU,\n\t\t{ -1 },\n\t\t0\n\t},\n\t{\n\t\tKEY_CMD_PLIST_ADD_FILE,\n\t\t\"add_file\",\n\t\t\"Add a file/directory to the playlist\",\n\t\tCON_MENU,\n\t\t{ 'a', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_PLIST_CLEAR,\n\t\t\"clear_playlist\",\n\t\t\"Clear the playlist\",\n\t\tCON_MENU,\n\t\t{ 'C', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_PLIST_ADD_DIR,\n\t\t\"add_directory\",\n\t\t\"Add a directory recursively to the playlist\",\n\t\tCON_MENU,\n\t\t{ 'A', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_PLIST_REMOVE_DEAD_ENTRIES,\n\t\t\"remove_dead_entries\",\n\t\t\"Remove playlist entries for non-existent files\",\n\t\tCON_MENU,\n\t\t{ 'Y', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MIXER_DEC_1,\n\t\t\"volume_down_1\",\n\t\t\"Decrease volume by 1%\",\n\t\tCON_MENU,\n\t\t{ '<', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MIXER_INC_1,\n\t\t\"volume_up_1\",\n\t\t\"Increase volume by 1%\",\n\t\tCON_MENU,\n\t\t{ '>', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MIXER_DEC_5,\n\t\t\"volume_down_5\",\n\t\t\"Decrease volume by 5%\",\n\t\tCON_MENU,\n\t\t{ ',', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MIXER_INC_5,\n\t\t\"volume_up_5\",\n\t\t\"Increase volume by 5%\",\n\t\tCON_MENU,\n\t\t{ '.', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_SEEK_FORWARD,\n\t\t\"seek_forward\",\n\t\t\"Seek forward by n-s\",\n\t\tCON_MENU,\n\t\t{ KEY_RIGHT, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_SEEK_BACKWARD,\n\t\t\"seek_backward\",\n\t\t\"Seek backward by n-s\",\n\t\tCON_MENU,\n\t\t{ KEY_LEFT, -1},\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_HELP,\n\t\t\"help\",\n\t\t\"Show the help screen\",\n\t\tCON_MENU,\n\t\t{ 'h', '?', -1 },\n\t\t2\n\t},\n\t{\n\t\tKEY_CMD_HIDE_MESSAGE,\n\t\t\"hide_message\",\n\t\t\"Hide error/informative message\",\n\t\tCON_MENU,\n\t\t{ 'M', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_REFRESH,\n\t\t\"refresh\",\n\t\t\"Refresh the screen\",\n\t\tCON_MENU,\n\t\t{ CTRL('r'), CTRL('l'), -1},\n\t\t2\n\t},\n\t{\n\t\tKEY_CMD_RELOAD,\n\t\t\"reload\",\n\t\t\"Reread directory content\",\n\t\tCON_MENU,\n\t\t{ 'r', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_SHOW_HIDDEN_FILES,\n\t\t\"toggle_hidden_files\",\n\t\t\"Toggle ShowHiddenFiles option\",\n\t\tCON_MENU,\n\t\t{ 'H', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_GO_MUSIC_DIR,\n\t\t\"go_to_music_directory\",\n\t\t\"Go to the music directory (requires config option)\",\n\t\tCON_MENU,\n\t\t{ 'm', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_PLIST_DEL,\n\t\t\"delete_from_playlist\",\n\t\t\"Delete an item from the playlist\",\n\t\tCON_MENU,\n\t\t{ 'd', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_MENU_SEARCH,\n\t\t\"search_menu\",\n\t\t\"Search the menu\",\n\t\tCON_MENU,\n\t\t{ 'g', '/', -1 },\n\t\t2\n\t},\n\t{\n\t\tKEY_CMD_PLIST_SAVE,\n\t\t\"save_playlist\",\n\t\t\"Save the playlist\",\n\t\tCON_MENU,\n\t\t{ 'V', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_SHOW_TIME,\n\t\t\"toggle_show_time\",\n\t\t\"Toggle ShowTime option\",\n\t\tCON_MENU,\n\t\t{ CTRL('t'), -1},\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_TOGGLE_SHOW_FORMAT,\n\t\t\"toggle_show_format\",\n\t\t\"Toggle ShowFormat option\",\n\t\tCON_MENU,\n\t\t{ CTRL('f'), -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_GO_URL,\n\t\t\"go_url\",\n\t\t\"Play from the URL\",\n\t\tCON_MENU,\n\t\t{ 'o', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_GO_TO_PLAYING_FILE,\n\t\t\"go_to_playing_file\",\n\t\t\"Go to the currently playing file's directory\",\n\t\tCON_MENU,\n\t\t{ 'G', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_GO_DIR,\n\t\t\"go_to_a_directory\",\n\t\t\"Go to a directory\",\n\t\tCON_MENU,\n\t\t{ 'i', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_GO_DIR_UP,\n\t\t\"go_up\",\n\t\t\"Go to '..'\",\n\t\tCON_MENU,\n\t\t{ 'U', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_CANCEL,\n\t\t\"cancel\",\n\t\t\"Exit from an entry\",\n\t\tCON_ENTRY,\n\t\t{ CTRL('x'), KEY_ESCAPE, -1 },\n\t\t2\n\t},\n\t{\n\t\tKEY_CMD_SEEK_FORWARD_5,\n\t\t\"seek_forward_fast\",\n\t\t\"Silent seek forward by 5s\",\n\t\tCON_MENU,\n\t\t{ ']', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_SEEK_BACKWARD_5,\n\t\t\"seek_backward_fast\",\n\t\t\"Silent seek backward by 5s\",\n\t\tCON_MENU,\n\t\t{ '[', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_VOLUME_10,\n\t\t\"volume_10\",\n\t\t\"Set volume to 10%\",\n\t\tCON_MENU,\n\t\t{ '1' | META_KEY_FLAG, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_VOLUME_20,\n\t\t\"volume_20\",\n\t\t\"Set volume to 20%\",\n\t\tCON_MENU,\n\t\t{ '2' | META_KEY_FLAG, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_VOLUME_30,\n\t\t\"volume_30\",\n\t\t\"Set volume to 30%\",\n\t\tCON_MENU,\n\t\t{ '3' | META_KEY_FLAG, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_VOLUME_40,\n\t\t\"volume_40\",\n\t\t\"Set volume to 40%\",\n\t\tCON_MENU,\n\t\t{ '4' | META_KEY_FLAG, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_VOLUME_50,\n\t\t\"volume_50\",\n\t\t\"Set volume to 50%\",\n\t\tCON_MENU,\n\t\t{ '5' | META_KEY_FLAG, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_VOLUME_60,\n\t\t\"volume_60\",\n\t\t\"Set volume to 60%\",\n\t\tCON_MENU,\n\t\t{ '6' | META_KEY_FLAG, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_VOLUME_70,\n\t\t\"volume_70\",\n\t\t\"Set volume to 70%\",\n\t\tCON_MENU,\n\t\t{ '7' | META_KEY_FLAG, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_VOLUME_80,\n\t\t\"volume_80\",\n\t\t\"Set volume to 80%\",\n\t\tCON_MENU,\n\t\t{ '8' | META_KEY_FLAG, -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_VOLUME_90,\n\t\t\"volume_90\",\n\t\t\"Set volume to 90%\",\n\t\tCON_MENU,\n\t\t{ '9' | META_KEY_FLAG, -1 },\n\t\t1\n\t},\n \t{\n \t\tKEY_CMD_MARK_START,\n \t\t\"mark_start\",\n \t\t\"Mark the start of a block\",\n \t\tCON_MENU,\n \t\t{ '\\'', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_MARK_END,\n \t\t\"mark_end\",\n \t\t\"Mark the end of a block\",\n \t\tCON_MENU,\n \t\t{ '\\\"', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_1,\n \t\t\"go_to_fast_dir1\",\n \t\t\"Go to a fast dir 1\",\n \t\tCON_MENU,\n \t\t{ '!', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_2,\n \t\t\"go_to_fast_dir2\",\n \t\t\"Go to a fast dir 2\",\n \t\tCON_MENU,\n \t\t{ '@', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_3,\n \t\t\"go_to_fast_dir3\",\n \t\t\"Go to a fast dir 3\",\n \t\tCON_MENU,\n \t\t{ '#', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_4,\n \t\t\"go_to_fast_dir4\",\n \t\t\"Go to a fast dir 4\",\n \t\tCON_MENU,\n \t\t{ '$', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_5,\n \t\t\"go_to_fast_dir5\",\n \t\t\"Go to a fast dir 5\",\n \t\tCON_MENU,\n \t\t{ '%', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_6,\n \t\t\"go_to_fast_dir6\",\n \t\t\"Go to a fast dir 6\",\n \t\tCON_MENU,\n \t\t{ '^', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_7,\n \t\t\"go_to_fast_dir7\",\n \t\t\"Go to a fast dir 7\",\n \t\tCON_MENU,\n \t\t{ '&', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_8,\n \t\t\"go_to_fast_dir8\",\n \t\t\"Go to a fast dir 8\",\n \t\tCON_MENU,\n \t\t{ '*', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_9,\n \t\t\"go_to_fast_dir9\",\n \t\t\"Go to a fast dir 9\",\n \t\tCON_MENU,\n \t\t{ '(', -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_FAST_DIR_10,\n \t\t\"go_to_fast_dir10\",\n \t\t\"Go to a fast dir 10\",\n \t\tCON_MENU,\n \t\t{ ')', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_HISTORY_UP,\n \t\t\"history_up\",\n \t\t\"Go to the previous entry in the history (entry)\",\n \t\tCON_ENTRY,\n \t\t{ KEY_UP, -1 },\n \t\t1\n \t},\n \t{\n \t\tKEY_CMD_HISTORY_DOWN,\n \t\t\"history_down\",\n \t\t\"Go to the next entry in the history (entry)\",\n \t\tCON_ENTRY,\n \t\t{ KEY_DOWN, -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_DELETE_START,\n \t\t\"delete_to_start\",\n \t\t\"Delete to start of line (entry)\",\n \t\tCON_ENTRY,\n \t\t{ CTRL('u'), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_DELETE_END,\n \t\t\"delete_to_end\",\n \t\t\"Delete to end of line (entry)\",\n \t\tCON_ENTRY,\n \t\t{ CTRL('k'), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_TOGGLE_MIXER,\n \t\t\"toggle_mixer\",\n \t\t\"Toggles the mixer channel\",\n \t\tCON_MENU,\n \t\t{ 'x', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_TOGGLE_SOFTMIXER,\n \t\t\"toggle_softmixer\",\n \t\t\"Toggles the software-mixer\",\n \t\tCON_MENU,\n \t\t{ 'w', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_TOGGLE_EQUALIZER,\n \t\t\"toggle_equalizer\",\n \t\t\"Toggles the equalizer\",\n \t\tCON_MENU,\n \t\t{ 'E', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EQUALIZER_REFRESH,\n \t\t\"equalizer_refresh\",\n \t\t\"Reload EQ-presets\",\n \t\tCON_MENU,\n \t\t{ 'e', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EQUALIZER_PREV,\n \t\t\"equalizer_prev\",\n \t\t\"Select previous equalizer-preset\",\n \t\tCON_MENU,\n \t\t{ 'K', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EQUALIZER_NEXT,\n \t\t\"equalizer_next\",\n \t\t\"Select next equalizer-preset\",\n \t\tCON_MENU,\n \t\t{ 'k', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_TOGGLE_MAKE_MONO,\n \t\t\"toggle_make_mono\",\n\t\t\"Toggle mono-mixing\",\n \t\tCON_MENU,\n \t\t{ 'J', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_PLIST_MOVE_UP,\n \t\t\"plist_move_up\",\n \t\t\"Move playlist item up\",\n \t\tCON_MENU,\n \t\t{ 'u', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_PLIST_MOVE_DOWN,\n \t\t\"plist_move_down\",\n \t\t\"Move playlist item down\",\n \t\tCON_MENU,\n \t\t{ 'j', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_ADD_STREAM,\n \t\t\"plist_add_stream\",\n \t\t\"Add a URL to the playlist using entry\",\n \t\tCON_MENU,\n \t\t{ CTRL('U'), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_THEME_MENU,\n \t\t\"theme_menu\",\n \t\t\"Switch to the theme selection menu\",\n \t\tCON_MENU,\n \t\t{ 'T', -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC1,\n \t\t\"exec_command1\",\n \t\t\"Execute ExecCommand1\",\n \t\tCON_MENU,\n \t\t{ KEY_F(1), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC2,\n \t\t\"exec_command2\",\n \t\t\"Execute ExecCommand2\",\n \t\tCON_MENU,\n \t\t{ KEY_F(2), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC3,\n \t\t\"exec_command3\",\n \t\t\"Execute ExecCommand3\",\n \t\tCON_MENU,\n \t\t{ KEY_F(3), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC4,\n \t\t\"exec_command4\",\n \t\t\"Execute ExecCommand4\",\n \t\tCON_MENU,\n \t\t{ KEY_F(4), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC5,\n \t\t\"exec_command5\",\n \t\t\"Execute ExecCommand5\",\n \t\tCON_MENU,\n \t\t{ KEY_F(5), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC6,\n \t\t\"exec_command6\",\n \t\t\"Execute ExecCommand6\",\n \t\tCON_MENU,\n \t\t{ KEY_F(6), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC7,\n \t\t\"exec_command7\",\n \t\t\"Execute ExecCommand7\",\n \t\tCON_MENU,\n \t\t{ KEY_F(7), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC8,\n \t\t\"exec_command8\",\n \t\t\"Execute ExecCommand8\",\n \t\tCON_MENU,\n \t\t{ KEY_F(8), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC9,\n \t\t\"exec_command9\",\n \t\t\"Execute ExecCommand9\",\n \t\tCON_MENU,\n \t\t{ KEY_F(9), -1 },\n \t\t1\n  \t},\n \t{\n \t\tKEY_CMD_EXEC10,\n \t\t\"exec_command10\",\n \t\t\"Execute ExecCommand10\",\n \t\tCON_MENU,\n \t\t{ KEY_F(10), -1 },\n \t\t1\n  \t},\n\t{\n\t\tKEY_CMD_LYRICS,\n\t\t\"show_lyrics\",\n\t\t\"Display lyrics of the current song (if available)\",\n\t\tCON_MENU,\n\t\t{ 'L',\t-1 },\n\t\t1\n\t},\n \t{\n \t\tKEY_CMD_TOGGLE_PLAYLIST_FULL_PATHS,\n \t\t\"playlist_full_paths\",\n \t\t\"Toggle displaying full paths in the playlist\",\n \t\tCON_MENU,\n \t\t{ 'P', -1 },\n \t\t1\n  \t},\n\t{\n\t\tKEY_CMD_QUEUE_TOGGLE_FILE,\n\t\t\"enqueue_file\",\n\t\t\"Add (or remove) a file to (from) queue\",\n\t\tCON_MENU,\n\t\t{ 'z', -1 },\n\t\t1\n\t},\n\t{\n\t\tKEY_CMD_QUEUE_CLEAR,\n\t\t\"clear_queue\",\n\t\t\"Clear the queue\",\n\t\tCON_MENU,\n\t\t{ 'Z', -1 },\n\t\t1\n\t}\n};\n\nstatic struct special_keys\n{\n\tchar *name;\n\tint key;\n} special_keys[] = {\n\t{ \"DOWN\",\t\tKEY_DOWN },\n\t{ \"UP\",\t\t\tKEY_UP },\n\t{ \"LEFT\",\t\tKEY_LEFT },\n\t{ \"RIGHT\",\t\tKEY_RIGHT },\n\t{ \"HOME\",\t\tKEY_HOME },\n\t{ \"BACKSPACE\",\t\tKEY_BACKSPACE },\n\t{ \"DEL\",\t\tKEY_DC },\n\t{ \"INS\",\t\tKEY_IC },\n\t{ \"ENTER\",\t\t'\\n' },\n\t{ \"PAGE_UP\",\t\tKEY_PPAGE },\n\t{ \"PAGE_DOWN\",\t\tKEY_NPAGE },\n\t{ \"TAB\",\t\t'\\t' },\n\t{ \"END\",\t\tKEY_END },\n\t{ \"KEYPAD_CENTER\",\tKEY_B2 },\n\t{ \"SPACE\",\t\t' ' },\n\t{ \"ESCAPE\",\t\tKEY_ESCAPE },\n\t{ \"F1\",\t\t\tKEY_F(1) },\n\t{ \"F2\",\t\t\tKEY_F(2) },\n\t{ \"F3\",\t\t\tKEY_F(3) },\n\t{ \"F4\",\t\t\tKEY_F(4) },\n\t{ \"F5\",\t\t\tKEY_F(5) },\n\t{ \"F6\",\t\t\tKEY_F(6) },\n\t{ \"F7\",\t\t\tKEY_F(7) },\n\t{ \"F8\",\t\t\tKEY_F(8) },\n\t{ \"F9\",\t\t\tKEY_F(9) },\n\t{ \"F10\",\t\tKEY_F(10) },\n\t{ \"F11\",\t\tKEY_F(11) },\n\t{ \"F12\",\t\tKEY_F(12) }\n};\n\n#define COMMANDS_NUM\t\t(ARRAY_SIZE(commands))\n#define SPECIAL_KEYS_NUM\t(ARRAY_SIZE(special_keys))\n\n/* Number of chars from the left where the help message starts\n * (skipping the key list). */\n#define HELP_INDENT\t15\n\nstatic char *help[COMMANDS_NUM];\n\nenum key_cmd get_key_cmd (const enum key_context context,\n                          const struct iface_key *key)\n{\n\tint k;\n\tsize_t i;\n\n\tk = (key->type == IFACE_KEY_CHAR) ? key->key.ucs : key->key.func;\n\n\tfor (i = 0; i < COMMANDS_NUM; i += 1) {\n\t\tif (commands[i].context == context) {\n\t\t\tint j = 0;\n\n\t\t\twhile (commands[i].keys[j] != -1) {\n\t\t\t\tif (commands[i].keys[j++] == k)\n\t\t\t\t\treturn commands[i].cmd;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn KEY_CMD_WRONG;\n}\n\n/* Return the path to the keymap file or NULL if none was specified. */\nstatic char *find_keymap_file ()\n{\n\tchar *file;\n\tstatic char path[PATH_MAX];\n\n\tif ((file = options_get_str(\"Keymap\"))) {\n\t\tif (file[0] == '/') {\n\n\t\t\t/* Absolute path */\n\t\t\tstrncpy (path, file, sizeof(path));\n\t\t\tif (path[sizeof(path)-1])\n\t\t\t\tfatal (\"Keymap path too long!\");\n\t\t\treturn path;\n\t\t}\n\n\t\tstrncpy (path, create_file_name(file), sizeof(path));\n\t\tif (path[sizeof(path)-1])\n\t\t\tfatal (\"Keymap path too long!\");\n\n\t\treturn path;\n\t}\n\n\treturn NULL;\n}\n\nstatic void keymap_parse_error (const int line, const char *msg)\n{\n\tfatal (\"Parse error in the keymap file line %d: %s\", line, msg);\n}\n\n/* Return a key for the symbolic key name (^c, M-F, etc.).\n * Return -1 on error. */\nstatic int parse_key (const char *symbol)\n{\n\tsize_t i;\n\n\tif (strlen(symbol) == 1) {\n\t\t/* Just a regular char */\n\t\tstatic bool digit_key_warned = false;\n\t\tif (!digit_key_warned && isdigit (symbol[0])) {\n\t\t\tfprintf (stderr,\n\t\t\t         \"\\n\\tUsing digits as keys is deprecated as they may\"\n\t\t\t         \"\\n\\tbe used for specific purposes in release 2.6.\\n\");\n\t\t\txsleep (5, 1);\n\t\t\tdigit_key_warned = true;\n\t\t}\n\t\treturn symbol[0];\n\t}\n\n\tif (symbol[0] == '^') {\n\n\t\t/* CTRL sequence */\n\t\tif (strlen(symbol) != 2)\n\t\t\treturn -1;\n\n\t\treturn CTRL(symbol[1]);\n\t}\n\n\tif (!strncasecmp(symbol, \"M-\", 2)) {\n\n\t\t/* Meta char */\n\t\tif (strlen(symbol) != 3)\n\t\t\treturn -1;\n\n\t\treturn symbol[2] | META_KEY_FLAG;\n\t}\n\n\t/* Special keys. */\n\tfor (i = 0; i < SPECIAL_KEYS_NUM; i += 1) {\n\t\tif (!strcasecmp(special_keys[i].name, symbol))\n\t\t\treturn special_keys[i].key;\n\t}\n\n\treturn -1;\n}\n\n/* Remove a single key from the default key definition for a command. */\nstatic void clear_default_key (int key)\n{\n\tsize_t cmd_ix;\n\n\tfor (cmd_ix = 0; cmd_ix < COMMANDS_NUM; cmd_ix += 1) {\n\t\tint key_ix;\n\n\t\tfor (key_ix = 0; key_ix < commands[cmd_ix].default_keys; key_ix++) {\n\t\t\tif (commands[cmd_ix].keys[key_ix] == key)\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (key_ix == commands[cmd_ix].default_keys)\n\t\t\t\tcontinue;\n\n\t\twhile (commands[cmd_ix].keys[key_ix] != -1) {\n\t\t\tcommands[cmd_ix].keys[key_ix] = commands[cmd_ix].keys[key_ix + 1];\n\t\t\tkey_ix += 1;\n\t\t}\n\n\t\tcommands[cmd_ix].default_keys -= 1;\n\n\t\tbreak;\n\t}\n}\n\n/* Remove default keys definition for a command. Return 0 on error. */\nstatic void clear_default_keys (size_t cmd_ix)\n{\n\tassert (cmd_ix < COMMANDS_NUM);\n\n\tcommands[cmd_ix].default_keys = 0;\n\tcommands[cmd_ix].keys[0] = -1;\n}\n\n/* Add a key to the command defined in the keymap file in line\n * line_num (used only when reporting an error). */\nstatic void add_key (const int line_num, size_t cmd_ix, const char *key_symbol)\n{\n\tint i, key;\n\n\tassert (cmd_ix < COMMANDS_NUM);\n\n\tkey = parse_key (key_symbol);\n\tif (key == -1)\n\t\tkeymap_parse_error (line_num, \"bad key sequence\");\n\n\tclear_default_key (key);\n\n\tfor (i = commands[cmd_ix].default_keys;\n\t     commands[cmd_ix].keys[i] != -1;\n\t     i += 1) {\n\t\tif (commands[cmd_ix].keys[i] == key)\n\t\t\treturn;\n\t}\n\n\tif (i == ARRAY_SIZE(commands[cmd_ix].keys) - 1)\n\t\tkeymap_parse_error (line_num, \"too many keys defined\");\n\n\tcommands[cmd_ix].keys[i] = key;\n\tcommands[cmd_ix].keys[i + 1] = -1;\n}\n\n/* Find command entry by command name; return COMMANDS_NUM if not found. */\nstatic size_t find_command_name (const char *command)\n{\n\tsize_t result;\n\n\tfor (result = 0; result < COMMANDS_NUM; result += 1) {\n\t\tif (!(strcasecmp(commands[result].name, command)))\n\t\t\tbreak;\n\t}\n\n\treturn result;\n}\n\n/* Load a key map from the file. */\nstatic void load_key_map (const char *file_name)\n{\n\tFILE *file;\n\tchar *line;\n\tint line_num = 0;\n\tsize_t cmd_ix;\n\n\tif (!(file = fopen(file_name, \"r\")))\n\t\tfatal (\"Can't open keymap file: %s\", xstrerror (errno));\n\n\t/* Read lines in format:\n\t * COMMAND = KEY [KEY ...]\n\t * Blank lines and beginning with # are ignored, see example_keymap. */\n\twhile ((line = read_line(file))) {\n\t\tchar *command, *tmp, *key;\n\n\t\tline_num++;\n\t\tif (line[0] == '#' || !(command = strtok(line, \" \\t\"))) {\n\n\t\t\t/* empty line or a comment */\n\t\t\tfree (line);\n\t\t\tcontinue;\n\t\t}\n\n\t\tcmd_ix = find_command_name (command);\n\t\tif (cmd_ix == COMMANDS_NUM)\n\t\t\tkeymap_parse_error (line_num, \"unknown command\");\n\n\t\ttmp = strtok(NULL, \" \\t\");\n\t\tif (!tmp || (strcmp(tmp, \"=\") && strcmp(tmp, \"+=\")))\n\t\t\tkeymap_parse_error (line_num, \"expected '=' or '+='\");\n\n\t\tif (strcmp(tmp, \"+=\")) {\n\t\t\tif (commands[cmd_ix].keys[commands[cmd_ix].default_keys] != -1)\n\t\t\t\tkeymap_parse_error (line_num, \"command previously bound\");\n\t\t\tclear_default_keys (cmd_ix);\n\t\t}\n\n\t\twhile ((key = strtok(NULL, \" \\t\")))\n\t\t\tadd_key (line_num, cmd_ix, key);\n\n\t\tfree (line);\n\t}\n\n\tfclose (file);\n}\n\n/* Get a nice key name.\n * Returned memory may be static. */\nstatic char *get_key_name (const int key)\n{\n\tsize_t i;\n\tstatic char key_str[4];\n\n\t/* Search for special keys */\n\tfor (i = 0; i < SPECIAL_KEYS_NUM; i += 1) {\n\t\tif (special_keys[i].key == key)\n\t\t\treturn special_keys[i].name;\n\t}\n\n\t/* CTRL combination */\n\tif (!(key & ~CTRL_KEY_CODE)) {\n\t\tkey_str[0] = '^';\n\t\tkey_str[1] = key + 0x60;\n\t\tkey_str[2] = 0;\n\n\t\treturn key_str;\n\t}\n\n\t/* Meta keys */\n\tif (key & META_KEY_FLAG) {\n\t\tstrcpy (key_str, \"M-\");\n\t\tkey_str[2] = key & ~META_KEY_FLAG;\n\t\tkey_str[3] = 0;\n\n\t\treturn key_str;\n\t}\n\n\t/* Normal key */\n\tkey_str[0] = key;\n\tkey_str[1] = 0;\n\n\treturn key_str;\n}\n\n/* Check if keys for cmd1 and cmd2 are different, if not, issue an error. */\nstatic void compare_keys (struct command *cmd1, struct command *cmd2)\n{\n\tint i = 0;\n\n\twhile (cmd1->keys[i] != -1) {\n\t\tint j = 0;\n\n\t\twhile (cmd2->keys[j] != -1 && cmd2->keys[j] != cmd1->keys[i])\n\t\t\tj++;\n\t\tif (cmd2->keys[j] != -1)\n\t\t\tfatal (\"Key %s is defined for %s and %s!\",\n\t\t\t\t\tget_key_name(cmd2->keys[j]),\n\t\t\t\t\tcmd1->name, cmd2->name);\n\t\ti++;\n\t}\n}\n\n/* Check that no key is defined more than once. */\nstatic void check_keys ()\n{\n\tsize_t i, j;\n\n\tfor (i = 0; i < COMMANDS_NUM; i += 1) {\n\t\tfor (j = 0; j < COMMANDS_NUM; j += 1) {\n\t\t\tif (j != i && commands[i].context == commands[j].context)\n\t\t\t\tcompare_keys (&commands[i], &commands[j]);\n\t\t}\n\t}\n}\n\n/* Return a string contains the list of keys used for command.\n * Returned memory is static. */\nstatic char *get_command_keys (const int idx)\n{\n\tstatic char keys[64];\n\tint i = 0;\n\n\tkeys[0] = 0;\n\n\twhile (commands[idx].keys[i] != -1) {\n\t\tstrncat (keys, get_key_name(commands[idx].keys[i]),\n\t\t\t\tsizeof(keys) - strlen(keys) - 1);\n\t\tstrncat (keys, \" \", sizeof(keys) - strlen(keys) - 1);\n\t\ti++;\n\t}\n\n\t/* strip the last space */\n\tif (keys[0] != 0)\n\t\tkeys[strlen (keys) - 1] = 0;\n\n\treturn keys;\n}\n\n/* Make the help message for keys. */\nstatic void make_help ()\n{\n\tsize_t i;\n\tconst char unassigned[] = \" [unassigned]\";\n\n\tfor (i = 0; i < COMMANDS_NUM; i += 1) {\n\t\tsize_t len;\n\n\t\tlen = HELP_INDENT + strlen (commands[i].help) + 1;\n\t\tif (commands[i].keys[0] == -1)\n\t\t\tlen += strlen (unassigned);\n\t\thelp[i] = xcalloc (sizeof(char), len);\n\t\tstrncpy (help[i], get_command_keys(i), HELP_INDENT);\n\t\tif (strlen(help[i]) < HELP_INDENT)\n\t\t\tmemset (help[i] + strlen(help[i]), ' ',\n\t\t\t\t\tHELP_INDENT - strlen(help[i]));\n\t\tstrcpy (help[i] + HELP_INDENT, commands[i].help);\n\t\tif (commands[i].keys[0] == -1)\n\t\t\tstrcat (help[i], unassigned);\n\t}\n}\n\n/* Load key map. Set default keys if necessary. */\nvoid keys_init ()\n{\n\tchar *file = find_keymap_file ();\n\n\tif (file) {\n\t\tload_key_map (file);\n\t\tcheck_keys ();\n\t}\n\n\tmake_help ();\n}\n\n/* Free the help message. */\nvoid keys_cleanup ()\n{\n\tsize_t i;\n\n\tfor (i = 0; i < COMMANDS_NUM; i += 1)\n\t\tfree (help[i]);\n}\n\n/* Return an array of strings with the keys help. The number of lines is put\n * in num. */\nchar **get_keys_help (int *num)\n{\n\t*num = (int) COMMANDS_NUM;\n\treturn help;\n}\n\n/* Find command entry by key command; return COMMANDS_NUM if not found. */\nstatic size_t find_command_cmd (const enum key_cmd cmd)\n{\n\tsize_t result;\n\n\tfor (result = 0; result < COMMANDS_NUM; result += 1) {\n\t\tif (commands[result].cmd == cmd)\n\t\t\tbreak;\n\t}\n\n\treturn result;\n}\n\n/* Return true iff the help key is still 'h'. */\nbool is_help_still_h ()\n{\n\tsize_t cmd_ix;\n\n\tcmd_ix = find_command_cmd (KEY_CMD_HELP);\n\n\tassert (cmd_ix < COMMANDS_NUM);\n\n\treturn commands[cmd_ix].keys[0] == 'h';\n}\n"
  },
  {
    "path": "keys.h",
    "content": "#ifndef KEYS_H\n#define KEYS_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nenum key_cmd\n{\n\tKEY_CMD_QUIT_CLIENT,\n\tKEY_CMD_GO,\n\tKEY_CMD_MENU_DOWN,\n\tKEY_CMD_MENU_UP,\n\tKEY_CMD_MENU_NPAGE,\n\tKEY_CMD_MENU_PPAGE,\n\tKEY_CMD_MENU_FIRST,\n\tKEY_CMD_MENU_LAST,\n\tKEY_CMD_QUIT,\n\tKEY_CMD_STOP,\n\tKEY_CMD_NEXT,\n\tKEY_CMD_PREVIOUS,\n\tKEY_CMD_PAUSE,\n\tKEY_CMD_TOGGLE_READ_TAGS,\n\tKEY_CMD_TOGGLE_REPEAT,\n\tKEY_CMD_TOGGLE_AUTO_NEXT,\n\tKEY_CMD_TOGGLE_MENU,\n\tKEY_CMD_TOGGLE_LAYOUT,\n\tKEY_CMD_TOGGLE_PERCENT,\n\tKEY_CMD_PLIST_ADD_FILE,\n\tKEY_CMD_PLIST_CLEAR,\n\tKEY_CMD_PLIST_ADD_DIR,\n\tKEY_CMD_PLIST_REMOVE_DEAD_ENTRIES,\n\tKEY_CMD_MIXER_DEC_1,\n\tKEY_CMD_MIXER_INC_1,\n\tKEY_CMD_MIXER_DEC_5,\n\tKEY_CMD_MIXER_INC_5,\n\tKEY_CMD_SEEK_FORWARD,\n\tKEY_CMD_SEEK_BACKWARD,\n\tKEY_CMD_SEEK_FORWARD_5,\n\tKEY_CMD_SEEK_BACKWARD_5,\n\tKEY_CMD_HELP,\n\tKEY_CMD_HIDE_MESSAGE,\n\tKEY_CMD_REFRESH,\n\tKEY_CMD_RELOAD,\n\tKEY_CMD_TOGGLE_SHOW_HIDDEN_FILES,\n\tKEY_CMD_GO_MUSIC_DIR,\n\tKEY_CMD_PLIST_DEL,\n\tKEY_CMD_MENU_SEARCH,\n\tKEY_CMD_PLIST_SAVE,\n\tKEY_CMD_TOGGLE_SHOW_FORMAT,\n\tKEY_CMD_TOGGLE_SHOW_TIME,\n\tKEY_CMD_GO_TO_PLAYING_FILE,\n\tKEY_CMD_GO_DIR,\n\tKEY_CMD_GO_DIR_UP,\n\tKEY_CMD_TOGGLE_SHUFFLE,\n\tKEY_CMD_CANCEL,\n\tKEY_CMD_GO_URL,\n\tKEY_CMD_VOLUME_10,\n\tKEY_CMD_VOLUME_20,\n\tKEY_CMD_VOLUME_30,\n\tKEY_CMD_VOLUME_40,\n\tKEY_CMD_VOLUME_50,\n\tKEY_CMD_VOLUME_60,\n\tKEY_CMD_VOLUME_70,\n\tKEY_CMD_VOLUME_80,\n\tKEY_CMD_VOLUME_90,\n\tKEY_CMD_MARK_START,\n\tKEY_CMD_MARK_END,\n \tKEY_CMD_FAST_DIR_1,\n \tKEY_CMD_FAST_DIR_2,\n \tKEY_CMD_FAST_DIR_3,\n \tKEY_CMD_FAST_DIR_4,\n \tKEY_CMD_FAST_DIR_5,\n \tKEY_CMD_FAST_DIR_6,\n \tKEY_CMD_FAST_DIR_7,\n \tKEY_CMD_FAST_DIR_8,\n \tKEY_CMD_FAST_DIR_9,\n \tKEY_CMD_FAST_DIR_10,\n\tKEY_CMD_TOGGLE_MIXER,\n\tKEY_CMD_HISTORY_UP,\n\tKEY_CMD_HISTORY_DOWN,\n\tKEY_CMD_DELETE_START,\n\tKEY_CMD_DELETE_END,\n\tKEY_CMD_PLIST_MOVE_UP,\n\tKEY_CMD_PLIST_MOVE_DOWN,\n\tKEY_CMD_ADD_STREAM,\n\tKEY_CMD_THEME_MENU,\n\tKEY_CMD_EXEC1,\n\tKEY_CMD_EXEC2,\n\tKEY_CMD_EXEC3,\n\tKEY_CMD_EXEC4,\n\tKEY_CMD_EXEC5,\n\tKEY_CMD_EXEC6,\n\tKEY_CMD_EXEC7,\n\tKEY_CMD_EXEC8,\n\tKEY_CMD_EXEC9,\n\tKEY_CMD_EXEC10,\n\tKEY_CMD_TOGGLE_PLAYLIST_FULL_PATHS,\n\tKEY_CMD_TOGGLE_SOFTMIXER,\n\tKEY_CMD_TOGGLE_EQUALIZER,\n\tKEY_CMD_EQUALIZER_REFRESH,\n\tKEY_CMD_EQUALIZER_PREV,\n\tKEY_CMD_EQUALIZER_NEXT,\n\tKEY_CMD_TOGGLE_MAKE_MONO,\n\tKEY_CMD_LYRICS,\n\tKEY_CMD_QUEUE_TOGGLE_FILE,\n\tKEY_CMD_QUEUE_CLEAR,\n\tKEY_CMD_WRONG\n};\n\n/* Key context is the place where the user presses a key. A key can have\n * different meanings in different places. */\nenum key_context\n{\n\tCON_MENU,\n\tCON_ENTRY_SEARCH,\n\tCON_ENTRY\n};\n\n#ifndef KEY_ESCAPE\n# define KEY_ESCAPE\t27\n#endif\n\n#define META_KEY_FLAG\t0x80\n#define CTRL_KEY_CODE\t0x1F\n\nstruct iface_key;\nenum key_cmd get_key_cmd (const enum key_context context, const struct iface_key *key);\nvoid keys_init ();\nvoid keys_cleanup ();\nchar **get_keys_help (int *num);\nbool is_help_still_h ();\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "lists.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2009 Damian Pietras <daper@daper.net> and John Fitzgerald\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <assert.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <strings.h>\n\n#include \"common.h\"\n#include \"lists.h\"\n\nstruct lists_strs {\n\tint size;          /* Number of strings on the list */\n\tint capacity;      /* Number of allocated strings */\n\tchar **strs;\n};\n\n/* Allocate a new list of strings and return its address. */\nlists_t_strs *lists_strs_new (int reserve)\n{\n\tlists_t_strs *result;\n\n\tassert (reserve >= 0);\n\n\tresult = (lists_t_strs *) xmalloc (sizeof (lists_t_strs));\n\tresult->size = 0;\n\tresult->capacity = (reserve ? reserve : 64);\n\tresult->strs = (char **) xcalloc (sizeof (char *), result->capacity);\n\n\treturn result;\n}\n\n/* Clear a list to an empty state. */\nvoid lists_strs_clear (lists_t_strs *list)\n{\n\tint ix;\n\n\tassert (list);\n\n\tfor (ix = 0; ix < list->size; ix += 1)\n\t\tfree ((void *) list->strs[ix]);\n\tlist->size = 0;\n}\n\n/* Free all storage associated with a list of strings. */\nvoid lists_strs_free (lists_t_strs *list)\n{\n\tassert (list);\n\n\tlists_strs_clear (list);\n\tfree (list->strs);\n\tfree (list);\n}\n\n/* Return the number of strings in a list. */\nint lists_strs_size (const lists_t_strs *list)\n{\n\tassert (list);\n\n\treturn list->size;\n}\n\n/* Return the total number of strings which could be held without growing. */\nint lists_strs_capacity (const lists_t_strs *list)\n{\n\tassert (list);\n\n\treturn list->capacity;\n}\n\n/* Return true iff the list has no members. */\nbool lists_strs_empty (const lists_t_strs *list)\n{\n\tassert (list);\n\n\treturn list->size == 0 ? true : false;\n}\n\n/* Given an index, return the string at that position in a list. */\nchar *lists_strs_at (const lists_t_strs *list, int index)\n{\n\tassert (list);\n\tassert (LIMIT(index, list->size));\n\n\treturn list->strs[index];\n}\n\n/* Sort string list into an order determined by caller's comparitor. */\nvoid lists_strs_sort (lists_t_strs *list, lists_t_compare *compare)\n{\n\tassert (list);\n\tassert (compare);\n\n\tqsort (list->strs, list->size, sizeof (char *), compare);\n}\n\n/* Reverse the order of entries in a list. */\nvoid lists_strs_reverse (lists_t_strs *list)\n{\n\tint ix, iy;\n\n\tassert (list);\n\n\tfor (ix = 0, iy = list->size - 1; ix < iy; ix += 1, iy -= 1) {\n\t\tchar *str;\n\n\t\tstr = list->strs[ix];\n\t\tlist->strs[ix] = list->strs[iy];\n\t\tlist->strs[iy] = str;\n\t}\n}\n\n/* Take a string and push it onto the end of a list\n * (expanding the list if necessary). */\nvoid lists_strs_push (lists_t_strs *list, char *s)\n{\n\tassert (list);\n\tassert (s);\n\n\tif (list->size == list->capacity) {\n\t\tlist->capacity *= 2;\n\t\tlist->strs = (char **) xrealloc (list->strs, list->capacity * sizeof (char *));\n\t}\n\n\tlist->strs[list->size] = s;\n\tlist->size += 1;\n}\n\n/* Remove the last string on the list and return it, or NULL if the list\n * is empty. */\nchar *lists_strs_pop (lists_t_strs *list)\n{\n\tchar *result;\n\n\tassert (list);\n\n\tresult = NULL;\n\tif (list->size > 0) {\n\t\tlist->size -= 1;\n\t\tresult = list->strs[list->size];\n\t}\n\n\treturn result;\n}\n\n/* Replace the nominated string with a new one and return the old one. */\nchar *lists_strs_swap (lists_t_strs *list, int index, char *s)\n{\n\tchar *result;\n\n\tassert (list);\n\tassert (LIMIT(index, list->size));\n\tassert (s);\n\n\tresult = list->strs[index];\n\tlist->strs[index] = s;\n\n\treturn result;\n}\n\n/* Copy a string and append it to the end of a list. */\nvoid lists_strs_append (lists_t_strs *list, const char *s)\n{\n\tchar *str;\n\n\tassert (list);\n\tassert (s);\n\n\tstr = xstrdup (s);\n\tlists_strs_push (list, str);\n}\n\n/* Remove a string from the end of the list and free it. */\nvoid lists_strs_remove (lists_t_strs *list)\n{\n\tchar *str;\n\n\tassert (list);\n\n\tstr = lists_strs_pop (list);\n\tif (str)\n\t\tfree (str);\n}\n\n/* Replace the nominated string with a copy of the new one\n * and free the old one. */\nvoid lists_strs_replace (lists_t_strs *list, int index, char *s)\n{\n\tchar *str;\n\n\tassert (list);\n\tassert (LIMIT(index, list->size));\n\n\tstr = xstrdup (s);\n\tstr = lists_strs_swap (list, index, str);\n\tfree (str);\n}\n\n/* Split a string at any delimiter in given string.  The resulting segments\n * are appended to the given string list.  Returns the number of tokens\n * appended. */\nint lists_strs_split (lists_t_strs *list, const char *s, const char *delim)\n{\n\tint result;\n\tchar *str, *token, *saveptr;\n\n\tassert (list);\n\tassert (s);\n\tassert (delim);\n\n\tresult = 0;\n\tstr = xstrdup (s);\n\ttoken = strtok_r (str, delim, &saveptr);\n\twhile (token) {\n\t\tresult += 1;\n\t\tlists_strs_append (list, token);\n\t\ttoken = strtok_r (NULL, delim, &saveptr);\n\t}\n\n\tfree (str);\n\treturn result;\n}\n\n/* Tokenise a string and append the tokens to the list.\n * Returns the number of tokens appended. */\nint lists_strs_tokenise (lists_t_strs *list, const char *s)\n{\n\tint result;\n\n\tassert (list);\n\tassert (s);\n\n\tresult = lists_strs_split (list, s, \" \\t\");\n\n\treturn result;\n}\n\n/* Return the concatenation of all the strings in a list using the\n * given format for each, or NULL if the list is empty. */\nGCC_DIAG_OFF(format-nonliteral)\nchar *lists_strs_fmt (const lists_t_strs *list, const char *fmt)\n{\n\tint len, ix, rc;\n\tchar *result, *ptr;\n\n\tassert (list);\n\tassert (strstr (fmt, \"%s\"));\n\n\tresult = NULL;\n\tif (!lists_strs_empty (list)) {\n\t\tlen = 0;\n\t\tfor (ix = 0; ix < lists_strs_size (list); ix += 1)\n\t\t\tlen += strlen (lists_strs_at (list, ix));\n\t\tlen += ix * (strlen (fmt) - 2);\n\n\t\tptr = result = xmalloc (len + 1);\n\t\tfor (ix = 0; ix < lists_strs_size (list); ix += 1) {\n\t\t\trc = snprintf (ptr, len + 1, fmt, lists_strs_at (list, ix));\n\t\t\tif (rc > len)\n\t\t\t\tfatal (\"Allocated string area was too small!\");\n\t\t\tlen -= rc;\n\t\t\tptr += rc;\n\t\t}\n\t}\n\n\treturn result;\n}\nGCC_DIAG_ON(format-nonliteral)\n\n/* Return the concatenation of all the strings in a list, or NULL\n * if the list is empty. */\nchar *lists_strs_cat (const lists_t_strs *list)\n{\n\tchar *result;\n\n\tassert (list);\n\n\tresult = lists_strs_fmt (list, \"%s\");\n\n\treturn result;\n}\n\n/* Return a \"snapshot\" of the given string list.  The returned memory is a\n * null-terminated list of pointers to the given list's strings copied into\n * memory allocated after the pointer list.  This list is suitable for passing\n * to functions which take such a list as an argument (e.g., execv()).\n * Invoking free() on the returned pointer also frees the strings. */\nchar **lists_strs_save (const lists_t_strs *list)\n{\n\tint ix, size;\n\tchar *ptr, **result;\n\n\tassert (list);\n\n\tsize = 0;\n\tfor (ix = 0; ix < lists_strs_size (list); ix += 1)\n\t\tsize += strlen (lists_strs_at (list, ix)) + 1;\n\tsize += sizeof (char *) * (lists_strs_size (list) + 1);\n\tresult = (char **) xmalloc (size);\n\tptr = (char *) (result + lists_strs_size (list) + 1);\n\tfor (ix = 0; ix < lists_strs_size (list); ix += 1) {\n\t\tstrcpy (ptr, lists_strs_at (list, ix));\n\t\tresult[ix] = ptr;\n\t\tptr += strlen (ptr) + 1;\n\t}\n\tresult[ix] = NULL;\n\n\treturn result;\n}\n\n/* Reload saved strings into a list.  The reloaded strings are appended\n * to the list.  The number of items reloaded is returned. */\nint lists_strs_load (lists_t_strs *list, const char **saved)\n{\n\tint size;\n\n\tassert (list);\n\tassert (saved);\n\n\tsize = lists_strs_size (list);\n\twhile (*saved)\n\t\tlists_strs_append (list, *saved++);\n\n\treturn lists_strs_size (list) - size;\n}\n\n/* Given a string, return the index of the first list entry which matches\n * it.  If not found, return the total number of entries.\n * The comparison is case-insensitive. */\nint lists_strs_find (lists_t_strs *list, const char *sought)\n{\n\tint result;\n\n\tassert (list);\n\tassert (sought);\n\n\tfor (result = 0; result < lists_strs_size (list); result += 1) {\n\t\tif (!strcasecmp (lists_strs_at (list, result), sought))\n\t\t\tbreak;\n\t}\n\n\treturn result;\n}\n\n/* Given a string, return true iff it exists in the list. */\nbool lists_strs_exists (lists_t_strs *list, const char *sought)\n{\n\tbool result = false;\n\n\tassert (list);\n\tassert (sought);\n\n\tif (lists_strs_find (list, sought) < lists_strs_size (list))\n\t\tresult = true;\n\n\treturn result;\n}\n"
  },
  {
    "path": "lists.h",
    "content": "#ifndef LISTS_H\n#define LISTS_H\n\n#include \"common.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef struct lists_strs lists_t_strs;\ntypedef int lists_t_compare (const void *, const void *);\n\n/* List administration functions. */\nlists_t_strs *lists_strs_new (int reserve);\nvoid lists_strs_clear (lists_t_strs *list);\nvoid lists_strs_free (lists_t_strs *list);\nint lists_strs_size (const lists_t_strs *list);\nint lists_strs_capacity (const lists_t_strs *list);\nbool lists_strs_empty (const lists_t_strs *list);\n\n/* List member access functions. */\nchar *lists_strs_at (const lists_t_strs *list, int index);\n\n/* List mutating functions. */\nvoid lists_strs_sort (lists_t_strs *list, lists_t_compare *compare);\nvoid lists_strs_reverse (lists_t_strs *list);\n\n/* Ownership transferring functions. */\nvoid lists_strs_push (lists_t_strs *list, char *s);\nchar *lists_strs_pop (lists_t_strs *list);\nchar *lists_strs_swap (lists_t_strs *list, int index, char *s);\n\n/* Ownership preserving functions. */\nvoid lists_strs_append (lists_t_strs *list, const char *s);\nvoid lists_strs_remove (lists_t_strs *list);\nvoid lists_strs_replace (lists_t_strs *list, int index, char *s);\n\n/* Helper functions. */\nint lists_strs_split (lists_t_strs *list, const char *s, const char *delim);\nint lists_strs_tokenise (lists_t_strs *list, const char *s);\nchar *lists_strs_fmt (const lists_t_strs *list, const char *fmt);\nchar *lists_strs_cat (const lists_t_strs *list);\nchar **lists_strs_save (const lists_t_strs *list);\nint lists_strs_load (lists_t_strs *list, const char **saved);\nint lists_strs_find (lists_t_strs *list, const char *sought);\nbool lists_strs_exists (lists_t_strs *list, const char *sought);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "log.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <stdint.h>\n#include <assert.h>\n#include <pthread.h>\n#include <time.h>\n#include <errno.h>\n#include <signal.h>\n\n#include \"common.h\"\n#include \"lists.h\"\n#include \"log.h\"\n#include \"options.h\"\n\n#ifndef NDEBUG\nstatic FILE *logfp = NULL; /* logging file stream */\n\nstatic enum {\n\tUNINITIALISED,\n\tBUFFERING,\n\tLOGGING\n} logging_state = UNINITIALISED;\n\nstatic lists_t_strs *buffered_log = NULL;\nstatic int log_records_spilt = 0;\n\nstatic lists_t_strs *circular_log = NULL;\nstatic int circular_ptr = 0;\n\nstatic pthread_mutex_t logging_mtx = PTHREAD_MUTEX_INITIALIZER;\n\nstatic struct {\n\tint sig;\n\tconst char *name;\n\tvolatile uint64_t raised;\n\tuint64_t logged;\n} sig_info[] = {\n\t{SIGINT, \"SIGINT\", 0, 0},\n\t{SIGHUP, \"SIGHUP\", 0, 0},\n\t{SIGQUIT, \"SIGQUIT\", 0, 0},\n\t{SIGTERM, \"SIGTERM\", 0, 0},\n\t{SIGCHLD, \"SIGCHLD\", 0, 0},\n#ifdef SIGWINCH\n\t{SIGWINCH, \"SIGWINCH\", 0, 0},\n#endif\n\t{0, \"SIG other\", 0, 0}\n};\n#endif\n\n#ifndef NDEBUG\nvoid log_signal (int sig)\n{\n\tint ix = 0;\n\n\twhile (sig_info[ix].sig && sig_info[ix].sig != sig)\n\t\tix += 1;\n\n\tsig_info[ix].raised += 1;\n}\n#endif\n\n#ifndef NDEBUG\nstatic inline void flush_log (void)\n{\n\tint rc;\n\n\tif (logfp) {\n\t\tdo {\n\t\t\trc = fflush (logfp);\n\t\t} while (rc != 0 && errno == EINTR);\n\t}\n}\n#endif\n\n#ifndef NDEBUG\nstatic void locked_logit (const char *file, const int line,\n                          const char *function, const char *msg)\n{\n\tint len;\n\tchar *str, time_str[20];\n\tstruct timespec utc_time;\n\ttime_t tv_sec;\n\tstruct tm tm_time;\n\tconst char fmt[] = \"%s.%06ld: %s:%d %s(): %s\\n\";\n\n\tassert (logging_state == BUFFERING || logging_state == LOGGING);\n\tassert (logging_state != BUFFERING || !logfp);\n\tassert (logging_state != BUFFERING || !circular_log);\n\tassert (logging_state != LOGGING || logfp || !circular_log);\n\n\tif (logging_state == LOGGING && !logfp)\n\t\treturn;\n\n\tget_realtime (&utc_time);\n\ttv_sec = utc_time.tv_sec;\n\tlocaltime_r (&tv_sec, &tm_time);\n\tstrftime (time_str, sizeof (time_str), \"%b %e %T\", &tm_time);\n\n\tif (logfp && !circular_log) {\n\t\tfprintf (logfp, fmt, time_str, utc_time.tv_nsec / 1000L,\n\t\t                     file, line, function, msg);\n\t\treturn;\n\t}\n\n\tlen = snprintf (NULL, 0, fmt, time_str, utc_time.tv_nsec / 1000L,\n\t                              file, line, function, msg);\n\tstr = xmalloc (len + 1);\n\tsnprintf (str, len + 1, fmt, time_str, utc_time.tv_nsec / 1000L,\n\t                             file, line, function, msg);\n\n\tif (logging_state == BUFFERING) {\n\t\tlists_strs_push (buffered_log, str);\n\t\treturn;\n\t}\n\n\tassert (circular_log);\n\n\tif (circular_ptr == lists_strs_capacity (circular_log))\n\t\tcircular_ptr = 0;\n\tif (circular_ptr < lists_strs_size (circular_log))\n\t\tfree (lists_strs_swap (circular_log, circular_ptr, str));\n\telse\n\t\tlists_strs_push (circular_log, str);\n\tcircular_ptr += 1;\n}\n#endif\n\n#ifndef NDEBUG\nstatic void log_signals_raised (void)\n{\n\tsize_t ix;\n\n    for (ix = 0; ix < ARRAY_SIZE(sig_info); ix += 1) {\n\t\twhile (sig_info[ix].raised > sig_info[ix].logged) {\n\t\t\tlocked_logit (__FILE__, __LINE__, __func__, sig_info[ix].name);\n\t\t\tsig_info[ix].logged += 1;\n\t\t}\n\t}\n}\n#endif\n\n/* Put something into the log.  If built with logging disabled,\n * this function is provided as a stub so independant plug-ins\n * configured with logging enabled can still resolve it. */\nvoid internal_logit (const char *file LOGIT_ONLY,\n                     const int line LOGIT_ONLY,\n                     const char *function LOGIT_ONLY,\n                     const char *format LOGIT_ONLY, ...)\n{\n#ifndef NDEBUG\n\tint saved_errno = errno;\n\tchar *msg;\n\tva_list va;\n\n\tLOCK(logging_mtx);\n\n\tif (!logfp) {\n\t\tswitch (logging_state) {\n\t\tcase UNINITIALISED:\n\t\t\tbuffered_log = lists_strs_new (128);\n\t\t\tlogging_state = BUFFERING;\n\t\t\tbreak;\n\t\tcase BUFFERING:\n\t\t\t/* Don't let storage run away on us. */\n\t\t\tif (lists_strs_size (buffered_log) < lists_strs_capacity (buffered_log))\n\t\t\t\tbreak;\n\t\t\tlog_records_spilt += 1;\n\t\tcase LOGGING:\n\t\t\tgoto end;\n\t\t}\n\t}\n\n\tlog_signals_raised ();\n\n\tva_start (va, format);\n\tmsg = format_msg_va (format, va);\n\tva_end (va);\n\tlocked_logit (file, line, function, msg);\n\tfree (msg);\n\n\tflush_log ();\n\n\tlog_signals_raised ();\n\nend:\n\tUNLOCK(logging_mtx);\n\n\terrno = saved_errno;\n#endif\n}\n\n/* Initialize logging stream */\nvoid log_init_stream (FILE *f LOGIT_ONLY, const char *fn LOGIT_ONLY)\n{\n#ifndef NDEBUG\n\tchar *msg;\n\n\tLOCK(logging_mtx);\n\n\tlogfp = f;\n\n\tif (logging_state == BUFFERING) {\n\t\tif (logfp) {\n\t\t\tint ix;\n\n\t\t\tfor (ix = 0; ix < lists_strs_size (buffered_log); ix += 1)\n\t\t\t\tfprintf (logfp, \"%s\", lists_strs_at (buffered_log, ix));\n\t\t}\n\t\tlists_strs_free (buffered_log);\n\t\tbuffered_log = NULL;\n\t}\n\n\tlogging_state = LOGGING;\n\tif (!logfp)\n\t\tgoto end;\n\n\tmsg = format_msg (\"Writing log to: %s\", fn);\n\tlocked_logit (__FILE__, __LINE__, __func__, msg);\n\tfree (msg);\n\n\tif (log_records_spilt > 0) {\n\t\tmsg = format_msg (\"%d log records spilt\", log_records_spilt);\n\t\tlocked_logit (__FILE__, __LINE__, __func__, msg);\n\t\tfree (msg);\n\t}\n\n\tflush_log ();\n\nend:\n\tUNLOCK(logging_mtx);\n#endif\n}\n\n/* Start circular logging (if enabled). */\nvoid log_circular_start ()\n{\n#ifndef NDEBUG\n\tint circular_size;\n\n\tassert (logging_state == LOGGING);\n\tassert (!circular_log);\n\n\tif (!logfp)\n\t\treturn;\n\n\tcircular_size = options_get_int (\"CircularLogSize\");\n\tif (circular_size > 0) {\n\t\tLOCK(logging_mtx);\n\n\t\tcircular_log = lists_strs_new (circular_size);\n\t\tcircular_ptr = 0;\n\n\t\tUNLOCK(logging_mtx);\n\t}\n#endif\n}\n\n/* Internal circular log reset. */\n#ifndef NDEBUG\nstatic inline void locked_circular_reset ()\n{\n\tlists_strs_clear (circular_log);\n\tcircular_ptr = 0;\n}\n#endif\n\n/* Reset the circular log (if enabled). */\nvoid log_circular_reset ()\n{\n#ifndef NDEBUG\n\tassert (logging_state == LOGGING);\n\n\tif (!circular_log)\n\t\treturn;\n\n\tLOCK(logging_mtx);\n\n\tlocked_circular_reset ();\n\n\tUNLOCK(logging_mtx);\n#endif\n}\n\n/* Write circular log (if enabled) to the log file. */\nvoid log_circular_log ()\n{\n#ifndef NDEBUG\n\tint ix;\n\n\tassert (logging_state == LOGGING && (logfp || !circular_log));\n\n\tif (!circular_log)\n\t\treturn;\n\n\tLOCK(logging_mtx);\n\n\tfprintf (logfp, \"\\n* Circular Log Starts *\\n\\n\");\n\n\tfor (ix = circular_ptr; ix < lists_strs_size (circular_log); ix += 1)\n\t\tfprintf (logfp, \"%s\", lists_strs_at (circular_log, ix));\n\n\tfflush (logfp);\n\n\tfor (ix = 0; ix < circular_ptr; ix += 1)\n\t\tfprintf (logfp, \"%s\", lists_strs_at (circular_log, ix));\n\n\tfprintf (logfp, \"\\n* Circular Log Ends *\\n\\n\");\n\n\tfflush (logfp);\n\n\tlocked_circular_reset ();\n\n\tUNLOCK(logging_mtx);\n#endif\n}\n\n/* Stop circular logging (if enabled). */\nvoid log_circular_stop ()\n{\n#ifndef NDEBUG\n\tassert (logging_state == LOGGING);\n\n\tif (!circular_log)\n\t\treturn;\n\n\tLOCK(logging_mtx);\n\n\tlists_strs_free (circular_log);\n\tcircular_log = NULL;\n\tcircular_ptr = 0;\n\n\tUNLOCK(logging_mtx);\n#endif\n}\n\nvoid log_close ()\n{\n#ifndef NDEBUG\n\tLOCK(logging_mtx);\n\n\tif (!(logfp == stdout || logfp == stderr || logfp == NULL)) {\n\t\tfclose (logfp);\n\t\tlogfp = NULL;\n\t}\n\n\tif (buffered_log) {\n\t\tlists_strs_free (buffered_log);\n\t\tbuffered_log = NULL;\n\t}\n\n\tlog_records_spilt = 0;\n\n\tUNLOCK(logging_mtx);\n#endif\n}\n"
  },
  {
    "path": "log.h",
    "content": "#ifndef LOG_H\n#define LOG_H\n\n#include <stdio.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* Suppress overly-enthusiastic GNU variadic macro extensions warning. */\n#if defined(__clang__) && HAVE_VARIADIC_MACRO_WARNING\n# pragma clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"\n#endif\n\n#ifdef DEBUG\n# define debug logit\n#else\n# define debug(...)  do {} while (0)\n#endif\n\n#ifndef NDEBUG\n# define logit(...) \\\n\tinternal_logit (__FILE__, __LINE__, __func__, ## __VA_ARGS__)\n#else\n# define logit(...)  do {} while (0)\n#endif\n\n#ifndef STRERROR_FN\n# define STRERROR_FN xstrerror\n#endif\n\n#ifndef NDEBUG\n#define log_errno(format, errnum) \\\n\tdo { \\\n\t\tchar *err##__LINE__ = STRERROR_FN (errnum); \\\n\t\tlogit (format \": %s\", err##__LINE__); \\\n\t\tfree (err##__LINE__); \\\n\t} while (0)\n#else\n# define log_errno(...) do {} while (0)\n#endif\n\nvoid internal_logit (const char *file, const int line, const char *function,\n\t\tconst char *format, ...) ATTR_PRINTF(4, 5);\n\n#ifndef NDEBUG\n# define LOGIT_ONLY\n#else\n# define LOGIT_ONLY ATTR_UNUSED\n#endif\n\n#if !defined(NDEBUG) && defined(DEBUG)\n# define DEBUG_ONLY\n#else\n# define DEBUG_ONLY ATTR_UNUSED\n#endif\n\nvoid log_init_stream (FILE *f, const char *fn);\nvoid log_circular_start ();\nvoid log_circular_log ();\nvoid log_circular_reset ();\nvoid log_circular_stop ();\nvoid log_close ();\n\n#ifndef NDEBUG\nvoid log_signal (int sig);\n#else\n# define log_signal(...) do {} while (0)\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "lyrics.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2008-2009 Geraud Le Falher and John Fitzgerald\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <assert.h>\n#include <errno.h>\n#include <string.h>\n\n#include \"common.h\"\n#include \"files.h\"\n#include \"log.h\"\n#include \"options.h\"\n#include \"lists.h\"\n#include \"lyrics.h\"\n\nstatic lists_t_strs *raw_lyrics = NULL;\nstatic const char *lyrics_message = NULL;\n\n/* Return the list of lyrics lines, or NULL if none are loaded. */\nlists_t_strs *lyrics_lines_get (void)\n{\n\treturn raw_lyrics;\n}\n\n/* Store new lyrics lines as supplied. */\nvoid lyrics_lines_set (lists_t_strs *lines)\n{\n\tassert (!raw_lyrics);\n\tassert (lines);\n\n\traw_lyrics = lines;\n\tlyrics_message = NULL;\n}\n\n/* Return a list of lyrics lines loaded from a file, or NULL on error. */\nlists_t_strs *lyrics_load_file (const char *filename)\n{\n\tint text_plain;\n\tFILE *lyrics_file = NULL;\n\tchar *mime, *line;\n\tlists_t_strs *result;\n\n\tassert (filename);\n\n\tlyrics_message = \"[No lyrics file!]\";\n\tif (!file_exists (filename))\n\t\treturn NULL;\n\tmime = file_mime_type (filename);\n\ttext_plain = mime ? !strncmp (mime, \"text/plain\", 10) : 0;\n\tfree (mime);\n\tif (!text_plain)\n\t\treturn NULL;\n\n\tlyrics_file = fopen (filename, \"r\");\n\tif (lyrics_file == NULL) {\n\t\tchar *err = xstrerror (errno);\n\t\tlogit (\"Error reading '%s': %s\", filename, err);\n\t\tfree (err);\n\t\tlyrics_message = \"[Lyrics file cannot be read!]\";\n\t\treturn NULL;\n\t}\n\n\tresult = lists_strs_new (0);\n\twhile ((line = read_line (lyrics_file)) != NULL)\n\t\tlists_strs_push (result, line);\n\tfclose (lyrics_file);\n\n\tlyrics_message = NULL;\n\treturn result;\n}\n\n/* Given an audio's file name, load lyrics from the default lyrics file name. */\nvoid lyrics_autoload (const char *filename)\n{\n\tchar *lyrics_filename, *extn;\n\n\tassert (!raw_lyrics);\n\tassert (lyrics_message);\n\n\tif (filename == NULL) {\n\t\tlyrics_message = \"[No file playing!]\";\n\t\treturn;\n\t}\n\n\tif (!options_get_bool (\"AutoLoadLyrics\")) {\n\t\tlyrics_message = \"[Lyrics not autoloaded!]\";\n\t\treturn;\n\t}\n\n\tif (is_url (filename)) {\n\t\tlyrics_message = \"[Lyrics from URL is not supported!]\";\n\t\treturn;\n\t}\n\n\tlyrics_filename = xstrdup (filename);\n\textn = ext_pos (lyrics_filename);\n\tif (extn) {\n\t\t*--extn = '\\0';\n\t\traw_lyrics = lyrics_load_file (lyrics_filename);\n\t}\n\telse\n\t\tlyrics_message = \"[No lyrics file!]\";\n\n\tfree (lyrics_filename);\n}\n\n/* Given a line, return a centred copy of it. */\nstatic char *centre_line (const char* line, int max)\n{\n\tchar *result;\n\tint len;\n\n\tlen = strlen (line);\n\tif (len < (max - 1)) {\n\t\tint space;\n\n\t\tspace = (max - len) / 2;\n\t\tresult = (char *) xmalloc (space + len + 2);\n\t\tmemset (result, ' ', space);\n\t\tstrcpy (&result[space], line);\n\t\tlen += space;\n\t}\n\telse {\n\t\tresult = (char *) xmalloc (max + 2);\n\t\tstrncpy (result, line, max);\n\t\tlen = max;\n\t}\n\tstrcpy (&result[len], \"\\n\");\n\n\treturn result;\n}\n\n/* Centre all the lines in the lyrics. */\nstatic lists_t_strs *centre_style (lists_t_strs *lines, int unused1 ATTR_UNUSED,\n                                   int width, void *unused2 ATTR_UNUSED)\n{\n\tlists_t_strs *result;\n\tint ix, size;\n\n\tsize = lists_strs_size (lines);\n\tresult = lists_strs_new (size);\n\tfor (ix = 0; ix < size; ix += 1) {\n\t\tchar *old_line, *new_line;\n\n\t\told_line = lists_strs_at (lines, ix);\n\t\tnew_line = centre_line (old_line, width);\n\t\tlists_strs_push (result, new_line);\n\t}\n\n\treturn result;\n}\n\n/* Formatting function information. */\nstatic lyrics_t_formatter *lyrics_formatter = centre_style;\nstatic lyrics_t_reaper *formatter_reaper = NULL;\nstatic void *formatter_data = NULL;\n\n/* Register a new function to be used for formatting.  A NULL formatter\n * resets formatting to the default centred style. */\nvoid lyrics_use_formatter (lyrics_t_formatter formatter,\n                           lyrics_t_reaper reaper, void *data)\n{\n\tif (formatter_reaper)\n\t\tformatter_reaper (formatter_data);\n\n\tif (formatter) {\n\t\tlyrics_formatter = formatter;\n\t\tformatter_reaper = reaper;\n\t\tformatter_data = data;\n\t}\n\telse {\n\t\tlyrics_formatter = centre_style;\n\t\tformatter_reaper = NULL;\n\t\tformatter_data = NULL;\n\t}\n}\n\n/* Return a list of either the formatted lyrics if any are loaded or\n * a centred message. */\nlists_t_strs *lyrics_format (int height, int width)\n{\n\tint ix;\n\tlists_t_strs *result;\n\n\tassert (raw_lyrics || lyrics_message);\n\n\tresult = NULL;\n\n\tif (raw_lyrics) {\n\t\tresult = lyrics_formatter (raw_lyrics, height, width - 1,\n\t\t\t                                                 formatter_data);\n\t\tif (!result)\n\t\t\tlyrics_message = \"[Error formatting lyrics!]\";\n\t}\n\n\tif (!result) {\n\t\tchar *line;\n\n\t\tresult = lists_strs_new (1);\n\t\tline = centre_line (lyrics_message, width - 1);\n\t\tlists_strs_push (result, line);\n\t}\n\n\tfor (ix = 0; ix < lists_strs_size (result); ix += 1) {\n\t\tint len;\n\t\tchar *this_line;\n\n\t\tthis_line = lists_strs_at (result, ix);\n\t\tlen = strlen (this_line);\n\t\tif (len > width - 1)\n\t\t\tstrcpy (&this_line[width - 1], \"\\n\");\n\t\telse if (this_line[len - 1] != '\\n') {\n\t\t\tchar *new_line;\n\n\t\t\tnew_line = xmalloc (len + 2);\n\t\t\tstrcpy (new_line, this_line);\n\t\t\tstrcat (new_line, \"\\n\");\n\t\t\tlists_strs_swap (result, ix, new_line);\n\t\t\tfree (this_line);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/* Dispose of raw lyrics lines. */\nvoid lyrics_cleanup (void)\n{\n\tif (raw_lyrics) {\n\t\tlists_strs_free (raw_lyrics);\n\t\traw_lyrics = NULL;\n\t}\n\n\tlyrics_message = \"[No lyrics loaded!]\";\n}\n"
  },
  {
    "path": "lyrics.h",
    "content": "#ifndef  LYRICS_H\n#define  LYRICS_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef lists_t_strs *lyrics_t_formatter (lists_t_strs *lines, int height, int width, void *data);\ntypedef void lyrics_t_reaper (void *data);\n\nlists_t_strs *lyrics_lines_get (void);\nvoid lyrics_lines_set (lists_t_strs *lines);\nlists_t_strs *lyrics_load_file (const char *filename);\nvoid lyrics_autoload (const char *filename);\nvoid lyrics_use_formatter (lyrics_t_formatter, lyrics_t_reaper, void *data);\nlists_t_strs *lyrics_format (int height, int width);\nvoid lyrics_cleanup (void);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "m4/ax_c___attribute__.m4",
    "content": "# ===========================================================================\n#    http://www.gnu.org/software/autoconf-archive/ax_c___attribute__.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_C___ATTRIBUTE__\n#\n# DESCRIPTION\n#\n#   Provides a test for the compiler support of __attribute__ extensions.\n#   Defines HAVE___ATTRIBUTE__ if it is found.\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Stepan Kasal <skasal@redhat.com>\n#   Copyright (c) 2008 Christian Haggstrom\n#   Copyright (c) 2008 Ryan McCabe <ryan@numb.org>\n#\n#   This program is free software; you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation; either version 2 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 8\n\nAC_DEFUN([AX_C___ATTRIBUTE__], [\n  AC_CACHE_CHECK([for __attribute__], [ax_cv___attribute__],\n    [AC_COMPILE_IFELSE(\n      [AC_LANG_PROGRAM(\n\t[[#include <stdlib.h>\n\t  static void foo(void) __attribute__ ((unused));\n\t  static void\n\t  foo(void) {\n\t      exit(1);\n\t  }\n        ]], [])],\n      [ax_cv___attribute__=yes],\n      [ax_cv___attribute__=no]\n    )\n  ])\n  if test \"$ax_cv___attribute__\" = \"yes\"; then\n    AC_DEFINE([HAVE___ATTRIBUTE__], 1, [define if your compiler has __attribute__])\n  fi\n])\n"
  },
  {
    "path": "m4/ax_cflags_gcc_option.m4",
    "content": "dnl @synopsis AX_CFLAGS_GCC_OPTION (optionflag [,[shellvar][,[A][,[NA]]])\ndnl\ndnl AX_CFLAGS_GCC_OPTION(-fvomit-frame) would show a message as like\ndnl \"checking CFLAGS for gcc -fvomit-frame ... yes\" and adds\ndnl the optionflag to CFLAGS if it is understood. You can override\ndnl the shellvar-default of CFLAGS of course. The order of arguments\ndnl stems from the explicit macros like AX_CFLAGS_WARN_ALL.\ndnl\ndnl The cousin AX_CXXFLAGS_GCC_OPTION would check for an option to add\ndnl to CXXFLAGS - and it uses the autoconf setup for C++ instead of C\ndnl (since it is possible to use different compilers for C and C++).\ndnl\ndnl The macro is a lot simpler than any special AX_CFLAGS_* macro (or\ndnl ac_cxx_rtti.m4 macro) but allows to check for arbitrary options.\ndnl However, if you use this macro in a few places, it would be great\ndnl if you would make up a new function-macro and submit it to the\ndnl ac-archive.\ndnl\ndnl   - $1 option-to-check-for : required (\"-option\" as non-value)\ndnl   - $2 shell-variable-to-add-to : CFLAGS (or CXXFLAGS in the other case)\ndnl   - $3 action-if-found : add value to shellvariable\ndnl   - $4 action-if-not-found : nothing\ndnl\ndnl note: in earlier versions, $1-$2 were swapped. We try to detect the\ndnl situation and accept a $2=~/-/ as being the old option-to-check-for.\ndnl\ndnl also: there are other variants that emerged from the original macro\ndnl variant which did just test an option to be possibly added. However,\ndnl some compilers accept an option silently, or possibly for just\ndnl another option that was not intended. Therefore, we have to do a\ndnl generic test for a compiler family. For gcc we check \"-pedantic\"\ndnl being accepted which is also understood by compilers who just want\ndnl to be compatible with gcc even when not being made from gcc sources.\ndnl\ndnl see also:\ndnl       AX_CFLAGS_SUN_OPTION               AX_CFLAGS_HPUX_OPTION\ndnl       AX_CFLAGS_AIX_OPTION               AX_CFLAGS_IRIX_OPTION\ndnl\ndnl @, tested, experimental\ndnl @version $Id: ax_cflags_gcc_option.m4,v 1.5 2003/11/29 08:13:25 guidod Exp $\ndnl @author Guido Draheim <guidod@gmx.de>\ndnl\nAC_DEFUN([AX_CFLAGS_GCC_OPTION_OLD], [dnl\nAS_VAR_PUSHDEF([FLAGS],[CFLAGS])dnl\nAS_VAR_PUSHDEF([VAR],[ac_cv_cflags_gcc_option_$2])dnl\nAC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for gcc m4_ifval($2,$2,-option)],\nVAR,[VAR=\"no, unknown\"\n AC_LANG_SAVE\n AC_LANG_C\n ac_save_[]FLAGS=\"$[]FLAGS\"\nfor ac_arg dnl\nin \"-pedantic  % m4_ifval($2,$2,-option)\"  dnl   GCC\n   #\ndo FLAGS=\"$ac_save_[]FLAGS \"`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'`\n   AC_TRY_COMPILE([],[return 0;],\n   [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break])\ndone\n FLAGS=\"$ac_save_[]FLAGS\"\n AC_LANG_RESTORE\n])\ncase \".$VAR\" in\n     .ok|.ok,*) m4_ifvaln($3,$3) ;;\n   .|.no|.no,*) m4_ifvaln($4,$4) ;;\n   *) m4_ifvaln($3,$3,[\n   if echo \" $[]m4_ifval($1,$1,FLAGS) \" | grep \" $VAR \" 2>&1 >/dev/null\n   then AC_RUN_LOG([: m4_ifval($1,$1,FLAGS) does contain $VAR])\n   else AC_RUN_LOG([: m4_ifval($1,$1,FLAGS)=\"$m4_ifval($1,$1,FLAGS) $VAR\"])\n                      m4_ifval($1,$1,FLAGS)=\"$m4_ifval($1,$1,FLAGS) $VAR\"\n   fi ]) ;;\nesac\nAS_VAR_POPDEF([VAR])dnl\nAS_VAR_POPDEF([FLAGS])dnl\n])\n\n\ndnl the only difference - the LANG selection... and the default FLAGS\n\nAC_DEFUN([AX_CXXFLAGS_GCC_OPTION_OLD], [dnl\nAS_VAR_PUSHDEF([FLAGS],[CXXFLAGS])dnl\nAS_VAR_PUSHDEF([VAR],[ac_cv_cxxflags_gcc_option_$2])dnl\nAC_CACHE_CHECK([m4_ifval($1,$1,FLAGS) for gcc m4_ifval($2,$2,-option)],\nVAR,[VAR=\"no, unknown\"\n AC_LANG_SAVE\n AC_LANG_CXX\n ac_save_[]FLAGS=\"$[]FLAGS\"\nfor ac_arg dnl\nin \"-pedantic  % m4_ifval($2,$2,-option)\"  dnl   GCC\n   #\ndo FLAGS=\"$ac_save_[]FLAGS \"`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'`\n   AC_TRY_COMPILE([],[return 0;],\n   [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break])\ndone\n FLAGS=\"$ac_save_[]FLAGS\"\n AC_LANG_RESTORE\n])\ncase \".$VAR\" in\n     .ok|.ok,*) m4_ifvaln($3,$3) ;;\n   .|.no|.no,*) m4_ifvaln($4,$4) ;;\n   *) m4_ifvaln($3,$3,[\n   if echo \" $[]m4_ifval($1,$1,FLAGS) \" | grep \" $VAR \" 2>&1 >/dev/null\n   then AC_RUN_LOG([: m4_ifval($1,$1,FLAGS) does contain $VAR])\n   else AC_RUN_LOG([: m4_ifval($1,$1,FLAGS)=\"$m4_ifval($1,$1,FLAGS) $VAR\"])\n                      m4_ifval($1,$1,FLAGS)=\"$m4_ifval($1,$1,FLAGS) $VAR\"\n   fi ]) ;;\nesac\nAS_VAR_POPDEF([VAR])dnl\nAS_VAR_POPDEF([FLAGS])dnl\n])\n\ndnl -------------------------------------------------------------------------\n\nAC_DEFUN([AX_CFLAGS_GCC_OPTION_NEW], [dnl\nAS_VAR_PUSHDEF([FLAGS],[CFLAGS])dnl\nAS_VAR_PUSHDEF([VAR],[ac_cv_cflags_gcc_option_$1])dnl\nAC_CACHE_CHECK([m4_ifval($2,$2,FLAGS) for gcc m4_ifval($1,$1,-option)],\nVAR,[VAR=\"no, unknown\"\n AC_LANG_SAVE\n AC_LANG_C\n ac_save_[]FLAGS=\"$[]FLAGS\"\nfor ac_arg dnl\nin \"-pedantic  % m4_ifval($1,$1,-option)\"  dnl   GCC\n   #\ndo FLAGS=\"$ac_save_[]FLAGS \"`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'`\n   AC_TRY_COMPILE([],[return 0;],\n   [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break])\ndone\n FLAGS=\"$ac_save_[]FLAGS\"\n AC_LANG_RESTORE\n])\ncase \".$VAR\" in\n     .ok|.ok,*) m4_ifvaln($3,$3) ;;\n   .|.no|.no,*) m4_ifvaln($4,$4) ;;\n   *) m4_ifvaln($3,$3,[\n   if echo \" $[]m4_ifval($2,$2,FLAGS) \" | grep \" $VAR \" 2>&1 >/dev/null\n   then AC_RUN_LOG([: m4_ifval($2,$2,FLAGS) does contain $VAR])\n   else AC_RUN_LOG([: m4_ifval($2,$2,FLAGS)=\"$m4_ifval($2,$2,FLAGS) $VAR\"])\n                      m4_ifval($2,$2,FLAGS)=\"$m4_ifval($2,$2,FLAGS) $VAR\"\n   fi ]) ;;\nesac\nAS_VAR_POPDEF([VAR])dnl\nAS_VAR_POPDEF([FLAGS])dnl\n])\n\n\ndnl the only difference - the LANG selection... and the default FLAGS\n\nAC_DEFUN([AX_CXXFLAGS_GCC_OPTION_NEW], [dnl\nAS_VAR_PUSHDEF([FLAGS],[CXXFLAGS])dnl\nAS_VAR_PUSHDEF([VAR],[ac_cv_cxxflags_gcc_option_$1])dnl\nAC_CACHE_CHECK([m4_ifval($2,$2,FLAGS) for gcc m4_ifval($1,$1,-option)],\nVAR,[VAR=\"no, unknown\"\n AC_LANG_SAVE\n AC_LANG_CXX\n ac_save_[]FLAGS=\"$[]FLAGS\"\nfor ac_arg dnl\nin \"-pedantic  % m4_ifval($1,$1,-option)\"  dnl   GCC\n   #\ndo FLAGS=\"$ac_save_[]FLAGS \"`echo $ac_arg | sed -e 's,%%.*,,' -e 's,%,,'`\n   AC_TRY_COMPILE([],[return 0;],\n   [VAR=`echo $ac_arg | sed -e 's,.*% *,,'` ; break])\ndone\n FLAGS=\"$ac_save_[]FLAGS\"\n AC_LANG_RESTORE\n])\ncase \".$VAR\" in\n     .ok|.ok,*) m4_ifvaln($3,$3) ;;\n   .|.no|.no,*) m4_ifvaln($4,$4) ;;\n   *) m4_ifvaln($3,$3,[\n   if echo \" $[]m4_ifval($2,$2,FLAGS) \" | grep \" $VAR \" 2>&1 >/dev/null\n   then AC_RUN_LOG([: m4_ifval($2,$2,FLAGS) does contain $VAR])\n   else AC_RUN_LOG([: m4_ifval($2,$2,FLAGS)=\"$m4_ifval($2,$2,FLAGS) $VAR\"])\n                      m4_ifval($2,$2,FLAGS)=\"$m4_ifval($2,$2,FLAGS) $VAR\"\n   fi ]) ;;\nesac\nAS_VAR_POPDEF([VAR])dnl\nAS_VAR_POPDEF([FLAGS])dnl\n])\n\nAC_DEFUN([AX_CFLAGS_GCC_OPTION],[ifelse(m4_bregexp([$2],[-]),-1,\n[AX_CFLAGS_GCC_OPTION_NEW($@)],[AX_CFLAGS_GCC_OPTION_OLD($@)])])\n\nAC_DEFUN([AX_CXXFLAGS_GCC_OPTION],[ifelse(m4_bregexp([$2],[-]),-1,\n[AX_CXXFLAGS_GCC_OPTION_NEW($@)],[AX_CXXFLAGS_GCC_OPTION_OLD($@)])])\n"
  },
  {
    "path": "m4/ax_compare_version.m4",
    "content": "# ===========================================================================\n#    http://www.gnu.org/software/autoconf-archive/ax_compare_version.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE])\n#\n# DESCRIPTION\n#\n#   This macro compares two version strings. Due to the various number of\n#   minor-version numbers that can exist, and the fact that string\n#   comparisons are not compatible with numeric comparisons, this is not\n#   necessarily trivial to do in a autoconf script. This macro makes doing\n#   these comparisons easy.\n#\n#   The six basic comparisons are available, as well as checking equality\n#   limited to a certain number of minor-version levels.\n#\n#   The operator OP determines what type of comparison to do, and can be one\n#   of:\n#\n#    eq  - equal (test A == B)\n#    ne  - not equal (test A != B)\n#    le  - less than or equal (test A <= B)\n#    ge  - greater than or equal (test A >= B)\n#    lt  - less than (test A < B)\n#    gt  - greater than (test A > B)\n#\n#   Additionally, the eq and ne operator can have a number after it to limit\n#   the test to that number of minor versions.\n#\n#    eq0 - equal up to the length of the shorter version\n#    ne0 - not equal up to the length of the shorter version\n#    eqN - equal up to N sub-version levels\n#    neN - not equal up to N sub-version levels\n#\n#   When the condition is true, shell commands ACTION-IF-TRUE are run,\n#   otherwise shell commands ACTION-IF-FALSE are run. The environment\n#   variable 'ax_compare_version' is always set to either 'true' or 'false'\n#   as well.\n#\n#   Examples:\n#\n#     AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8])\n#     AX_COMPARE_VERSION([3.15],[lt],[3.15.8])\n#\n#   would both be true.\n#\n#     AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8])\n#     AX_COMPARE_VERSION([3.15],[gt],[3.15.8])\n#\n#   would both be false.\n#\n#     AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8])\n#\n#   would be true because it is only comparing two minor versions.\n#\n#     AX_COMPARE_VERSION([3.15.7],[eq0],[3.15])\n#\n#   would be true because it is only comparing the lesser number of minor\n#   versions of the two values.\n#\n#   Note: The characters that separate the version numbers do not matter. An\n#   empty string is the same as version 0. OP is evaluated by autoconf, not\n#   configure, so must be a string, not a variable.\n#\n#   The author would like to acknowledge Guido Draheim whose advice about\n#   the m4_case and m4_ifvaln functions make this macro only include the\n#   portions necessary to perform the specific comparison specified by the\n#   OP argument in the final configure script.\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Tim Toolan <toolan@ele.uri.edu>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved. This file is offered as-is, without any\n#   warranty.\n\n#serial 11\n\ndnl #########################################################################\nAC_DEFUN([AX_COMPARE_VERSION], [\n  AC_REQUIRE([AC_PROG_AWK])\n\n  # Used to indicate true or false condition\n  ax_compare_version=false\n\n  # Convert the two version strings to be compared into a format that\n  # allows a simple string comparison.  The end result is that a version\n  # string of the form 1.12.5-r617 will be converted to the form\n  # 0001001200050617.  In other words, each number is zero padded to four\n  # digits, and non digits are removed.\n  AS_VAR_PUSHDEF([A],[ax_compare_version_A])\n  A=`echo \"$1\" | sed -e 's/\\([[0-9]]*\\)/Z\\1Z/g' \\\n                     -e 's/Z\\([[0-9]]\\)Z/Z0\\1Z/g' \\\n                     -e 's/Z\\([[0-9]][[0-9]]\\)Z/Z0\\1Z/g' \\\n                     -e 's/Z\\([[0-9]][[0-9]][[0-9]]\\)Z/Z0\\1Z/g' \\\n                     -e 's/[[^0-9]]//g'`\n\n  AS_VAR_PUSHDEF([B],[ax_compare_version_B])\n  B=`echo \"$3\" | sed -e 's/\\([[0-9]]*\\)/Z\\1Z/g' \\\n                     -e 's/Z\\([[0-9]]\\)Z/Z0\\1Z/g' \\\n                     -e 's/Z\\([[0-9]][[0-9]]\\)Z/Z0\\1Z/g' \\\n                     -e 's/Z\\([[0-9]][[0-9]][[0-9]]\\)Z/Z0\\1Z/g' \\\n                     -e 's/[[^0-9]]//g'`\n\n  dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary\n  dnl # then the first line is used to determine if the condition is true.\n  dnl # The sed right after the echo is to remove any indented white space.\n  m4_case(m4_tolower($2),\n  [lt],[\n    ax_compare_version=`echo \"x$A\nx$B\" | sed 's/^ *//' | sort -r | sed \"s/x${A}/false/;s/x${B}/true/;1q\"`\n  ],\n  [gt],[\n    ax_compare_version=`echo \"x$A\nx$B\" | sed 's/^ *//' | sort | sed \"s/x${A}/false/;s/x${B}/true/;1q\"`\n  ],\n  [le],[\n    ax_compare_version=`echo \"x$A\nx$B\" | sed 's/^ *//' | sort | sed \"s/x${A}/true/;s/x${B}/false/;1q\"`\n  ],\n  [ge],[\n    ax_compare_version=`echo \"x$A\nx$B\" | sed 's/^ *//' | sort -r | sed \"s/x${A}/true/;s/x${B}/false/;1q\"`\n  ],[\n    dnl Split the operator from the subversion count if present.\n    m4_bmatch(m4_substr($2,2),\n    [0],[\n      # A count of zero means use the length of the shorter version.\n      # Determine the number of characters in A and B.\n      ax_compare_version_len_A=`echo \"$A\" | $AWK '{print(length)}'`\n      ax_compare_version_len_B=`echo \"$B\" | $AWK '{print(length)}'`\n\n      # Set A to no more than B's length and B to no more than A's length.\n      A=`echo \"$A\" | sed \"s/\\(.\\{$ax_compare_version_len_B\\}\\).*/\\1/\"`\n      B=`echo \"$B\" | sed \"s/\\(.\\{$ax_compare_version_len_A\\}\\).*/\\1/\"`\n    ],\n    [[0-9]+],[\n      # A count greater than zero means use only that many subversions\n      A=`echo \"$A\" | sed \"s/\\(\\([[0-9]]\\{4\\}\\)\\{m4_substr($2,2)\\}\\).*/\\1/\"`\n      B=`echo \"$B\" | sed \"s/\\(\\([[0-9]]\\{4\\}\\)\\{m4_substr($2,2)\\}\\).*/\\1/\"`\n    ],\n    [.+],[\n      AC_WARNING(\n        [illegal OP numeric parameter: $2])\n    ],[])\n\n    # Pad zeros at end of numbers to make same length.\n    ax_compare_version_tmp_A=\"$A`echo $B | sed 's/./0/g'`\"\n    B=\"$B`echo $A | sed 's/./0/g'`\"\n    A=\"$ax_compare_version_tmp_A\"\n\n    # Check for equality or inequality as necessary.\n    m4_case(m4_tolower(m4_substr($2,0,2)),\n    [eq],[\n      test \"x$A\" = \"x$B\" && ax_compare_version=true\n    ],\n    [ne],[\n      test \"x$A\" != \"x$B\" && ax_compare_version=true\n    ],[\n      AC_WARNING([illegal OP parameter: $2])\n    ])\n  ])\n\n  AS_VAR_POPDEF([A])dnl\n  AS_VAR_POPDEF([B])dnl\n\n  dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE.\n  if test \"$ax_compare_version\" = \"true\" ; then\n    m4_ifvaln([$4],[$4],[:])dnl\n    m4_ifvaln([$5],[else $5])dnl\n  fi\n]) dnl AX_COMPARE_VERSION\n"
  },
  {
    "path": "m4/ax_gcc_func_attribute.m4",
    "content": "# ===========================================================================\n#   http://www.gnu.org/software/autoconf-archive/ax_gcc_func_attribute.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_GCC_FUNC_ATTRIBUTE(ATTRIBUTE)\n#\n# DESCRIPTION\n#\n#   This macro checks if the compiler supports one of GCC's function\n#   attributes; many other compilers also provide function attributes with\n#   the same syntax. Compiler warnings are used to detect supported\n#   attributes as unsupported ones are ignored by default so quieting\n#   warnings when using this macro will yield false positives.\n#\n#   The ATTRIBUTE parameter holds the name of the attribute to be checked.\n#\n#   If ATTRIBUTE is supported define HAVE_FUNC_ATTRIBUTE_<ATTRIBUTE>.\n#\n#   The macro caches its result in the ax_cv_have_func_attribute_<attribute>\n#   variable.\n#\n#   The macro currently supports the following function attributes:\n#\n#    alias\n#    aligned\n#    alloc_size\n#    always_inline\n#    artificial\n#    cold\n#    const\n#    constructor\n#    deprecated\n#    destructor\n#    dllexport\n#    dllimport\n#    error\n#    externally_visible\n#    flatten\n#    format\n#    format_arg\n#    gnu_inline\n#    hot\n#    ifunc\n#    leaf\n#    malloc\n#    noclone\n#    noinline\n#    nonnull\n#    noreturn\n#    nothrow\n#    optimize\n#    pure\n#    unused\n#    used\n#    visibility\n#    warning\n#    warn_unused_result\n#    weak\n#    weakref\n#\n#   Unsuppored function attributes will be tested with a prototype returning\n#   an int and not accepting any arguments and the result of the check might\n#   be wrong or meaningless so use with care.\n#\n# LICENSE\n#\n#   Copyright (c) 2013 Gabriele Svelto <gabriele.svelto@gmail.com>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved.  This file is offered as-is, without any\n#   warranty.\n\n#serial 2\n\nAC_DEFUN([AX_GCC_FUNC_ATTRIBUTE], [\n    AS_VAR_PUSHDEF([ac_var], [ax_cv_have_func_attribute_$1])\n\n    AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [\n        AC_LINK_IFELSE([AC_LANG_PROGRAM([\n            m4_case([$1],\n                [alias], [\n                    int foo( void ) { return 0; }\n                    int bar( void ) __attribute__(($1(\"foo\")));\n                ],\n                [aligned], [\n                    int foo( void ) __attribute__(($1(32)));\n                ],\n                [alloc_size], [\n                    void *foo(int a) __attribute__(($1(1)));\n                ],\n                [always_inline], [\n                    inline __attribute__(($1)) int foo( void ) { return 0; }\n                ],\n                [artificial], [\n                    inline __attribute__(($1)) int foo( void ) { return 0; }\n                ],\n                [cold], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [const], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [constructor], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [deprecated], [\n                    int foo( void ) __attribute__(($1(\"\")));\n                ],\n                [destructor], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [dllexport], [\n                    __attribute__(($1)) int foo( void ) { return 0; }\n                ],\n                [dllimport], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [error], [\n                    int foo( void ) __attribute__(($1(\"\")));\n                ],\n                [externally_visible], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [flatten], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [format], [\n                    int foo(const char *p, ...) __attribute__(($1(printf, 1, 2)));\n                ],\n                [format_arg], [\n                    char *foo(const char *p) __attribute__(($1(1)));\n                ],\n                [gnu_inline], [\n                    inline __attribute__(($1)) int foo( void ) { return 0; }\n                ],\n                [hot], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [ifunc], [\n                    int my_foo( void ) { return 0; }\n                    static int (*resolve_foo(void))(void) { return my_foo; }\n                    int foo( void ) __attribute__(($1(\"resolve_foo\")));\n                ],\n                [leaf], [\n                    __attribute__(($1)) int foo( void ) { return 0; }\n                ],\n                [malloc], [\n                    void *foo( void ) __attribute__(($1));\n                ],\n                [noclone], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [noinline], [\n                    __attribute__(($1)) int foo( void ) { return 0; }\n                ],\n                [nonnull], [\n                    int foo(char *p) __attribute__(($1(1)));\n                ],\n                [noreturn], [\n                    void foo( void ) __attribute__(($1));\n                ],\n                [nothrow], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [optimize], [\n                    __attribute__(($1(3))) int foo( void ) { return 0; }\n                ],\n                [pure], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [unused], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [used], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [visibility], [\n                    int foo_def( void ) __attribute__(($1(\"default\")));\n                    int foo_hid( void ) __attribute__(($1(\"hidden\")));\n                    int foo_int( void ) __attribute__(($1(\"internal\")));\n                    int foo_pro( void ) __attribute__(($1(\"protected\")));\n                ],\n                [warning], [\n                    int foo( void ) __attribute__(($1(\"\")));\n                ],\n                [warn_unused_result], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [weak], [\n                    int foo( void ) __attribute__(($1));\n                ],\n                [weakref], [\n                    static int foo( void ) { return 0; }\n                    static int bar( void ) __attribute__(($1(\"foo\")));\n                ],\n                [\n                 m4_warn([syntax], [Unsupported attribute $1, the test may fail])\n                 int foo( void ) __attribute__(($1));\n                ]\n            )], [])\n            ],\n            dnl GCC doesn't exit with an error if an unknown attribute is\n            dnl provided but only outputs a warning, so accept the attribute\n            dnl only if no warning were issued.\n            [AS_IF([test -s conftest.err],\n                [AS_VAR_SET([ac_var], [no])],\n                [AS_VAR_SET([ac_var], [yes])])],\n            [AS_VAR_SET([ac_var], [no])])\n    ])\n\n    AS_IF([test yes = AS_VAR_GET([ac_var])],\n        [AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_FUNC_ATTRIBUTE_$1), 1,\n            [Define to 1 if the system has the `$1' function attribute])], [])\n\n    AS_VAR_POPDEF([ac_var])\n])\n"
  },
  {
    "path": "m4/ax_gcc_var_attribute.m4",
    "content": "# ===========================================================================\n#   http://www.gnu.org/software/autoconf-archive/ax_gcc_var_attribute.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_GCC_VAR_ATTRIBUTE(ATTRIBUTE)\n#\n# DESCRIPTION\n#\n#   This macro checks if the compiler supports one of GCC's variable\n#   attributes; many other compilers also provide variable attributes with\n#   the same syntax. Compiler warnings are used to detect supported\n#   attributes as unsupported ones are ignored by default so quieting\n#   warnings when using this macro will yield false positives.\n#\n#   The ATTRIBUTE parameter holds the name of the attribute to be checked.\n#\n#   If ATTRIBUTE is supported define HAVE_VAR_ATTRIBUTE_<ATTRIBUTE>.\n#\n#   The macro caches its result in the ax_cv_have_var_attribute_<attribute>\n#   variable.\n#\n#   The macro currently supports the following variable attributes:\n#\n#    aligned\n#    cleanup\n#    common\n#    nocommon\n#    deprecated\n#    mode\n#    packed\n#    tls_model\n#    unused\n#    used\n#    vector_size\n#    weak\n#    dllimport\n#    dllexport\n#\n#   Unsuppored variable attributes will be tested against a global integer\n#   variable and without any arguments given to the attribute itself; the\n#   result of this check might be wrong or meaningless so use with care.\n#\n# LICENSE\n#\n#   Copyright (c) 2013 Gabriele Svelto <gabriele.svelto@gmail.com>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved.  This file is offered as-is, without any\n#   warranty.\n\n#serial 2\n\nAC_DEFUN([AX_GCC_VAR_ATTRIBUTE], [\n    AS_VAR_PUSHDEF([ac_var], [ax_cv_have_var_attribute_$1])\n\n    AC_CACHE_CHECK([for __attribute__(($1))], [ac_var], [\n        AC_LINK_IFELSE([AC_LANG_PROGRAM([\n            m4_case([$1],\n                [aligned], [\n                    int foo __attribute__(($1(32)));\n                ],\n                [cleanup], [\n                    int bar(int *t) { return *t; };\n                ],\n                [common], [\n                    int foo __attribute__(($1));\n                ],\n                [nocommon], [\n                    int foo __attribute__(($1));\n                ],\n                [deprecated], [\n                    int foo __attribute__(($1)) = 0;\n                ],\n                [mode], [\n                    long foo __attribute__(($1(word)));\n                ],\n                [packed], [\n                    struct bar {\n                        int baz __attribute__(($1));\n                    };\n                ],\n                [tls_model], [\n                    __thread int bar1 __attribute__(($1(\"global-dynamic\")));\n                    __thread int bar2 __attribute__(($1(\"local-dynamic\")));\n                    __thread int bar3 __attribute__(($1(\"initial-exec\")));\n                    __thread int bar4 __attribute__(($1(\"local-exec\")));\n                ],\n                [unused], [\n                    int foo __attribute__(($1));\n                ],\n                [used], [\n                    int foo __attribute__(($1));\n                ],\n                [vector_size], [\n                    int foo __attribute__(($1(16)));\n                ],\n                [weak], [\n                    int foo __attribute__(($1));\n                ],\n                [dllimport], [\n                    int foo __attribute__(($1));\n                ],\n                [dllexport], [\n                    int foo __attribute__(($1));\n                ],\n                [\n                 m4_warn([syntax], [Unsupported attribute $1, the test may fail])\n                 int foo __attribute__(($1));\n                ]\n            )], [\n            m4_case([$1],\n                [cleanup], [\n                    int foo __attribute__(($1(bar))) = 0;\n                    foo = foo + 1;\n                ],\n                []\n            )])\n            ],\n            dnl GCC doesn't exit with an error if an unknown attribute is\n            dnl provided but only outputs a warning, so accept the attribute\n            dnl only if no warning were issued.\n            [AS_IF([test -s conftest.err],\n                [AS_VAR_SET([ac_var], [no])],\n                [AS_VAR_SET([ac_var], [yes])])],\n            [AS_VAR_SET([ac_var], [no])])\n    ])\n\n    AS_IF([test yes = AS_VAR_GET([ac_var])],\n        [AC_DEFINE_UNQUOTED(AS_TR_CPP(HAVE_VAR_ATTRIBUTE_$1), 1,\n            [Define to 1 if the system has the `$1' variable attribute])], [])\n\n    AS_VAR_POPDEF([ac_var])\n])\n"
  },
  {
    "path": "m4/ax_path_bdb.m4",
    "content": "# ===========================================================================\n#        http://www.gnu.org/software/autoconf-archive/ax_path_bdb.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_PATH_BDB([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\n#\n# DESCRIPTION\n#\n#   This macro finds the latest version of Berkeley DB on the system, and\n#   ensures that the header file and library versions match. If\n#   MINIMUM-VERSION is specified, it will ensure that the library found is\n#   at least that version.\n#\n#   It determines the name of the library as well as the path to the header\n#   file and library. It will check both the default environment as well as\n#   the default Berkeley DB install location. When found, it sets BDB_LIBS,\n#   BDB_CPPFLAGS, and BDB_LDFLAGS to the necessary values to add to LIBS,\n#   CPPFLAGS, and LDFLAGS, as well as setting BDB_VERSION to the version\n#   found. HAVE_DB_H is defined also.\n#\n#   The option --with-bdb-dir=DIR can be used to specify a specific Berkeley\n#   DB installation to use.\n#\n#   An example of it's use is:\n#\n#     AX_PATH_BDB([3],[\n#       LIBS=\"$BDB_LIBS $LIBS\"\n#       LDFLAGS=\"$BDB_LDFLAGS $LDFLAGS\"\n#       CPPFLAGS=\"$CPPFLAGS $BDB_CPPFLAGS\"\n#     ])\n#\n#   which will locate the latest version of Berkeley DB on the system, and\n#   ensure that it is version 3.0 or higher.\n#\n#   Details: This macro does not use either AC_CHECK_HEADERS or AC_CHECK_LIB\n#   because, first, the functions inside the library are sometimes renamed\n#   to contain a version code that is only available from the db.h on the\n#   system, and second, because it is common to have multiple db.h and libdb\n#   files on a system it is important to make sure the ones being used\n#   correspond to the same version. Additionally, there are many different\n#   possible names for libdb when installed by an OS distribution, and these\n#   need to be checked if db.h does not correspond to libdb.\n#\n#   When cross compiling, only header versions are verified since it would\n#   be difficult to check the library version. Additionally the default\n#   Berkeley DB installation locations /usr/local/BerkeleyDB* are not\n#   searched for higher versions of the library.\n#\n#   The format for the list of library names to search came from the Cyrus\n#   IMAP distribution, although they are generated dynamically here, and\n#   only for the version found in db.h.\n#\n#   The macro AX_COMPARE_VERSION is required to use this macro, and should\n#   be available from the Autoconf Macro Archive.\n#\n#   The author would like to acknowledge the generous and valuable feedback\n#   from Guido Draheim, without which this macro would be far less robust,\n#   and have poor and inconsistent cross compilation support.\n#\n#   Changes:\n#\n#    1/5/05 applied patch from Rafal Rzepecki to eliminate compiler\n#           warning about unused variable, argv\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Tim Toolan <toolan@ele.uri.edu>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved. This file is offered as-is, without any\n#   warranty.\n\n#serial 11\n\ndnl #########################################################################\nAC_DEFUN([AX_PATH_BDB], [\n  dnl # Used to indicate success or failure of this function.\n  ax_path_bdb_ok=no\n\n  # Add --with-bdb-dir option to configure.\n  AC_ARG_WITH([bdb-dir],\n    [AS_HELP_STRING([--with-bdb-dir=DIR],\n                    [Berkeley DB installation directory])])\n\n  # Check if --with-bdb-dir was specified.\n  if test \"x$with_bdb_dir\" = \"x\" ; then\n    # No option specified, so just search the system.\n    AX_PATH_BDB_NO_OPTIONS([$1], [HIGHEST], [\n      ax_path_bdb_ok=yes\n    ])\n   else\n     # Set --with-bdb-dir option.\n     ax_path_bdb_INC=\"$with_bdb_dir/include\"\n     ax_path_bdb_LIB=\"$with_bdb_dir/lib\"\n\n     dnl # Save previous environment, and modify with new stuff.\n     ax_path_bdb_save_CPPFLAGS=\"$CPPFLAGS\"\n     CPPFLAGS=\"-I$ax_path_bdb_INC $CPPFLAGS\"\n\n     ax_path_bdb_save_LDFLAGS=$LDFLAGS\n     LDFLAGS=\"-L$ax_path_bdb_LIB $LDFLAGS\"\n\n     # Check for specific header file db.h\n     AC_MSG_CHECKING([db.h presence in $ax_path_bdb_INC])\n     if test -f \"$ax_path_bdb_INC/db.h\" ; then\n       AC_MSG_RESULT([yes])\n       # Check for library\n       AX_PATH_BDB_NO_OPTIONS([$1], [ENVONLY], [\n         ax_path_bdb_ok=yes\n         BDB_CPPFLAGS=\"-I$ax_path_bdb_INC\"\n         BDB_LDFLAGS=\"-L$ax_path_bdb_LIB\"\n       ])\n     else\n       AC_MSG_RESULT([no])\n       AC_MSG_NOTICE([no usable Berkeley DB not found])\n     fi\n\n     dnl # Restore the environment.\n     CPPFLAGS=\"$ax_path_bdb_save_CPPFLAGS\"\n     LDFLAGS=\"$ax_path_bdb_save_LDFLAGS\"\n\n  fi\n\n  dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND.\n  if test \"$ax_path_bdb_ok\" = \"yes\" ; then\n    m4_ifvaln([$2],[$2],[:])dnl\n    m4_ifvaln([$3],[else $3])dnl\n  fi\n\n]) dnl AX_PATH_BDB\n\ndnl #########################################################################\ndnl Check for berkeley DB of at least MINIMUM-VERSION on system.\ndnl\ndnl The OPTION argument determines how the checks occur, and can be one of:\ndnl\ndnl   HIGHEST -  Check both the environment and the default installation\ndnl              directories for Berkeley DB and choose the version that\ndnl              is highest. (default)\ndnl   ENVFIRST - Check the environment first, and if no satisfactory\ndnl              library is found there check the default installation\ndnl              directories for Berkeley DB which is /usr/local/BerkeleyDB*\ndnl   ENVONLY -  Check the current environment only.\ndnl\ndnl Requires AX_PATH_BDB_PATH_GET_VERSION, AX_PATH_BDB_PATH_FIND_HIGHEST,\ndnl          AX_PATH_BDB_ENV_CONFIRM_LIB, AX_PATH_BDB_ENV_GET_VERSION, and\ndnl          AX_COMPARE_VERSION macros.\ndnl\ndnl Result: sets ax_path_bdb_no_options_ok to yes or no\ndnl         sets BDB_LIBS, BDB_CPPFLAGS, BDB_LDFLAGS, BDB_VERSION\ndnl\ndnl AX_PATH_BDB_NO_OPTIONS([MINIMUM-VERSION], [OPTION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\nAC_DEFUN([AX_PATH_BDB_NO_OPTIONS], [\n  dnl # Used to indicate success or failure of this function.\n  ax_path_bdb_no_options_ok=no\n\n  # Values to add to environment to use Berkeley DB.\n  BDB_VERSION=''\n  BDB_LIBS=''\n  BDB_CPPFLAGS=''\n  BDB_LDFLAGS=''\n\n  # Check cross compilation here.\n  if test \"x$cross_compiling\" = \"xyes\" ; then\n    # If cross compiling, can't use AC_RUN_IFELSE so do these tests.\n    # The AC_PREPROC_IFELSE confirms that db.h is preprocessable,\n    # and extracts the version number from it.\n    AC_MSG_CHECKING([for db.h])\n\n    AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_no_options_HEADER_VERSION])dnl\n    HEADER_VERSION=''\n    AC_PREPROC_IFELSE([\n      AC_LANG_SOURCE([[\n#include <db.h>\nAX_PATH_BDB_STUFF DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH\n      ]])\n    ],[\n      # Extract version from preprocessor output.\n      HEADER_VERSION=`eval \"$ac_cpp conftest.$ac_ext\" 2> /dev/null \\\n        | grep AX_PATH_BDB_STUFF | sed 's/[[^0-9,]]//g;s/,/./g;1q'`\n    ],[])\n\n    if test \"x$HEADER_VERSION\" = \"x\" ; then\n      AC_MSG_RESULT([no])\n    else\n      AC_MSG_RESULT([$HEADER_VERSION])\n\n      # Check that version is high enough.\n      AX_COMPARE_VERSION([$HEADER_VERSION],[ge],[$1],[\n        # get major and minor version numbers\n        AS_VAR_PUSHDEF([MAJ],[ax_path_bdb_no_options_MAJOR])dnl\n        MAJ=`echo $HEADER_VERSION | sed 's,\\..*,,'`\n        AS_VAR_PUSHDEF([MIN],[ax_path_bdb_no_options_MINOR])dnl\n        MIN=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\\.,,;s,\\.[[0-9]]*$,,'`\n\n\tdnl # Save LIBS.\n\tax_path_bdb_no_options_save_LIBS=\"$LIBS\"\n\n        # Check that we can link with the library.\n        AC_SEARCH_LIBS([db_version],\n          [db db-$MAJ.$MIN db$MAJ.$MIN db$MAJ$MIN db-$MAJ db$MAJ],[\n            # Sucessfully found library.\n            ax_path_bdb_no_options_ok=yes\n            BDB_VERSION=$HEADER_VERSION\n\n\t    # Extract library from LIBS\n\t    ax_path_bdb_no_options_LEN=` \\\n              echo \"x$ax_path_bdb_no_options_save_LIBS\" \\\n              | awk '{print(length)}'`\n            BDB_LIBS=`echo \"x$LIBS \" \\\n              | sed \"s/.\\{$ax_path_bdb_no_options_LEN\\}\\$//;s/^x//;s/ //g\"`\n        ],[])\n\n        dnl # Restore LIBS\n\tLIBS=\"$ax_path_bdb_no_options_save_LIBS\"\n\n        AS_VAR_POPDEF([MAJ])dnl\n        AS_VAR_POPDEF([MIN])dnl\n      ])\n    fi\n\n    AS_VAR_POPDEF([HEADER_VERSION])dnl\n  else\n    # Not cross compiling.\n    # Check version of Berkeley DB in the current environment.\n    AX_PATH_BDB_ENV_GET_VERSION([\n      AX_COMPARE_VERSION([$ax_path_bdb_env_get_version_VERSION],[ge],[$1],[\n        # Found acceptable version in current environment.\n        ax_path_bdb_no_options_ok=yes\n        BDB_VERSION=\"$ax_path_bdb_env_get_version_VERSION\"\n        BDB_LIBS=\"$ax_path_bdb_env_get_version_LIBS\"\n      ])\n    ])\n\n    # Determine if we need to search /usr/local/BerkeleyDB*\n    ax_path_bdb_no_options_DONE=no\n    if test \"x$2\" = \"xENVONLY\" ; then\n      ax_path_bdb_no_options_DONE=yes\n    elif test \"x$2\" = \"xENVFIRST\" ; then\n      ax_path_bdb_no_options_DONE=$ax_path_bdb_no_options_ok\n    fi\n\n    if test \"$ax_path_bdb_no_options_DONE\" = \"no\" ; then\n      # Check for highest in /usr/local/BerkeleyDB*\n      AX_PATH_BDB_PATH_FIND_HIGHEST([\n        if test \"$ax_path_bdb_no_options_ok\" = \"yes\" ; then\n        # If we already have an acceptable version use this if higher.\n          AX_COMPARE_VERSION(\n             [$ax_path_bdb_path_find_highest_VERSION],[gt],[$BDB_VERSION])\n        else\n          # Since we didn't have an acceptable version check if this one is.\n          AX_COMPARE_VERSION(\n             [$ax_path_bdb_path_find_highest_VERSION],[ge],[$1])\n        fi\n      ])\n\n      dnl # If result from _AX_COMPARE_VERSION is true we want this version.\n      if test \"$ax_compare_version\" = \"true\" ; then\n        ax_path_bdb_no_options_ok=yes\n        BDB_LIBS=\"-ldb\"\n\tif test \"x$ax_path_bdb_path_find_highest_DIR\" != x ; then\n\t  BDB_CPPFLAGS=\"-I$ax_path_bdb_path_find_highest_DIR/include\"\n\t  BDB_LDFLAGS=\"-L$ax_path_bdb_path_find_highest_DIR/lib\"\n\tfi\n        BDB_VERSION=\"$ax_path_bdb_path_find_highest_VERSION\"\n      fi\n    fi\n  fi\n\n  dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND.\n  if test \"$ax_path_bdb_no_options_ok\" = \"yes\" ; then\n    AC_MSG_NOTICE([using Berkeley DB version $BDB_VERSION])\n    AC_DEFINE([HAVE_DB_H],[1],\n              [Define to 1 if you have the <db.h> header file.])\n    m4_ifvaln([$3],[$3])dnl\n  else\n    AC_MSG_NOTICE([no Berkeley DB version $1 or higher found])\n    m4_ifvaln([$4],[$4])dnl\n  fi\n]) dnl AX_PATH_BDB_NO_OPTIONS\n\ndnl #########################################################################\ndnl Check the default installation directory for Berkeley DB which is\ndnl of the form /usr/local/BerkeleyDB* for the highest version.\ndnl\ndnl Result: sets ax_path_bdb_path_find_highest_ok to yes or no,\ndnl         sets ax_path_bdb_path_find_highest_VERSION to version,\ndnl         sets ax_path_bdb_path_find_highest_DIR to directory.\ndnl\ndnl AX_PATH_BDB_PATH_FIND_HIGHEST([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\nAC_DEFUN([AX_PATH_BDB_PATH_FIND_HIGHEST], [\n  dnl # Used to indicate success or failure of this function.\n  ax_path_bdb_path_find_highest_ok=no\n\n  AS_VAR_PUSHDEF([VERSION],[ax_path_bdb_path_find_highest_VERSION])dnl\n  VERSION=''\n\n  ax_path_bdb_path_find_highest_DIR=''\n\n  # find highest verison in default install directory for Berkeley DB\n  AS_VAR_PUSHDEF([CURDIR],[ax_path_bdb_path_find_highest_CURDIR])dnl\n  AS_VAR_PUSHDEF([CUR_VERSION],[ax_path_bdb_path_get_version_VERSION])dnl\n\n  for CURDIR in `ls -d /usr/local/BerkeleyDB* 2> /dev/null`\n  do\n    AX_PATH_BDB_PATH_GET_VERSION([$CURDIR],[\n      AX_COMPARE_VERSION([$CUR_VERSION],[gt],[$VERSION],[\n        ax_path_bdb_path_find_highest_ok=yes\n        ax_path_bdb_path_find_highest_DIR=\"$CURDIR\"\n        VERSION=\"$CUR_VERSION\"\n      ])\n    ])\n  done\n\n  AS_VAR_POPDEF([VERSION])dnl\n  AS_VAR_POPDEF([CUR_VERSION])dnl\n  AS_VAR_POPDEF([CURDIR])dnl\n\n  dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND.\n  if test \"$ax_path_bdb_path_find_highest_ok\" = \"yes\" ; then\n    m4_ifvaln([$1],[$1],[:])dnl\n    m4_ifvaln([$2],[else $2])dnl\n  fi\n\n]) dnl AX_PATH_BDB_PATH_FIND_HIGHEST\n\ndnl #########################################################################\ndnl Checks for Berkeley DB in specified directory's lib and include\ndnl subdirectories.\ndnl\ndnl Result: sets ax_path_bdb_path_get_version_ok to yes or no,\ndnl         sets ax_path_bdb_path_get_version_VERSION to version.\ndnl\ndnl AX_PATH_BDB_PATH_GET_VERSION(BDB-DIR, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\nAC_DEFUN([AX_PATH_BDB_PATH_GET_VERSION], [\n  dnl # Used to indicate success or failure of this function.\n  ax_path_bdb_path_get_version_ok=no\n\n  # Indicate status of checking for Berkeley DB header.\n  AC_MSG_CHECKING([in $1/include for db.h])\n  ax_path_bdb_path_get_version_got_header=no\n  test -f \"$1/include/db.h\" && ax_path_bdb_path_get_version_got_header=yes\n  AC_MSG_RESULT([$ax_path_bdb_path_get_version_got_header])\n\n  # Indicate status of checking for Berkeley DB library.\n  AC_MSG_CHECKING([in $1/lib for library -ldb])\n\n  ax_path_bdb_path_get_version_VERSION=''\n\n  if test -d \"$1/include\" && test -d \"$1/lib\" &&\n     test \"$ax_path_bdb_path_get_version_got_header\" = \"yes\" ; then\n    dnl # save and modify environment\n    ax_path_bdb_path_get_version_save_CPPFLAGS=\"$CPPFLAGS\"\n    CPPFLAGS=\"-I$1/include $CPPFLAGS\"\n\n    ax_path_bdb_path_get_version_save_LIBS=\"$LIBS\"\n    LIBS=\"$LIBS -ldb\"\n\n    ax_path_bdb_path_get_version_save_LDFLAGS=\"$LDFLAGS\"\n    LDFLAGS=\"-L$1/lib $LDFLAGS\"\n\n    # Compile and run a program that compares the version defined in\n    # the header file with a version defined in the library function\n    # db_version.\n    AC_RUN_IFELSE([\n      AC_LANG_SOURCE([[\n#include <stdio.h>\n#include <db.h>\nint main(int argc,char **argv)\n{\n  int major,minor,patch;\n  (void) argv;\n  db_version(&major,&minor,&patch);\n  if (argc > 1)\n    printf(\"%d.%d.%d\\n\",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH);\n  if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor &&\n      DB_VERSION_PATCH == patch)\n    return 0;\n  else\n    return 1;\n}\n      ]])\n    ],[\n      # Program compiled and ran, so get version by adding argument.\n      ax_path_bdb_path_get_version_VERSION=`./conftest$ac_exeext x`\n      ax_path_bdb_path_get_version_ok=yes\n    ],[],[])\n\n    dnl # restore environment\n    CPPFLAGS=\"$ax_path_bdb_path_get_version_save_CPPFLAGS\"\n    LIBS=\"$ax_path_bdb_path_get_version_save_LIBS\"\n    LDFLAGS=\"$ax_path_bdb_path_get_version_save_LDFLAGS\"\n  fi\n\n  dnl # Finally, execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND.\n  if test \"$ax_path_bdb_path_get_version_ok\" = \"yes\" ; then\n    AC_MSG_RESULT([$ax_path_bdb_path_get_version_VERSION])\n    m4_ifvaln([$2],[$2])dnl\n  else\n    AC_MSG_RESULT([no])\n    m4_ifvaln([$3],[$3])dnl\n  fi\n]) dnl AX_PATH_BDB_PATH_GET_VERSION\n\n#############################################################################\ndnl Checks if version of library and header match specified version.\ndnl Only meant to be used by AX_PATH_BDB_ENV_GET_VERSION macro.\ndnl\ndnl Requires AX_COMPARE_VERSION macro.\ndnl\ndnl Result: sets ax_path_bdb_env_confirm_lib_ok to yes or no.\ndnl\ndnl AX_PATH_BDB_ENV_CONFIRM_LIB(VERSION, [LIBNAME])\nAC_DEFUN([AX_PATH_BDB_ENV_CONFIRM_LIB], [\n  dnl # Used to indicate success or failure of this function.\n  ax_path_bdb_env_confirm_lib_ok=no\n\n  dnl # save and modify environment to link with library LIBNAME\n  ax_path_bdb_env_confirm_lib_save_LIBS=\"$LIBS\"\n  LIBS=\"$LIBS $2\"\n\n  # Compile and run a program that compares the version defined in\n  # the header file with a version defined in the library function\n  # db_version.\n  AC_RUN_IFELSE([\n    AC_LANG_SOURCE([[\n#include <stdio.h>\n#include <db.h>\nint main(int argc,char **argv)\n{\n  int major,minor,patch;\n  (void) argv;\n  db_version(&major,&minor,&patch);\n  if (argc > 1)\n    printf(\"%d.%d.%d\\n\",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH);\n  if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor &&\n      DB_VERSION_PATCH == patch)\n    return 0;\n  else\n    return 1;\n}\n    ]])\n  ],[\n    # Program compiled and ran, so get version by giving an argument,\n    # which will tell the program to print the output.\n    ax_path_bdb_env_confirm_lib_VERSION=`./conftest$ac_exeext x`\n\n    # If the versions all match up, indicate success.\n    AX_COMPARE_VERSION([$ax_path_bdb_env_confirm_lib_VERSION],[eq],[$1],[\n      ax_path_bdb_env_confirm_lib_ok=yes\n    ])\n  ],[],[])\n\n  dnl # restore environment\n  LIBS=\"$ax_path_bdb_env_confirm_lib_save_LIBS\"\n\n]) dnl AX_PATH_BDB_ENV_CONFIRM_LIB\n\n#############################################################################\ndnl Finds the version and library name for Berkeley DB in the\ndnl current environment.  Tries many different names for library.\ndnl\ndnl Requires AX_PATH_BDB_ENV_CONFIRM_LIB macro.\ndnl\ndnl Result: set ax_path_bdb_env_get_version_ok to yes or no,\ndnl         set ax_path_bdb_env_get_version_VERSION to the version found,\ndnl         and ax_path_bdb_env_get_version_LIBNAME to the library name.\ndnl\ndnl AX_PATH_BDB_ENV_GET_VERSION([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])\nAC_DEFUN([AX_PATH_BDB_ENV_GET_VERSION], [\n  dnl # Used to indicate success or failure of this function.\n  ax_path_bdb_env_get_version_ok=no\n\n  ax_path_bdb_env_get_version_VERSION=''\n  ax_path_bdb_env_get_version_LIBS=''\n\n  AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_env_get_version_HEADER_VERSION])dnl\n  AS_VAR_PUSHDEF([TEST_LIBNAME],[ax_path_bdb_env_get_version_TEST_LIBNAME])dnl\n\n  # Indicate status of checking for Berkeley DB library.\n  AC_MSG_CHECKING([for db.h])\n\n  # Compile and run a program that determines the Berkeley DB version\n  # in the header file db.h.\n  HEADER_VERSION=''\n  AC_RUN_IFELSE([\n    AC_LANG_SOURCE([[\n#include <stdio.h>\n#include <db.h>\nint main(int argc,char **argv)\n{\n  (void) argv;\n  if (argc > 1)\n    printf(\"%d.%d.%d\\n\",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH);\n  return 0;\n}\n    ]])\n  ],[\n    # Program compiled and ran, so get version by adding an argument.\n    HEADER_VERSION=`./conftest$ac_exeext x`\n    AC_MSG_RESULT([$HEADER_VERSION])\n  ],[AC_MSG_RESULT([no])],[AC_MSG_RESULT([no])])\n\n  # Have header version, so try to find corresponding library.\n  # Looks for library names in the order:\n  #   nothing, db, db-X.Y, dbX.Y, dbXY, db-X, dbX\n  # and stops when it finds the first one that matches the version\n  # of the header file.\n  if test \"x$HEADER_VERSION\" != \"x\" ; then\n    AC_MSG_CHECKING([for library containing Berkeley DB $HEADER_VERSION])\n\n    AS_VAR_PUSHDEF([MAJOR],[ax_path_bdb_env_get_version_MAJOR])dnl\n    AS_VAR_PUSHDEF([MINOR],[ax_path_bdb_env_get_version_MINOR])dnl\n\n    # get major and minor version numbers\n    MAJOR=`echo $HEADER_VERSION | sed 's,\\..*,,'`\n    MINOR=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\\.,,;s,\\.[[0-9]]*$,,'`\n\n    # see if it is already specified in LIBS\n    TEST_LIBNAME=''\n    AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME])\n\n    if test \"$ax_path_bdb_env_confirm_lib_ok\" = \"no\" ; then\n      # try format \"db\"\n      TEST_LIBNAME='-ldb'\n      AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME])\n    fi\n\n    if test \"$ax_path_bdb_env_confirm_lib_ok\" = \"no\" ; then\n      # try format \"db-X.Y\"\n      TEST_LIBNAME=\"-ldb-${MAJOR}.$MINOR\"\n      AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME])\n    fi\n\n    if test \"$ax_path_bdb_env_confirm_lib_ok\" = \"no\" ; then\n      # try format \"dbX.Y\"\n      TEST_LIBNAME=\"-ldb${MAJOR}.$MINOR\"\n      AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME])\n    fi\n\n    if test \"$ax_path_bdb_env_confirm_lib_ok\" = \"no\" ; then\n      # try format \"dbXY\"\n      TEST_LIBNAME=\"-ldb$MAJOR$MINOR\"\n      AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME])\n    fi\n\n    if test \"$ax_path_bdb_env_confirm_lib_ok\" = \"no\" ; then\n      # try format \"db-X\"\n      TEST_LIBNAME=\"-ldb-$MAJOR\"\n      AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME])\n    fi\n\n    if test \"$ax_path_bdb_env_confirm_lib_ok\" = \"no\" ; then\n      # try format \"dbX\"\n      TEST_LIBNAME=\"-ldb$MAJOR\"\n      AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME])\n    fi\n\n    dnl # Found a valid library.\n    if test \"$ax_path_bdb_env_confirm_lib_ok\" = \"yes\" ; then\n      if test \"x$TEST_LIBNAME\" = \"x\" ; then\n        AC_MSG_RESULT([none required])\n      else\n        AC_MSG_RESULT([$TEST_LIBNAME])\n      fi\n      ax_path_bdb_env_get_version_VERSION=\"$HEADER_VERSION\"\n      ax_path_bdb_env_get_version_LIBS=\"$TEST_LIBNAME\"\n      ax_path_bdb_env_get_version_ok=yes\n    else\n      AC_MSG_RESULT([no])\n    fi\n\n    AS_VAR_POPDEF([MAJOR])dnl\n    AS_VAR_POPDEF([MINOR])dnl\n  fi\n\n  AS_VAR_POPDEF([HEADER_VERSION])dnl\n  AS_VAR_POPDEF([TEST_LIBNAME])dnl\n\n  dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND.\n  if test \"$ax_path_bdb_env_confirm_lib_ok\" = \"yes\" ; then\n    m4_ifvaln([$1],[$1],[:])dnl\n    m4_ifvaln([$2],[else $2])dnl\n  fi\n\n]) dnl BDB_ENV_GET_VERSION\n\n#############################################################################\n"
  },
  {
    "path": "m4/ax_pthread.m4",
    "content": "# ===========================================================================\n#        http://www.gnu.org/software/autoconf-archive/ax_pthread.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])\n#\n# DESCRIPTION\n#\n#   This macro figures out how to build C programs using POSIX threads. It\n#   sets the PTHREAD_LIBS output variable to the threads library and linker\n#   flags, and the PTHREAD_CFLAGS output variable to any special C compiler\n#   flags that are needed. (The user can also force certain compiler\n#   flags/libs to be tested by setting these environment variables.)\n#\n#   Also sets PTHREAD_CC to any special C compiler that is needed for\n#   multi-threaded programs (defaults to the value of CC otherwise). (This\n#   is necessary on AIX to use the special cc_r compiler alias.)\n#\n#   NOTE: You are assumed to not only compile your program with these flags,\n#   but also link it with them as well. e.g. you should link with\n#   $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS\n#\n#   If you are only building threads programs, you may wish to use these\n#   variables in your default LIBS, CFLAGS, and CC:\n#\n#     LIBS=\"$PTHREAD_LIBS $LIBS\"\n#     CFLAGS=\"$CFLAGS $PTHREAD_CFLAGS\"\n#     CC=\"$PTHREAD_CC\"\n#\n#   In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant\n#   has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name\n#   (e.g. PTHREAD_CREATE_UNDETACHED on AIX).\n#\n#   Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the\n#   PTHREAD_PRIO_INHERIT symbol is defined when compiling with\n#   PTHREAD_CFLAGS.\n#\n#   ACTION-IF-FOUND is a list of shell commands to run if a threads library\n#   is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it\n#   is not found. If ACTION-IF-FOUND is not specified, the default action\n#   will define HAVE_PTHREAD.\n#\n#   Please let the authors know if this macro fails on any platform, or if\n#   you have any other suggestions or comments. This macro was based on work\n#   by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help\n#   from M. Frigo), as well as ac_pthread and hb_pthread macros posted by\n#   Alejandro Forero Cuervo to the autoconf macro repository. We are also\n#   grateful for the helpful feedback of numerous users.\n#\n#   Updated for Autoconf 2.68 by Daniel Richard G.\n#\n# LICENSE\n#\n#   Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>\n#   Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>\n#\n#   This program is free software: you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation, either version 3 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 21\n\nAU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])\nAC_DEFUN([AX_PTHREAD], [\nAC_REQUIRE([AC_CANONICAL_HOST])\nAC_LANG_PUSH([C])\nax_pthread_ok=no\n\n# We used to check for pthread.h first, but this fails if pthread.h\n# requires special compiler flags (e.g. on True64 or Sequent).\n# It gets checked for in the link test anyway.\n\n# First of all, check if the user has set any of the PTHREAD_LIBS,\n# etcetera environment variables, and if threads linking works using\n# them:\nif test x\"$PTHREAD_LIBS$PTHREAD_CFLAGS\" != x; then\n        save_CFLAGS=\"$CFLAGS\"\n        CFLAGS=\"$CFLAGS $PTHREAD_CFLAGS\"\n        save_LIBS=\"$LIBS\"\n        LIBS=\"$PTHREAD_LIBS $LIBS\"\n        AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS])\n        AC_TRY_LINK_FUNC([pthread_join], [ax_pthread_ok=yes])\n        AC_MSG_RESULT([$ax_pthread_ok])\n        if test x\"$ax_pthread_ok\" = xno; then\n                PTHREAD_LIBS=\"\"\n                PTHREAD_CFLAGS=\"\"\n        fi\n        LIBS=\"$save_LIBS\"\n        CFLAGS=\"$save_CFLAGS\"\nfi\n\n# We must check for the threads library under a number of different\n# names; the ordering is very important because some systems\n# (e.g. DEC) have both -lpthread and -lpthreads, where one of the\n# libraries is broken (non-POSIX).\n\n# Create a list of thread flags to try.  Items starting with a \"-\" are\n# C compiler flags, and other items are library names, except for \"none\"\n# which indicates that we try without any flags at all, and \"pthread-config\"\n# which is a program returning the flags for the Pth emulation library.\n\nax_pthread_flags=\"pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config\"\n\n# The ordering *is* (sometimes) important.  Some notes on the\n# individual items follow:\n\n# pthreads: AIX (must check this before -lpthread)\n# none: in case threads are in libc; should be tried before -Kthread and\n#       other compiler flags to prevent continual compiler warnings\n# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)\n# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)\n# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)\n# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads)\n# -pthreads: Solaris/gcc\n# -mthreads: Mingw32/gcc, Lynx/gcc\n# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it\n#      doesn't hurt to check since this sometimes defines pthreads too;\n#      also defines -D_REENTRANT)\n#      ... -mt is also the pthreads flag for HP/aCC\n# pthread: Linux, etcetera\n# --thread-safe: KAI C++\n# pthread-config: use pthread-config program (for GNU Pth library)\n\ncase ${host_os} in\n        solaris*)\n\n        # On Solaris (at least, for some versions), libc contains stubbed\n        # (non-functional) versions of the pthreads routines, so link-based\n        # tests will erroneously succeed.  (We need to link with -pthreads/-mt/\n        # -lpthread.)  (The stubs are missing pthread_cleanup_push, or rather\n        # a function called by this macro, so we could check for that, but\n        # who knows whether they'll stub that too in a future libc.)  So,\n        # we'll just look for -pthreads and -lpthread first:\n\n        ax_pthread_flags=\"-pthreads pthread -mt -pthread $ax_pthread_flags\"\n        ;;\n\n        darwin*)\n        ax_pthread_flags=\"-pthread $ax_pthread_flags\"\n        ;;\nesac\n\n# Clang doesn't consider unrecognized options an error unless we specify\n# -Werror. We throw in some extra Clang-specific options to ensure that\n# this doesn't happen for GCC, which also accepts -Werror.\n\nAC_MSG_CHECKING([if compiler needs -Werror to reject unknown flags])\nsave_CFLAGS=\"$CFLAGS\"\nax_pthread_extra_flags=\"-Werror\"\nCFLAGS=\"$CFLAGS $ax_pthread_extra_flags -Wunknown-warning-option -Wsizeof-array-argument\"\nAC_COMPILE_IFELSE([AC_LANG_PROGRAM([int foo(void);],[foo()])],\n                  [AC_MSG_RESULT([yes])],\n                  [ax_pthread_extra_flags=\n                   AC_MSG_RESULT([no])])\nCFLAGS=\"$save_CFLAGS\"\n\nif test x\"$ax_pthread_ok\" = xno; then\nfor flag in $ax_pthread_flags; do\n\n        case $flag in\n                none)\n                AC_MSG_CHECKING([whether pthreads work without any flags])\n                ;;\n\n                -*)\n                AC_MSG_CHECKING([whether pthreads work with $flag])\n                PTHREAD_CFLAGS=\"$flag\"\n                ;;\n\n                pthread-config)\n                AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])\n                if test x\"$ax_pthread_config\" = xno; then continue; fi\n                PTHREAD_CFLAGS=\"`pthread-config --cflags`\"\n                PTHREAD_LIBS=\"`pthread-config --ldflags` `pthread-config --libs`\"\n                ;;\n\n                *)\n                AC_MSG_CHECKING([for the pthreads library -l$flag])\n                PTHREAD_LIBS=\"-l$flag\"\n                ;;\n        esac\n\n        save_LIBS=\"$LIBS\"\n        save_CFLAGS=\"$CFLAGS\"\n        LIBS=\"$PTHREAD_LIBS $LIBS\"\n        CFLAGS=\"$CFLAGS $PTHREAD_CFLAGS $ax_pthread_extra_flags\"\n\n        # Check for various functions.  We must include pthread.h,\n        # since some functions may be macros.  (On the Sequent, we\n        # need a special flag -Kthread to make this header compile.)\n        # We check for pthread_join because it is in -lpthread on IRIX\n        # while pthread_create is in libc.  We check for pthread_attr_init\n        # due to DEC craziness with -lpthreads.  We check for\n        # pthread_cleanup_push because it is one of the few pthread\n        # functions on Solaris that doesn't have a non-functional libc stub.\n        # We try pthread_create on general principles.\n        AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>\n                        static void routine(void *a) { a = 0; }\n                        static void *start_routine(void *a) { return a; }],\n                       [pthread_t th; pthread_attr_t attr;\n                        pthread_create(&th, 0, start_routine, 0);\n                        pthread_join(th, 0);\n                        pthread_attr_init(&attr);\n                        pthread_cleanup_push(routine, 0);\n                        pthread_cleanup_pop(0) /* ; */])],\n                [ax_pthread_ok=yes],\n                [])\n\n        LIBS=\"$save_LIBS\"\n        CFLAGS=\"$save_CFLAGS\"\n\n        AC_MSG_RESULT([$ax_pthread_ok])\n        if test \"x$ax_pthread_ok\" = xyes; then\n                break;\n        fi\n\n        PTHREAD_LIBS=\"\"\n        PTHREAD_CFLAGS=\"\"\ndone\nfi\n\n# Various other checks:\nif test \"x$ax_pthread_ok\" = xyes; then\n        save_LIBS=\"$LIBS\"\n        LIBS=\"$PTHREAD_LIBS $LIBS\"\n        save_CFLAGS=\"$CFLAGS\"\n        CFLAGS=\"$CFLAGS $PTHREAD_CFLAGS\"\n\n        # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.\n        AC_MSG_CHECKING([for joinable pthread attribute])\n        attr_name=unknown\n        for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do\n            AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],\n                           [int attr = $attr; return attr /* ; */])],\n                [attr_name=$attr; break],\n                [])\n        done\n        AC_MSG_RESULT([$attr_name])\n        if test \"$attr_name\" != PTHREAD_CREATE_JOINABLE; then\n            AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], [$attr_name],\n                               [Define to necessary symbol if this constant\n                                uses a non-standard name on your system.])\n        fi\n\n        AC_MSG_CHECKING([if more special flags are required for pthreads])\n        flag=no\n        case ${host_os} in\n            aix* | freebsd* | darwin*) flag=\"-D_THREAD_SAFE\";;\n            osf* | hpux*) flag=\"-D_REENTRANT\";;\n            solaris*)\n            if test \"$GCC\" = \"yes\"; then\n                flag=\"-D_REENTRANT\"\n            else\n                # TODO: What about Clang on Solaris?\n                flag=\"-mt -D_REENTRANT\"\n            fi\n            ;;\n        esac\n        AC_MSG_RESULT([$flag])\n        if test \"x$flag\" != xno; then\n            PTHREAD_CFLAGS=\"$flag $PTHREAD_CFLAGS\"\n        fi\n\n        AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],\n            [ax_cv_PTHREAD_PRIO_INHERIT], [\n                AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],\n                                                [[int i = PTHREAD_PRIO_INHERIT;]])],\n                    [ax_cv_PTHREAD_PRIO_INHERIT=yes],\n                    [ax_cv_PTHREAD_PRIO_INHERIT=no])\n            ])\n        AS_IF([test \"x$ax_cv_PTHREAD_PRIO_INHERIT\" = \"xyes\"],\n            [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])])\n\n        LIBS=\"$save_LIBS\"\n        CFLAGS=\"$save_CFLAGS\"\n\n        # More AIX lossage: compile with *_r variant\n        if test \"x$GCC\" != xyes; then\n            case $host_os in\n                aix*)\n                AS_CASE([\"x/$CC\"],\n                  [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],\n                  [#handle absolute path differently from PATH based program lookup\n                   AS_CASE([\"x$CC\"],\n                     [x/*],\n                     [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC=\"${CC}_r\"])],\n                     [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])\n                ;;\n            esac\n        fi\nfi\n\ntest -n \"$PTHREAD_CC\" || PTHREAD_CC=\"$CC\"\n\nAC_SUBST([PTHREAD_LIBS])\nAC_SUBST([PTHREAD_CFLAGS])\nAC_SUBST([PTHREAD_CC])\n\n# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:\nif test x\"$ax_pthread_ok\" = xyes; then\n        ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])\n        :\nelse\n        ax_pthread_ok=no\n        $2\nfi\nAC_LANG_POP\n])dnl AX_PTHREAD\n"
  },
  {
    "path": "m4/ax_require_defined.m4",
    "content": "# ===========================================================================\n#    http://www.gnu.org/software/autoconf-archive/ax_require_defined.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_REQUIRE_DEFINED(MACRO)\n#\n# DESCRIPTION\n#\n#   AX_REQUIRE_DEFINED is a simple helper for making sure other macros have\n#   been defined and thus are available for use.  This avoids random issues\n#   where a macro isn't expanded.  Instead the configure script emits a\n#   non-fatal:\n#\n#     ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found\n#\n#   It's like AC_REQUIRE except it doesn't expand the required macro.\n#\n#   Here's an example:\n#\n#     AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])\n#\n# LICENSE\n#\n#   Copyright (c) 2014 Mike Frysinger <vapier@gentoo.org>\n#\n#   Copying and distribution of this file, with or without modification, are\n#   permitted in any medium without royalty provided the copyright notice\n#   and this notice are preserved. This file is offered as-is, without any\n#   warranty.\n\n#serial 1\n\nAC_DEFUN([AX_REQUIRE_DEFINED], [dnl\n  m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])])\n])dnl AX_REQUIRE_DEFINED\n"
  },
  {
    "path": "m4/ax_with_curses.m4",
    "content": "# ===========================================================================\n#      http://www.gnu.org/software/autoconf-archive/ax_with_curses.html\n# ===========================================================================\n#\n# SYNOPSIS\n#\n#   AX_WITH_CURSES\n#\n# DESCRIPTION\n#\n#   This macro checks whether a SysV or X/Open-compatible Curses library is\n#   present, along with the associated header file.  The NcursesW\n#   (wide-character) library is searched for first, followed by Ncurses,\n#   then the system-default plain Curses.  The first library found is the\n#   one returned. Finding libraries will first be attempted by using\n#   pkg-config, and should the pkg-config files not be available, will\n#   fallback to combinations of known flags itself.\n#\n#   The following options are understood: --with-ncursesw, --with-ncurses,\n#   --without-ncursesw, --without-ncurses.  The \"--with\" options force the\n#   macro to use that particular library, terminating with an error if not\n#   found.  The \"--without\" options simply skip the check for that library.\n#   The effect on the search pattern is:\n#\n#     (no options)                           - NcursesW, Ncurses, Curses\n#     --with-ncurses     --with-ncursesw     - NcursesW only [*]\n#     --without-ncurses  --with-ncursesw     - NcursesW only [*]\n#                        --with-ncursesw     - NcursesW only [*]\n#     --with-ncurses     --without-ncursesw  - Ncurses only [*]\n#     --with-ncurses                         - NcursesW, Ncurses [**]\n#     --without-ncurses  --without-ncursesw  - Curses only\n#                        --without-ncursesw  - Ncurses, Curses\n#     --without-ncurses                      - NcursesW, Curses\n#\n#   [*]  If the library is not found, abort the configure script.\n#\n#   [**] If the second library (Ncurses) is not found, abort configure.\n#\n#   The following preprocessor symbols may be defined by this macro if the\n#   appropriate conditions are met:\n#\n#     HAVE_CURSES             - if any SysV or X/Open Curses library found\n#     HAVE_CURSES_ENHANCED    - if library supports X/Open Enhanced functions\n#     HAVE_CURSES_COLOR       - if library supports color (enhanced functions)\n#     HAVE_CURSES_OBSOLETE    - if library supports certain obsolete features\n#     HAVE_NCURSESW           - if NcursesW (wide char) library is to be used\n#     HAVE_NCURSES            - if the Ncurses library is to be used\n#\n#     HAVE_CURSES_H           - if <curses.h> is present and should be used\n#     HAVE_NCURSESW_H         - if <ncursesw.h> should be used\n#     HAVE_NCURSES_H          - if <ncurses.h> should be used\n#     HAVE_NCURSESW_CURSES_H  - if <ncursesw/curses.h> should be used\n#     HAVE_NCURSES_CURSES_H   - if <ncurses/curses.h> should be used\n#\n#   (These preprocessor symbols are discussed later in this document.)\n#\n#   The following output variables are defined by this macro; they are\n#   precious and may be overridden on the ./configure command line:\n#\n#     CURSES_LIBS  - library to add to xxx_LDADD\n#     CURSES_CFLAGS  - include paths to add to xxx_CPPFLAGS\n#\n#   In previous versions of this macro, the flags CURSES_LIB and\n#   CURSES_CPPFLAGS were defined. These have been renamed, in keeping with\n#   AX_WITH_CURSES's close bigger brother, PKG_CHECK_MODULES, which should\n#   eventually supersede the use of AX_WITH_CURSES. Neither the library\n#   listed in CURSES_LIBS, nor the flags in CURSES_CFLAGS are added to LIBS,\n#   respectively CPPFLAGS, by default. You need to add both to the\n#   appropriate xxx_LDADD/xxx_CPPFLAGS line in your Makefile.am. For\n#   example:\n#\n#     prog_LDADD = @CURSES_LIBS@\n#     prog_CPPFLAGS = @CURSES_CFLAGS@\n#\n#   If CURSES_LIBS is set on the configure command line (such as by running\n#   \"./configure CURSES_LIBS=-lmycurses\"), then the only header searched for\n#   is <curses.h>. If the user needs to specify an alternative path for a\n#   library (such as for a non-standard NcurseW), the user should use the\n#   LDFLAGS variable.\n#\n#   The following shell variables may be defined by this macro:\n#\n#     ax_cv_curses           - set to \"yes\" if any Curses library found\n#     ax_cv_curses_enhanced  - set to \"yes\" if Enhanced functions present\n#     ax_cv_curses_color     - set to \"yes\" if color functions present\n#     ax_cv_curses_obsolete  - set to \"yes\" if obsolete features present\n#\n#     ax_cv_ncursesw      - set to \"yes\" if NcursesW library found\n#     ax_cv_ncurses       - set to \"yes\" if Ncurses library found\n#     ax_cv_plaincurses   - set to \"yes\" if plain Curses library found\n#     ax_cv_curses_which  - set to \"ncursesw\", \"ncurses\", \"plaincurses\" or \"no\"\n#\n#   These variables can be used in your configure.ac to determine the level\n#   of support you need from the Curses library.  For example, if you must\n#   have either Ncurses or NcursesW, you could include:\n#\n#     AX_WITH_CURSES\n#     if test \"x$ax_cv_ncursesw\" != xyes && test \"x$ax_cv_ncurses\" != xyes; then\n#         AC_MSG_ERROR([requires either NcursesW or Ncurses library])\n#     fi\n#\n#   If any Curses library will do (but one must be present and must support\n#   color), you could use:\n#\n#     AX_WITH_CURSES\n#     if test \"x$ax_cv_curses\" != xyes || test \"x$ax_cv_curses_color\" != xyes; then\n#         AC_MSG_ERROR([requires an X/Open-compatible Curses library with color])\n#     fi\n#\n#   Certain preprocessor symbols and shell variables defined by this macro\n#   can be used to determine various features of the Curses library.  In\n#   particular, HAVE_CURSES and ax_cv_curses are defined if the Curses\n#   library found conforms to the traditional SysV and/or X/Open Base Curses\n#   definition.  Any working Curses library conforms to this level.\n#\n#   HAVE_CURSES_ENHANCED and ax_cv_curses_enhanced are defined if the\n#   library supports the X/Open Enhanced Curses definition.  In particular,\n#   the wide-character types attr_t, cchar_t and wint_t, the functions\n#   wattr_set() and wget_wch() and the macros WA_NORMAL and _XOPEN_CURSES\n#   are checked.  The Ncurses library does NOT conform to this definition,\n#   although NcursesW does.\n#\n#   HAVE_CURSES_COLOR and ax_cv_curses_color are defined if the library\n#   supports color functions and macros such as COLOR_PAIR, A_COLOR,\n#   COLOR_WHITE, COLOR_RED and init_pair().  These are NOT part of the\n#   X/Open Base Curses definition, but are part of the Enhanced set of\n#   functions.  The Ncurses library DOES support these functions, as does\n#   NcursesW.\n#\n#   HAVE_CURSES_OBSOLETE and ax_cv_curses_obsolete are defined if the\n#   library supports certain features present in SysV and BSD Curses but not\n#   defined in the X/Open definition.  In particular, the functions\n#   getattrs(), getcurx() and getmaxx() are checked.\n#\n#   To use the HAVE_xxx_H preprocessor symbols, insert the following into\n#   your system.h (or equivalent) header file:\n#\n#     #if defined HAVE_NCURSESW_CURSES_H\n#     #  include <ncursesw/curses.h>\n#     #elif defined HAVE_NCURSESW_H\n#     #  include <ncursesw.h>\n#     #elif defined HAVE_NCURSES_CURSES_H\n#     #  include <ncurses/curses.h>\n#     #elif defined HAVE_NCURSES_H\n#     #  include <ncurses.h>\n#     #elif defined HAVE_CURSES_H\n#     #  include <curses.h>\n#     #else\n#     #  error \"SysV or X/Open-compatible Curses header file required\"\n#     #endif\n#\n#   For previous users of this macro: you should not need to change anything\n#   in your configure.ac or Makefile.am, as the previous (serial 10)\n#   semantics are still valid.  However, you should update your system.h (or\n#   equivalent) header file to the fragment shown above. You are encouraged\n#   also to make use of the extended functionality provided by this version\n#   of AX_WITH_CURSES, as well as in the additional macros\n#   AX_WITH_CURSES_PANEL, AX_WITH_CURSES_MENU and AX_WITH_CURSES_FORM.\n#\n# LICENSE\n#\n#   Copyright (c) 2009 Mark Pulford <mark@kyne.com.au>\n#   Copyright (c) 2009 Damian Pietras <daper@daper.net>\n#   Copyright (c) 2012 Reuben Thomas <rrt@sc3d.org>\n#   Copyright (c) 2011 John Zaitseff <J.Zaitseff@zap.org.au>\n#\n#   This program is free software: you can redistribute it and/or modify it\n#   under the terms of the GNU General Public License as published by the\n#   Free Software Foundation, either version 3 of the License, or (at your\n#   option) any later version.\n#\n#   This program is distributed in the hope that it will be useful, but\n#   WITHOUT ANY WARRANTY; without even the implied warranty of\n#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\n#   Public License for more details.\n#\n#   You should have received a copy of the GNU General Public License along\n#   with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n#   As a special exception, the respective Autoconf Macro's copyright owner\n#   gives unlimited permission to copy, distribute and modify the configure\n#   scripts that are the output of Autoconf when processing the Macro. You\n#   need not follow the terms of the GNU General Public License when using\n#   or distributing such scripts, even though portions of the text of the\n#   Macro appear in them. The GNU General Public License (GPL) does govern\n#   all other use of the material that constitutes the Autoconf Macro.\n#\n#   This special exception to the GPL applies to versions of the Autoconf\n#   Macro released by the Autoconf Archive. When you make and distribute a\n#   modified version of the Autoconf Macro, you may extend this special\n#   exception to the GPL to apply to your modified version as well.\n\n#serial 17\n\n# internal function to factorize common code that is used by both ncurses\n# and ncursesw\nAC_DEFUN([_FIND_CURSES_FLAGS], [\n    AC_MSG_CHECKING([for $1 via pkg-config])\n\n    AX_REQUIRE_DEFINED([PKG_CHECK_EXISTS])\n    _PKG_CONFIG([_ax_cv_$1_libs], [libs], [$1])\n    _PKG_CONFIG([_ax_cv_$1_cppflags], [cflags], [$1])\n\n    AS_IF([test \"x$pkg_failed\" = \"xyes\" || test \"x$pkg_failed\" = \"xuntried\"],[\n        AC_MSG_RESULT([no])\n        # No suitable .pc file found, have to find flags via fallback\n        AC_CACHE_CHECK([for $1 via fallback], [ax_cv_$1], [\n            AS_ECHO()\n            pkg_cv__ax_cv_$1_libs=\"-l$1\"\n            pkg_cv__ax_cv_$1_cppflags=\"-D_GNU_SOURCE $CURSES_CFLAGS\"\n            LIBS=\"$ax_saved_LIBS $pkg_cv__ax_cv_$1_libs\"\n            CPPFLAGS=\"$ax_saved_CPPFLAGS $pkg_cv__ax_cv_$1_cppflags\"\n\n            AC_MSG_CHECKING([for initscr() with $pkg_cv__ax_cv_$1_libs])\n            AC_LINK_IFELSE([AC_LANG_CALL([], [initscr])],\n                [\n                    AC_MSG_RESULT([yes])\n                    AC_MSG_CHECKING([for nodelay() with $pkg_cv__ax_cv_$1_libs])\n                    AC_LINK_IFELSE([AC_LANG_CALL([], [nodelay])],[\n                        ax_cv_$1=yes\n                        ],[\n                        AC_MSG_RESULT([no])\n                        m4_if(\n                            [$1],[ncursesw],[pkg_cv__ax_cv_$1_libs=\"$pkg_cv__ax_cv_$1_libs -ltinfow\"],\n                            [$1],[ncurses],[pkg_cv__ax_cv_$1_libs=\"$pkg_cv__ax_cv_$1_libs -ltinfo\"]\n                        )\n                        LIBS=\"$ax_saved_LIBS $pkg_cv__ax_cv_$1_libs\"\n\n                        AC_MSG_CHECKING([for nodelay() with $pkg_cv__ax_cv_$1_libs])\n                        AC_LINK_IFELSE([AC_LANG_CALL([], [nodelay])],[\n                            ax_cv_$1=yes\n                            ],[\n                            ax_cv_$1=no\n                        ])\n                    ])\n                ],[\n                    ax_cv_$1=no\n            ])\n        ])\n        ],[\n        AC_MSG_RESULT([yes])\n        # Found .pc file, using its information\n        LIBS=\"$ax_saved_LIBS $pkg_cv__ax_cv_$1_libs\"\n        CPPFLAGS=\"$ax_saved_CPPFLAGS $pkg_cv__ax_cv_$1_cppflags\"\n        ax_cv_$1=yes\n    ])\n])\n\nAU_ALIAS([MP_WITH_CURSES], [AX_WITH_CURSES])\nAC_DEFUN([AX_WITH_CURSES], [\n    AC_ARG_VAR([CURSES_LIBS], [linker library for Curses, e.g. -lcurses])\n    AC_ARG_VAR([CURSES_CFLAGS], [preprocessor flags for Curses, e.g. -I/usr/include/ncursesw])\n    AC_ARG_WITH([ncurses], [AS_HELP_STRING([--with-ncurses],\n        [force the use of Ncurses or NcursesW])],\n        [], [with_ncurses=check])\n    AC_ARG_WITH([ncursesw], [AS_HELP_STRING([--without-ncursesw],\n        [do not use NcursesW (wide character support)])],\n        [], [with_ncursesw=check])\n\n    ax_saved_LIBS=$LIBS\n    ax_saved_CPPFLAGS=$CPPFLAGS\n\n    AS_IF([test \"x$with_ncurses\" = xyes || test \"x$with_ncursesw\" = xyes],\n        [ax_with_plaincurses=no], [ax_with_plaincurses=check])\n\n    ax_cv_curses_which=no\n\n    # Test for NcursesW\n    AS_IF([test \"x$CURSES_LIBS\" = x && test \"x$with_ncursesw\" != xno], [\n        _FIND_CURSES_FLAGS([ncursesw])\n\n        AS_IF([test \"x$ax_cv_ncursesw\" = xno && test \"x$with_ncursesw\" = xyes], [\n            AC_MSG_ERROR([--with-ncursesw specified but could not find NcursesW library])\n        ])\n\n        AS_IF([test \"x$ax_cv_ncursesw\" = xyes], [\n            ax_cv_curses=yes\n            ax_cv_curses_which=ncursesw\n            CURSES_LIBS=\"$pkg_cv__ax_cv_ncursesw_libs\"\n            CURSES_CFLAGS=\"$pkg_cv__ax_cv_ncursesw_cppflags\"\n            AC_DEFINE([HAVE_NCURSESW], [1], [Define to 1 if the NcursesW library is present])\n            AC_DEFINE([HAVE_CURSES],   [1], [Define to 1 if a SysV or X/Open compatible Curses library is present])\n\n            AC_CACHE_CHECK([for working ncursesw/curses.h], [ax_cv_header_ncursesw_curses_h], [\n                AC_LINK_IFELSE([AC_LANG_PROGRAM([[\n                        @%:@define _XOPEN_SOURCE_EXTENDED 1\n                        @%:@include <ncursesw/curses.h>\n                    ]], [[\n                        chtype a = A_BOLD;\n                        int b = KEY_LEFT;\n                        chtype c = COLOR_PAIR(1) & A_COLOR;\n                        attr_t d = WA_NORMAL;\n                        cchar_t e;\n                        wint_t f;\n                        int g = getattrs(stdscr);\n                        int h = getcurx(stdscr) + getmaxx(stdscr);\n                        initscr();\n                        init_pair(1, COLOR_WHITE, COLOR_RED);\n                        wattr_set(stdscr, d, 0, NULL);\n                        wget_wch(stdscr, &f);\n                    ]])],\n                    [ax_cv_header_ncursesw_curses_h=yes],\n                    [ax_cv_header_ncursesw_curses_h=no])\n            ])\n            AS_IF([test \"x$ax_cv_header_ncursesw_curses_h\" = xyes], [\n                ax_cv_curses_enhanced=yes\n                ax_cv_curses_color=yes\n                ax_cv_curses_obsolete=yes\n                AC_DEFINE([HAVE_CURSES_ENHANCED],   [1], [Define to 1 if library supports X/Open Enhanced functions])\n                AC_DEFINE([HAVE_CURSES_COLOR],      [1], [Define to 1 if library supports color (enhanced functions)])\n                AC_DEFINE([HAVE_CURSES_OBSOLETE],   [1], [Define to 1 if library supports certain obsolete features])\n                AC_DEFINE([HAVE_NCURSESW_CURSES_H], [1], [Define to 1 if <ncursesw/curses.h> is present])\n            ])\n\n            AC_CACHE_CHECK([for working ncursesw.h], [ax_cv_header_ncursesw_h], [\n                AC_LINK_IFELSE([AC_LANG_PROGRAM([[\n                        @%:@define _XOPEN_SOURCE_EXTENDED 1\n                        @%:@include <ncursesw.h>\n                    ]], [[\n                        chtype a = A_BOLD;\n                        int b = KEY_LEFT;\n                        chtype c = COLOR_PAIR(1) & A_COLOR;\n                        attr_t d = WA_NORMAL;\n                        cchar_t e;\n                        wint_t f;\n                        int g = getattrs(stdscr);\n                        int h = getcurx(stdscr) + getmaxx(stdscr);\n                        initscr();\n                        init_pair(1, COLOR_WHITE, COLOR_RED);\n                        wattr_set(stdscr, d, 0, NULL);\n                        wget_wch(stdscr, &f);\n                    ]])],\n                    [ax_cv_header_ncursesw_h=yes],\n                    [ax_cv_header_ncursesw_h=no])\n            ])\n            AS_IF([test \"x$ax_cv_header_ncursesw_h\" = xyes], [\n                ax_cv_curses_enhanced=yes\n                ax_cv_curses_color=yes\n                ax_cv_curses_obsolete=yes\n                AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions])\n                AC_DEFINE([HAVE_CURSES_COLOR],    [1], [Define to 1 if library supports color (enhanced functions)])\n                AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features])\n                AC_DEFINE([HAVE_NCURSESW_H],      [1], [Define to 1 if <ncursesw.h> is present])\n            ])\n\n            AC_CACHE_CHECK([for working ncurses.h], [ax_cv_header_ncurses_h_with_ncursesw], [\n                AC_LINK_IFELSE([AC_LANG_PROGRAM([[\n                        @%:@define _XOPEN_SOURCE_EXTENDED 1\n                        @%:@include <ncurses.h>\n                    ]], [[\n                        chtype a = A_BOLD;\n                        int b = KEY_LEFT;\n                        chtype c = COLOR_PAIR(1) & A_COLOR;\n                        attr_t d = WA_NORMAL;\n                        cchar_t e;\n                        wint_t f;\n                        int g = getattrs(stdscr);\n                        int h = getcurx(stdscr) + getmaxx(stdscr);\n                        initscr();\n                        init_pair(1, COLOR_WHITE, COLOR_RED);\n                        wattr_set(stdscr, d, 0, NULL);\n                        wget_wch(stdscr, &f);\n                    ]])],\n                    [ax_cv_header_ncurses_h_with_ncursesw=yes],\n                    [ax_cv_header_ncurses_h_with_ncursesw=no])\n            ])\n            AS_IF([test \"x$ax_cv_header_ncurses_h_with_ncursesw\" = xyes], [\n                ax_cv_curses_enhanced=yes\n                ax_cv_curses_color=yes\n                ax_cv_curses_obsolete=yes\n                AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions])\n                AC_DEFINE([HAVE_CURSES_COLOR],    [1], [Define to 1 if library supports color (enhanced functions)])\n                AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features])\n                AC_DEFINE([HAVE_NCURSES_H],       [1], [Define to 1 if <ncurses.h> is present])\n            ])\n\n            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], [\n                AC_MSG_WARN([could not find a working ncursesw/curses.h, ncursesw.h or ncurses.h])\n            ])\n        ])\n    ])\n    unset pkg_cv__ax_cv_ncursesw_libs\n    unset pkg_cv__ax_cv_ncursesw_cppflags\n\n    # Test for Ncurses\n    AS_IF([test \"x$CURSES_LIBS\" = x && test \"x$with_ncurses\" != xno && test \"x$ax_cv_curses_which\" = xno], [\n        _FIND_CURSES_FLAGS([ncurses])\n\n        AS_IF([test \"x$ax_cv_ncurses\" = xno && test \"x$with_ncurses\" = xyes], [\n            AC_MSG_ERROR([--with-ncurses specified but could not find Ncurses library])\n        ])\n\n        AS_IF([test \"x$ax_cv_ncurses\" = xyes], [\n            ax_cv_curses=yes\n            ax_cv_curses_which=ncurses\n            CURSES_LIBS=\"$pkg_cv__ax_cv_ncurses_libs\"\n            CURSES_CFLAGS=\"$pkg_cv__ax_cv_ncurses_cppflags\"\n            AC_DEFINE([HAVE_NCURSES], [1], [Define to 1 if the Ncurses library is present])\n            AC_DEFINE([HAVE_CURSES],  [1], [Define to 1 if a SysV or X/Open compatible Curses library is present])\n\n            AC_CACHE_CHECK([for working ncurses/curses.h], [ax_cv_header_ncurses_curses_h], [\n                AC_LINK_IFELSE([AC_LANG_PROGRAM([[\n                        @%:@include <ncurses/curses.h>\n                    ]], [[\n                        chtype a = A_BOLD;\n                        int b = KEY_LEFT;\n                        chtype c = COLOR_PAIR(1) & A_COLOR;\n                        int g = getattrs(stdscr);\n                        int h = getcurx(stdscr) + getmaxx(stdscr);\n                        initscr();\n                        init_pair(1, COLOR_WHITE, COLOR_RED);\n                    ]])],\n                    [ax_cv_header_ncurses_curses_h=yes],\n                    [ax_cv_header_ncurses_curses_h=no])\n            ])\n            AS_IF([test \"x$ax_cv_header_ncurses_curses_h\" = xyes], [\n                ax_cv_curses_color=yes\n                ax_cv_curses_obsolete=yes\n                AC_DEFINE([HAVE_CURSES_COLOR],     [1], [Define to 1 if library supports color (enhanced functions)])\n                AC_DEFINE([HAVE_CURSES_OBSOLETE],  [1], [Define to 1 if library supports certain obsolete features])\n                AC_DEFINE([HAVE_NCURSES_CURSES_H], [1], [Define to 1 if <ncurses/curses.h> is present])\n            ])\n\n            AC_CACHE_CHECK([for working ncurses.h], [ax_cv_header_ncurses_h], [\n                AC_LINK_IFELSE([AC_LANG_PROGRAM([[\n                        @%:@include <ncurses.h>\n                    ]], [[\n                        chtype a = A_BOLD;\n                        int b = KEY_LEFT;\n                        chtype c = COLOR_PAIR(1) & A_COLOR;\n                        int g = getattrs(stdscr);\n                        int h = getcurx(stdscr) + getmaxx(stdscr);\n                        initscr();\n                        init_pair(1, COLOR_WHITE, COLOR_RED);\n                    ]])],\n                    [ax_cv_header_ncurses_h=yes],\n                    [ax_cv_header_ncurses_h=no])\n            ])\n            AS_IF([test \"x$ax_cv_header_ncurses_h\" = xyes], [\n                ax_cv_curses_color=yes\n                ax_cv_curses_obsolete=yes\n                AC_DEFINE([HAVE_CURSES_COLOR],    [1], [Define to 1 if library supports color (enhanced functions)])\n                AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features])\n                AC_DEFINE([HAVE_NCURSES_H],       [1], [Define to 1 if <ncurses.h> is present])\n            ])\n\n            AS_IF([test \"x$ax_cv_header_ncurses_curses_h\" = xno && test \"x$ax_cv_header_ncurses_h\" = xno], [\n                AC_MSG_WARN([could not find a working ncurses/curses.h or ncurses.h])\n            ])\n        ])\n    ])\n    unset pkg_cv__ax_cv_ncurses_libs\n    unset pkg_cv__ax_cv_ncurses_cppflags\n\n    # Test for plain Curses (or if CURSES_LIBS was set by user)\n    AS_IF([test \"x$with_plaincurses\" != xno && test \"x$ax_cv_curses_which\" = xno], [\n        AS_IF([test \"x$CURSES_LIBS\" != x], [\n            LIBS=\"$ax_saved_LIBS $CURSES_LIBS\"\n        ], [\n            LIBS=\"$ax_saved_LIBS -lcurses\"\n        ])\n\n        AC_CACHE_CHECK([for Curses library], [ax_cv_plaincurses], [\n            AC_LINK_IFELSE([AC_LANG_CALL([], [initscr])],\n                [ax_cv_plaincurses=yes], [ax_cv_plaincurses=no])\n        ])\n\n        AS_IF([test \"x$ax_cv_plaincurses\" = xyes], [\n            ax_cv_curses=yes\n            ax_cv_curses_which=plaincurses\n            AS_IF([test \"x$CURSES_LIBS\" = x], [\n                CURSES_LIBS=\"-lcurses\"\n            ])\n            AC_DEFINE([HAVE_CURSES], [1], [Define to 1 if a SysV or X/Open compatible Curses library is present])\n\n            # Check for base conformance (and header file)\n\n            AC_CACHE_CHECK([for working curses.h], [ax_cv_header_curses_h], [\n                AC_LINK_IFELSE([AC_LANG_PROGRAM([[\n                        @%:@include <curses.h>\n                    ]], [[\n                        chtype a = A_BOLD;\n                        int b = KEY_LEFT;\n                        initscr();\n                    ]])],\n                    [ax_cv_header_curses_h=yes],\n                    [ax_cv_header_curses_h=no])\n            ])\n            AS_IF([test \"x$ax_cv_header_curses_h\" = xyes], [\n                AC_DEFINE([HAVE_CURSES_H], [1], [Define to 1 if <curses.h> is present])\n\n                # Check for X/Open Enhanced conformance\n\n                AC_CACHE_CHECK([for X/Open Enhanced Curses conformance], [ax_cv_plaincurses_enhanced], [\n                    AC_LINK_IFELSE([AC_LANG_PROGRAM([[\n                            @%:@define _XOPEN_SOURCE_EXTENDED 1\n                            @%:@include <curses.h>\n                            @%:@ifndef _XOPEN_CURSES\n                            @%:@error \"this Curses library is not enhanced\"\n                            \"this Curses library is not enhanced\"\n                            @%:@endif\n                        ]], [[\n                            chtype a = A_BOLD;\n                            int b = KEY_LEFT;\n                            chtype c = COLOR_PAIR(1) & A_COLOR;\n                            attr_t d = WA_NORMAL;\n                            cchar_t e;\n                            wint_t f;\n                            initscr();\n                            init_pair(1, COLOR_WHITE, COLOR_RED);\n                            wattr_set(stdscr, d, 0, NULL);\n                            wget_wch(stdscr, &f);\n                        ]])],\n                        [ax_cv_plaincurses_enhanced=yes],\n                        [ax_cv_plaincurses_enhanced=no])\n                ])\n                AS_IF([test \"x$ax_cv_plaincurses_enhanced\" = xyes], [\n                    ax_cv_curses_enhanced=yes\n                    ax_cv_curses_color=yes\n                    AC_DEFINE([HAVE_CURSES_ENHANCED], [1], [Define to 1 if library supports X/Open Enhanced functions])\n                    AC_DEFINE([HAVE_CURSES_COLOR],    [1], [Define to 1 if library supports color (enhanced functions)])\n                ])\n\n                # Check for color functions\n\n                AC_CACHE_CHECK([for Curses color functions], [ax_cv_plaincurses_color], [\n                    AC_LINK_IFELSE([AC_LANG_PROGRAM([[\n                        @%:@define _XOPEN_SOURCE_EXTENDED 1\n                        @%:@include <curses.h>\n                        ]], [[\n                            chtype a = A_BOLD;\n                            int b = KEY_LEFT;\n                            chtype c = COLOR_PAIR(1) & A_COLOR;\n                            initscr();\n                            init_pair(1, COLOR_WHITE, COLOR_RED);\n                        ]])],\n                        [ax_cv_plaincurses_color=yes],\n                        [ax_cv_plaincurses_color=no])\n                ])\n                AS_IF([test \"x$ax_cv_plaincurses_color\" = xyes], [\n                    ax_cv_curses_color=yes\n                    AC_DEFINE([HAVE_CURSES_COLOR], [1], [Define to 1 if library supports color (enhanced functions)])\n                ])\n\n                # Check for obsolete functions\n\n                AC_CACHE_CHECK([for obsolete Curses functions], [ax_cv_plaincurses_obsolete], [\n                AC_LINK_IFELSE([AC_LANG_PROGRAM([[\n                        @%:@include <curses.h>\n                    ]], [[\n                        chtype a = A_BOLD;\n                        int b = KEY_LEFT;\n                        int g = getattrs(stdscr);\n                        int h = getcurx(stdscr) + getmaxx(stdscr);\n                        initscr();\n                    ]])],\n                    [ax_cv_plaincurses_obsolete=yes],\n                    [ax_cv_plaincurses_obsolete=no])\n                ])\n                AS_IF([test \"x$ax_cv_plaincurses_obsolete\" = xyes], [\n                    ax_cv_curses_obsolete=yes\n                    AC_DEFINE([HAVE_CURSES_OBSOLETE], [1], [Define to 1 if library supports certain obsolete features])\n                ])\n            ])\n\n            AS_IF([test \"x$ax_cv_header_curses_h\" = xno], [\n                AC_MSG_WARN([could not find a working curses.h])\n            ])\n        ])\n    ])\n\n    AS_IF([test \"x$ax_cv_curses\"          != xyes], [ax_cv_curses=no])\n    AS_IF([test \"x$ax_cv_curses_enhanced\" != xyes], [ax_cv_curses_enhanced=no])\n    AS_IF([test \"x$ax_cv_curses_color\"    != xyes], [ax_cv_curses_color=no])\n    AS_IF([test \"x$ax_cv_curses_obsolete\" != xyes], [ax_cv_curses_obsolete=no])\n\n    LIBS=$ax_saved_LIBS\n    CPPFLAGS=$ax_saved_CPPFLAGS\n\n    unset ax_saved_LIBS\n    unset ax_saved_CPPFLAGS\n])dnl\n"
  },
  {
    "path": "main.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004-2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <ctype.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/time.h>\n#include <sys/socket.h>\n#include <sys/un.h>\n#include <sys/utsname.h>\n#include <unistd.h>\n#include <signal.h>\n#include <errno.h>\n#include <time.h>\n#include <locale.h>\n#include <assert.h>\n#include <popt.h>\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"interface.h\"\n#include \"options.h\"\n#include \"protocol.h\"\n#include \"log.h\"\n#include \"decoder.h\"\n#include \"lists.h\"\n#include \"files.h\"\n#include \"rcc.h\"\n\nstatic int mocp_argc;\nstatic const char **mocp_argv;\nstatic int popt_next_val = 1;\nstatic char *render_popt_command_line ();\n\n/* List of MOC-specific environment variables. */\nstatic struct {\n\tconst char *name;\n\tconst char *desc;\n} environment_variables[] = {\n\t{\"MOCP_OPTS\", \"Additional command line options\"},\n\t{\"MOCP_POPTRC\", \"List of POPT configuration files\"}\n};\n\nstruct parameters\n{\n\tchar *config_file;\n\tint no_config_file;\n\tint debug;\n\tint only_server;\n\tint foreground;\n\tint append;\n\tint enqueue;\n\tint clear;\n\tint play;\n\tint allow_iface;\n\tint stop;\n\tint exit;\n\tint pause;\n\tint unpause;\n\tint next;\n\tint previous;\n\tint get_file_info;\n\tint toggle_pause;\n\tint playit;\n\tint seek_by;\n\tchar jump_type;\n\tint jump_to;\n\tchar *formatted_info_param;\n\tint get_formatted_info;\n\tchar *adj_volume;\n\tchar *toggle;\n\tchar *on;\n\tchar *off;\n};\n\n/* Connect to the server, return fd of the socket or -1 on error. */\nstatic int server_connect ()\n{\n\tstruct sockaddr_un sock_name;\n\tint sock;\n\n\t/* Create a socket.\n\t * For reasons why AF_UNIX is the correct constant to use in both\n\t * cases, see the commentary the SVN log for commit r2833. */\n\tif ((sock = socket (AF_UNIX, SOCK_STREAM, 0)) == -1)\n\t\t return -1;\n\n\tsock_name.sun_family = AF_UNIX;\n\tstrcpy (sock_name.sun_path, socket_name());\n\n\tif (connect(sock, (struct sockaddr *)&sock_name,\n\t\t\t\tSUN_LEN(&sock_name)) == -1) {\n\t\tclose (sock);\n\t\treturn -1;\n\t}\n\n\treturn sock;\n}\n\n/* Ping the server.\n * Return 1 if the server responds with EV_PONG, otherwise 0. */\nstatic int ping_server (int sock)\n{\n\tint event;\n\n\tsend_int(sock, CMD_PING); /* ignore errors - the server could have\n\t\t\t\t     already closed the connection and sent\n\t\t\t\t     EV_BUSY */\n\tif (!get_int(sock, &event))\n\t\tfatal (\"Error when receiving pong response!\");\n\treturn event == EV_PONG ? 1 : 0;\n}\n\n/* Check if a directory ./.moc exists and create if needed. */\nstatic void check_moc_dir ()\n{\n\tchar *dir_name = create_file_name (\"\");\n\tstruct stat file_stat;\n\n\t/* strip trailing slash */\n\tdir_name[strlen(dir_name)-1] = 0;\n\n\tif (stat (dir_name, &file_stat) == -1) {\n\t\tif (errno != ENOENT)\n\t\t\tfatal (\"Error trying to check for \"CONFIG_DIR\" directory: %s\",\n\t\t\t        xstrerror (errno));\n\n\t\tif (mkdir (dir_name, 0700) == -1)\n\t\t\tfatal (\"Can't create directory %s: %s\",\n\t\t\t\t\tdir_name, xstrerror (errno));\n\t}\n\telse {\n\t\tif (!S_ISDIR(file_stat.st_mode) || access (dir_name, W_OK))\n\t\t\tfatal (\"%s is not a writable directory!\", dir_name);\n\t}\n}\n\n/* Run client and the server if needed. */\nstatic void start_moc (const struct parameters *params, lists_t_strs *args)\n{\n\tint server_sock;\n\n\tif (params->foreground) {\n\t\tset_me_server ();\n\t\tserver_init (params->debug, params->foreground);\n\t\tserver_loop ();\n\t\treturn;\n\t}\n\n\tserver_sock = server_connect ();\n\n\tif (server_sock != -1 && params->only_server)\n\t\tfatal (\"Server is already running!\");\n\n\tif (server_sock == -1) {\n\t\tint i = 0;\n\t\tint notify_pipe[2];\n\t\tssize_t rc;\n\n\t\tprintf (\"Running the server...\\n\");\n\n\t\t/* To notify the client that the server socket is ready */\n\t\tif (pipe(notify_pipe))\n\t\t\tfatal (\"pipe() failed: %s\", xstrerror (errno));\n\n\t\tswitch (fork()) {\n\t\tcase 0: /* child - start server */\n\t\t\tset_me_server ();\n\t\t\tserver_init (params->debug, params->foreground);\n\t\t\trc = write (notify_pipe[1], &i, sizeof(i));\n\t\t\tif (rc < 0)\n\t\t\t\tfatal (\"write() to notify pipe failed: %s\", xstrerror (errno));\n\t\t\tclose (notify_pipe[0]);\n\t\t\tclose (notify_pipe[1]);\n\t\t\tserver_loop ();\n\t\t\toptions_free ();\n\t\t\tdecoder_cleanup ();\n\t\t\tio_cleanup ();\n\t\t\tfiles_cleanup ();\n\t\t\trcc_cleanup ();\n\t\t\tcommon_cleanup ();\n\t\t\texit (EXIT_SUCCESS);\n\t\tcase -1:\n\t\t\tfatal (\"fork() failed: %s\", xstrerror (errno));\n\t\tdefault:\n\t\t\tclose (notify_pipe[1]);\n\t\t\tif (read(notify_pipe[0], &i, sizeof(i)) != sizeof(i))\n\t\t\t\tfatal (\"Server exited!\");\n\t\t\tclose (notify_pipe[0]);\n\t\t\tserver_sock = server_connect ();\n\t\t\tif (server_sock == -1) {\n\t\t\t\tperror (\"server_connect()\");\n\t\t\t\tfatal (\"Can't connect to the server!\");\n\t\t\t}\n\t\t}\n\t}\n\n\tif (params->only_server)\n\t\tsend_int (server_sock, CMD_DISCONNECT);\n\telse {\n\t\txsignal (SIGPIPE, SIG_IGN);\n\t\tif (!ping_server (server_sock))\n\t\t\tfatal (\"Can't connect to the server!\");\n\n\t\tinit_interface (server_sock, params->debug, args);\n\t\tinterface_loop ();\n\t\tinterface_end ();\n\t}\n\n\tclose (server_sock);\n}\n\n/* Send commands requested in params to the server. */\nstatic void server_command (struct parameters *params, lists_t_strs *args)\n{\n\tint sock;\n\n\tif ((sock = server_connect()) == -1)\n\t\tfatal (\"The server is not running!\");\n\n\txsignal (SIGPIPE, SIG_IGN);\n\tif (!ping_server (sock))\n\t\tfatal (\"Can't connect to the server!\");\n\n\tif (params->playit)\n\t\tinterface_cmdline_playit (sock, args);\n\tif (params->clear)\n\t\tinterface_cmdline_clear_plist (sock);\n\tif (params->append)\n\t\tinterface_cmdline_append (sock, args);\n\tif (params->enqueue)\n\t\tinterface_cmdline_enqueue (sock, args);\n\tif (params->play)\n\t\tinterface_cmdline_play_first (sock);\n\tif (params->get_file_info)\n\t\tinterface_cmdline_file_info (sock);\n\tif (params->seek_by)\n\t\tinterface_cmdline_seek_by (sock, params->seek_by);\n\tif (params->jump_type=='%')\n\t\tinterface_cmdline_jump_to_percent (sock,params->jump_to);\n\tif (params->jump_type=='s')\n\t\tinterface_cmdline_jump_to (sock,params->jump_to);\n\tif (params->get_formatted_info)\n\t\tinterface_cmdline_formatted_info (sock, params->formatted_info_param);\n\tif (params->adj_volume)\n\t\tinterface_cmdline_adj_volume (sock, params->adj_volume);\n\tif (params->toggle)\n\t\tinterface_cmdline_set (sock, params->toggle, 2);\n\tif (params->on)\n\t\tinterface_cmdline_set (sock, params->on, 1);\n\tif (params->off)\n\t\tinterface_cmdline_set (sock, params->off, 0);\n\tif (params->exit) {\n\t\tif (!send_int(sock, CMD_QUIT))\n\t\t\tfatal (\"Can't send command!\");\n\t}\n\telse if (params->stop) {\n\t\tif (!send_int(sock, CMD_STOP) || !send_int(sock, CMD_DISCONNECT))\n\t\t\tfatal (\"Can't send commands!\");\n\t}\n\telse if (params->pause) {\n\t\tif (!send_int(sock, CMD_PAUSE) || !send_int(sock, CMD_DISCONNECT))\n\t\t\tfatal (\"Can't send commands!\");\n\t}\n\telse if (params->next) {\n\t\tif (!send_int(sock, CMD_NEXT) || !send_int(sock, CMD_DISCONNECT))\n\t\t\tfatal (\"Can't send commands!\");\n\t}\n\telse if (params->previous) {\n\t\tif (!send_int(sock, CMD_PREV) || !send_int(sock, CMD_DISCONNECT))\n\t\t\tfatal (\"Can't send commands!\");\n\t}\n\telse if (params->unpause) {\n\t\tif (!send_int(sock, CMD_UNPAUSE) || !send_int(sock, CMD_DISCONNECT))\n\t\t\tfatal (\"Can't send commands!\");\n\t}\n\telse if (params->toggle_pause) {\n\t\tint state, ev, cmd = -1;\n\n\t\tif (!send_int(sock, CMD_GET_STATE))\n\t\t\tfatal (\"Can't send commands!\");\n\t\tif (!get_int(sock, &ev) || ev != EV_DATA || !get_int(sock, &state))\n\t\t\tfatal (\"Can't get data from the server!\");\n\n\t\tif (state == STATE_PAUSE)\n\t\t\tcmd = CMD_UNPAUSE;\n\t\telse if (state == STATE_PLAY)\n\t\t\tcmd = CMD_PAUSE;\n\n\t\tif (cmd != -1 && !send_int(sock, cmd))\n\t\t\tfatal (\"Can't send commands!\");\n\t\tif (!send_int(sock, CMD_DISCONNECT))\n\t\t\tfatal (\"Can't send commands!\");\n\t}\n\n\tclose (sock);\n}\n\nstatic void show_version ()\n{\n\tint rc;\n\tstruct utsname uts;\n\n\tputchar ('\\n');\n\tprintf (\"          This is : %s\\n\", PACKAGE_NAME);\n\tprintf (\"          Version : %s\\n\", PACKAGE_VERSION);\n\n#ifdef PACKAGE_REVISION\n\tprintf (\"         Revision : %s\\n\", PACKAGE_REVISION);\n#endif\n\n\t/* Show build time */\n#ifdef __DATE__\n\tprintf (\"            Built : %s\", __DATE__);\n# ifdef __TIME__\n\tprintf (\" %s\", __TIME__);\n# endif\n\tputchar ('\\n');\n#endif\n\n\t/* Show compiled-in components */\n\tprintf (\"    Compiled with :\");\n#ifdef HAVE_OSS\n\tprintf (\" OSS\");\n#endif\n#ifdef HAVE_SNDIO\n\tprintf (\" SNDIO\");\n#endif\n#ifdef HAVE_ALSA\n\tprintf (\" ALSA\");\n#endif\n#ifdef HAVE_JACK\n\tprintf (\" JACK\");\n#endif\n#ifndef NDEBUG\n\tprintf (\" DEBUG\");\n#endif\n#ifdef HAVE_CURL\n\tprintf (\" Network streams\");\n#endif\n#ifdef HAVE_SAMPLERATE\n\tprintf (\" resample\");\n#endif\n\tputchar ('\\n');\n\n\trc = uname (&uts);\n\tif (rc == 0)\n\t\tprintf (\"       Running on : %s %s %s\\n\", uts.sysname, uts.release,\n\t                                                           uts.machine);\n\n\tprintf (\"           Author : Damian Pietras\\n\");\n\tprintf (\"         Homepage : %s\\n\", PACKAGE_URL);\n\tprintf (\"           E-Mail : %s\\n\", PACKAGE_BUGREPORT);\n\tprintf (\"        Copyright : (C) 2003-2016 Damian Pietras and others\\n\");\n\tprintf (\"          License : GNU General Public License, version 2 or later\\n\");\n\tputchar ('\\n');\n}\n\n/* Show program banner. */\nstatic void show_banner ()\n{\n\tprintf (\"%s (version %s\", PACKAGE_NAME, PACKAGE_VERSION);\n#ifdef PACKAGE_REVISION\n\tprintf (\", revision %s\", PACKAGE_REVISION);\n#endif\n\tprintf (\")\\n\");\n}\n\nstatic const char mocp_summary[] = \"[OPTIONS] [FILE|DIR ...]\";\n\n/* Show program usage. */\nstatic void show_usage (poptContext ctx)\n{\n\tshow_banner ();\n\tpoptSetOtherOptionHelp (ctx, mocp_summary);\n\tpoptPrintUsage (ctx, stdout, 0);\n}\n\n/* Show program help. */\nstatic void show_help (poptContext ctx)\n{\n\tsize_t ix;\n\n\tshow_banner ();\n\tpoptSetOtherOptionHelp (ctx, mocp_summary);\n\tpoptPrintHelp (ctx, stdout, 0);\n\n\tprintf (\"\\nEnvironment variables:\\n\\n\");\n\tfor (ix = 0; ix < ARRAY_SIZE(environment_variables); ix += 1)\n\t\tprintf (\"  %-34s%s\\n\", environment_variables[ix].name,\n\t\t                       environment_variables[ix].desc);\n\tprintf (\"\\n\");\n}\n\n/* Show POPT-interpreted command line arguments. */\nstatic void show_args ()\n{\n\tif (mocp_argc > 0) {\n\t\tchar *str;\n\n\t\tstr = getenv (\"MOCP_POPTRC\");\n\t\tif (str)\n\t\t\tprintf (\"MOCP_POPTRC='%s' \", str);\n\n\t\tstr = getenv (\"MOCP_OPTS\");\n\t\tif (str)\n\t\t\tprintf (\"MOCP_OPTS='%s' \", str);\n\n\t\tstr = render_popt_command_line ();\n\t\tprintf (\"%s\\n\", str);\n\t\tfree (str);\n\t}\n}\n\n/* Disambiguate the user's request. */\nstatic void show_misc_cb (poptContext ctx,\n                          enum poptCallbackReason unused1 ATTR_UNUSED,\n                          const struct poptOption *opt,\n                          const char *unused2 ATTR_UNUSED,\n                          void *unused3 ATTR_UNUSED)\n{\n\tswitch (opt->shortName) {\n\tcase 'V':\n\t\tshow_version ();\n\t\tbreak;\n\tcase 'h':\n\t\tshow_help (ctx);\n\t\tbreak;\n\tcase 0:\n\t\tif (!strcmp (opt->longName, \"echo-args\"))\n\t\t\tshow_args ();\n\t\telse\n\t\t\tif (!strcmp (opt->longName, \"usage\"))\n\t\t\t\tshow_usage (ctx);\n\t\tbreak;\n\t}\n\n\texit (EXIT_SUCCESS);\n}\n\nenum {\n\tCL_HANDLED = 0,\n\tCL_NOIFACE,\n\tCL_SDRIVER,\n\tCL_MUSICDIR,\n\tCL_THEME,\n\tCL_SETOPTION,\n\tCL_MOCDIR,\n\tCL_SYNCPL,\n\tCL_NOSYNC,\n\tCL_ASCII,\n\tCL_JUMP,\n\tCL_GETINFO\n};\n\nstatic struct parameters params;\n\nstatic struct poptOption general_opts[] = {\n#ifndef NDEBUG\n\t{\"debug\", 'D', POPT_ARG_NONE, &params.debug, CL_HANDLED,\n\t\t\t\"Turn on logging to a file\", NULL},\n#endif\n\t{\"moc-dir\", 'M', POPT_ARG_STRING, NULL, CL_MOCDIR,\n\t\t\t\"Use the specified MOC directory instead of the default\", \"DIR\"},\n\t{\"music-dir\", 'm', POPT_ARG_NONE, NULL, CL_MUSICDIR,\n\t\t\t\"Start in MusicDir\", NULL},\n\t{\"config\", 'C', POPT_ARG_STRING, &params.config_file, CL_HANDLED,\n\t\t\t\"Use the specified config file instead of the default\"\n\t\t\t\" (conflicts with '--no-config')\", \"FILE\"},\n\t{\"no-config\", 0, POPT_ARG_NONE, &params.no_config_file, CL_HANDLED,\n\t\t\t\"Use program defaults rather than any config file\"\n\t\t\t\" (conflicts with '--config')\", NULL},\n\t{\"set-option\", 'O', POPT_ARG_STRING, NULL, CL_SETOPTION,\n\t\t\t\"Override the configuration option NAME with VALUE\", \"'NAME=VALUE'\"},\n\t{\"foreground\", 'F', POPT_ARG_NONE, &params.foreground, CL_HANDLED,\n\t\t\t\"Run the server in foreground (logging to stdout)\", NULL},\n\t{\"server\", 'S', POPT_ARG_NONE, &params.only_server, CL_HANDLED,\n\t\t\t\"Only run the server\", NULL},\n\t{\"sound-driver\", 'R', POPT_ARG_STRING, NULL, CL_SDRIVER,\n\t\t\t\"Use the first valid sound driver\", \"DRIVERS\"},\n\t{\"ascii\", 'A', POPT_ARG_NONE, NULL, CL_ASCII,\n\t\t\t\"Use ASCII characters to draw lines\", NULL},\n\t{\"theme\", 'T', POPT_ARG_STRING, NULL, CL_THEME,\n\t\t\t\"Use the selected theme file (read from ~/.moc/themes if the path is not absolute)\", \"FILE\"},\n\t{\"sync\", 'y', POPT_ARG_NONE, NULL, CL_SYNCPL,\n\t\t\t\"Synchronize the playlist with other clients\", NULL},\n\t{\"nosync\", 'n', POPT_ARG_NONE, NULL, CL_NOSYNC,\n\t\t\t\"Don't synchronize the playlist with other clients\", NULL},\n\tPOPT_TABLEEND\n};\n\nstatic struct poptOption server_opts[] = {\n\t{\"pause\", 'P', POPT_ARG_NONE, &params.pause, CL_NOIFACE,\n\t\t\t\"Pause\", NULL},\n\t{\"unpause\", 'U', POPT_ARG_NONE, &params.unpause, CL_NOIFACE,\n\t\t\t\"Unpause\", NULL},\n\t{\"toggle-pause\", 'G', POPT_ARG_NONE, &params.toggle_pause, CL_NOIFACE,\n\t\t\t\"Toggle between playing and paused\", NULL},\n\t{\"stop\", 's', POPT_ARG_NONE, &params.stop, CL_NOIFACE,\n\t\t\t\"Stop playing\", NULL},\n\t{\"next\", 'f', POPT_ARG_NONE, &params.next, CL_NOIFACE,\n\t\t\t\"Play the next song\", NULL},\n\t{\"previous\", 'r', POPT_ARG_NONE, &params.previous, CL_NOIFACE,\n\t\t\t\"Play the previous song\", NULL},\n\t{\"seek\", 'k', POPT_ARG_INT, &params.seek_by, CL_NOIFACE,\n\t\t\t\"Seek by N seconds (can be negative)\", \"N\"},\n\t{\"jump\", 'j', POPT_ARG_STRING, NULL, CL_JUMP,\n\t\t\t\"Jump to some position in the current track\", \"N{%,s}\"},\n\t{\"volume\", 'v', POPT_ARG_STRING, &params.adj_volume, CL_NOIFACE,\n\t\t\t\"Adjust the PCM volume\", \"[+,-]LEVEL\"},\n\t{\"exit\", 'x', POPT_ARG_NONE, &params.exit, CL_NOIFACE,\n\t\t\t\"Shutdown the server\", NULL},\n\t{\"append\", 'a', POPT_ARG_NONE, &params.append, CL_NOIFACE,\n\t\t\t\"Append the files/directories/playlists passed in \"\n\t\t\t\"the command line to playlist\", NULL},\n\t{\"recursively\", 'e', POPT_ARG_NONE, &params.append, CL_NOIFACE,\n\t\t\t\"Alias for --append\", NULL},\n\t{\"enqueue\", 'q', POPT_ARG_NONE, &params.enqueue, CL_NOIFACE,\n\t\t\t\"Add the files given on command line to the queue\", NULL},\n\t{\"clear\", 'c', POPT_ARG_NONE, &params.clear, CL_NOIFACE,\n\t\t\t\"Clear the playlist\", NULL},\n\t{\"play\", 'p', POPT_ARG_NONE, &params.play, CL_NOIFACE,\n\t\t\t\"Start playing from the first item on the playlist\", NULL},\n\t{\"playit\", 'l', POPT_ARG_NONE, &params.playit, CL_NOIFACE,\n\t\t\t\"Play files given on command line without modifying the playlist\", NULL},\n\t{\"toggle\", 't', POPT_ARG_STRING, &params.toggle, CL_NOIFACE,\n\t\t\t\"Toggle a control (shuffle, autonext, repeat)\", \"CONTROL\"},\n\t{\"on\", 'o', POPT_ARG_STRING, &params.on, CL_NOIFACE,\n\t\t\t\"Turn on a control (shuffle, autonext, repeat)\", \"CONTROL\"},\n\t{\"off\", 'u', POPT_ARG_STRING, &params.off, CL_NOIFACE,\n\t\t\t\"Turn off a control (shuffle, autonext, repeat)\", \"CONTROL\"},\n\t{\"info\", 'i', POPT_ARG_NONE, &params.get_file_info, CL_NOIFACE,\n\t\t\t\"Print information about the file currently playing\", NULL},\n\t{\"format\", 'Q', POPT_ARG_STRING, &params.formatted_info_param, CL_GETINFO,\n\t\t\t\"Print formatted information about the file currently playing\", \"FORMAT\"},\n\tPOPT_TABLEEND\n};\n\nstatic struct poptOption misc_opts[] = {\n\t{NULL, 0, POPT_ARG_CALLBACK,\n\t       (void *) (uintptr_t) show_misc_cb, 0, NULL, NULL},\n\t{\"version\", 'V', POPT_ARG_NONE, NULL, 0,\n\t\t\t\"Print version information\", NULL},\n\t{\"echo-args\", 0, POPT_ARG_NONE, NULL, 0,\n\t\t\t\"Print POPT-interpreted arguments\", NULL},\n\t{\"usage\", 0, POPT_ARG_NONE, NULL, 0,\n\t\t\t\"Print brief usage\", NULL},\n\t{\"help\", 'h', POPT_ARG_NONE, NULL, 0,\n\t\t\t\"Print extended usage\", NULL},\n\tPOPT_TABLEEND\n};\n\nstatic struct poptOption mocp_opts[] = {\n\t{NULL, 0, POPT_ARG_INCLUDE_TABLE, general_opts, 0, \"General options:\", NULL},\n\t{NULL, 0, POPT_ARG_INCLUDE_TABLE, server_opts, 0, \"Server commands:\", NULL},\n\t{NULL, 0, POPT_ARG_INCLUDE_TABLE, misc_opts, 0, \"Miscellaneous options:\", NULL},\n\tPOPT_AUTOALIAS\n\tPOPT_TABLEEND\n};\n\n/* Read the POPT configuration files as given in MOCP_POPTRC. */\nstatic void read_mocp_poptrc (poptContext ctx, const char *env_poptrc)\n{\n\tint ix, rc, count;\n\tlists_t_strs *files;\n\n\tfiles = lists_strs_new (4);\n\tcount = lists_strs_split (files, env_poptrc, \":\");\n\n\tfor (ix = 0; ix < count; ix += 1) {\n\t\tconst char *fn;\n\n\t\tfn = lists_strs_at (files, ix);\n\t\tif (!strlen (fn))\n\t\t\tcontinue;\n\n\t\tif (!is_secure (fn))\n\t\t\tfatal (\"POPT config file is not secure: %s\", fn);\n\n\t\trc = poptReadConfigFile (ctx, fn);\n\t\tif (rc < 0)\n\t\t\tfatal (\"Error reading POPT config file '%s': %s\",\n\t\t\t        fn, poptStrerror (rc));\n\t}\n\n\tlists_strs_free (files);\n}\n\n/* Check that the ~/.popt file is secure. */\nstatic void check_popt_secure ()\n{\n\tint len;\n\tconst char *home, dot_popt[] = \".popt\";\n\tchar *home_popt;\n\n\thome = get_home ();\n\tlen = strlen (home) + strlen (dot_popt) + 2;\n\thome_popt = xcalloc (len, sizeof (char));\n\tsnprintf (home_popt, len, \"%s/%s\", home, dot_popt);\n\tif (!is_secure (home_popt))\n\t\tfatal (\"POPT config file is not secure: %s\", home_popt);\n\tfree (home_popt);\n}\n\n/* Read the default POPT configuration file. */\nstatic void read_default_poptrc (poptContext ctx)\n{\n\tint rc;\n\n\tcheck_popt_secure ();\n\trc = poptReadDefaultConfig (ctx, 0);\n\n\tif (rc == POPT_ERROR_ERRNO) {\n\t\tint saved_errno = errno;\n\n\t\tfprintf (stderr, \"\\n\"\n\t\t         \"WARNING: The following fatal error message may be bogus!\\n\"\n\t\t         \"         If you have an empty /etc/popt.d directory, try\\n\"\n\t\t         \"         adding an empty file to it.  If that does not fix\\n\"\n\t\t         \"         the problem then you have a genuine error.\\n\");\n\n\t\terrno = saved_errno;\n\t}\n\n\tif (rc != 0)\n\t\tfatal (\"Error reading default POPT config file: %s\",\n\t\t        poptStrerror (rc));\n}\n\n/* Read the POPT configuration files(s). */\nstatic void read_popt_config (poptContext ctx)\n{\n\tconst char *env_poptrc;\n\n\tenv_poptrc = getenv (\"MOCP_POPTRC\");\n\tif (env_poptrc)\n\t\tread_mocp_poptrc (ctx, env_poptrc);\n\telse\n\t\tread_default_poptrc (ctx);\n}\n\n/* Prepend MOCP_OPTS to the command line. */\nstatic void prepend_mocp_opts (poptContext ctx)\n{\n\tint rc;\n\tconst char *env_opts;\n\n\tenv_opts = getenv (\"MOCP_OPTS\");\n\tif (env_opts && strlen (env_opts)) {\n\t\tint env_argc;\n\t\tconst char **env_argv;\n\n\t\trc = poptParseArgvString (env_opts, &env_argc, &env_argv);\n\t\tif (rc < 0)\n\t\t\tfatal (\"Error parsing MOCP_OPTS: %s\", poptStrerror (rc));\n\n\t\trc = poptStuffArgs (ctx, env_argv);\n\t\tif (rc < 0)\n\t\t\tfatal (\"Error prepending MOCP_OPTS: %s\", poptStrerror (rc));\n\n\t\tfree (env_argv);\n\t}\n}\n\nstatic const struct poptOption specials[] = {POPT_AUTOHELP\n                                             POPT_AUTOALIAS\n                                             POPT_TABLEEND};\n\n/* Return true iff 'opt' is a POPT AutoHelp option. */\nstatic inline bool is_autohelp (const struct poptOption *opt)\n{\n\tconst struct poptOption *autohelp = &specials[0];\n\n\treturn opt->argInfo == autohelp->argInfo && opt->arg == autohelp->arg;\n}\n\n/* Return true iff 'opt' is a POPT AutoAlias option. */\nstatic inline bool is_autoalias (const struct poptOption *opt)\n{\n\tconst struct poptOption *autoalias = &specials[1];\n\n\treturn opt->argInfo == autoalias->argInfo && opt->arg == autoalias->arg;\n}\n\n/* Return true iff 'opt' is the POPT end-of-table marker. */\nstatic inline bool is_tableend (const struct poptOption *opt)\n{\n\tconst struct poptOption *tableend = &specials[2];\n\n\treturn opt->longName == tableend->longName &&\n\t       opt->shortName == tableend->shortName &&\n\t       opt->arg == tableend->arg;\n}\n\n/* Return a copy of the POPT option table structure which is suitable\n * for rendering the POPT expansions of the command line. */\nstruct poptOption *clone_popt_options (struct poptOption *opts)\n{\n\tsize_t tally, ix, iy = 0;\n\tstruct poptOption *result;\n\n\tassert (opts);\n\n\tfor (tally = 1; !is_tableend (&opts[tally - 1]); tally += 1);\n\n\tresult = xcalloc (tally, sizeof (struct poptOption));\n\n\tfor (ix = 0; ix < tally; ix += 1) {\n\t\tif (opts[ix].argInfo == POPT_ARG_CALLBACK)\n\t\t\tcontinue;\n\n\t\tif (is_autohelp (&opts[ix]))\n\t\t\tcontinue;\n\n\t\tif (is_autoalias (&opts[ix]))\n\t\t\tcontinue;\n\n\t\tmemcpy (&result[iy], &opts[ix], sizeof (struct poptOption));\n\n\t\tif (is_tableend (&opts[ix]))\n\t\t\tcontinue;\n\n\t\tif (opts[ix].argInfo == POPT_ARG_INCLUDE_TABLE) {\n\t\t\tresult[iy++].arg = clone_popt_options (opts[ix].arg);\n\t\t\tcontinue;\n\t\t}\n\n\t\tswitch (result[iy].argInfo) {\n\t\tcase POPT_ARG_STRING:\n\t\tcase POPT_ARG_INT:\n\t\tcase POPT_ARG_LONG:\n\t\tcase POPT_ARG_FLOAT:\n\t\tcase POPT_ARG_DOUBLE:\n\t\t\tresult[iy].argInfo = POPT_ARG_STRING;\n\t\t\tbreak;\n\t\tcase POPT_ARG_VAL:\n\t\t\tresult[iy].argInfo = POPT_ARG_NONE;\n\t\t\tbreak;\n\t\tcase POPT_ARG_NONE:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfatal (\"Unknown POPT option table argInfo type: %d\",\n\t\t\t                                result[iy].argInfo);\n\t\t}\n\n\t\tresult[iy].arg = NULL;\n\t\tresult[iy++].val = popt_next_val++;\n\t}\n\n\treturn result;\n}\n\n/* Free a copied POPT option table structure. */\nvoid free_popt_clone (struct poptOption *opts)\n{\n\tint ix;\n\n\tassert (opts);\n\n\tfor (ix = 0; !is_tableend (&opts[ix]); ix += 1) {\n\t\tif (opts[ix].argInfo == POPT_ARG_INCLUDE_TABLE)\n\t\t\tfree_popt_clone (opts[ix].arg);\n\t}\n\n\tfree (opts);\n}\n\n/* Return a pointer to the copied POPT option table entry for which the\n * 'val' field matches 'wanted'.  */\nstruct poptOption *find_popt_option (struct poptOption *opts, int wanted)\n{\n\tassert (opts);\n\tassert (LIMIT(wanted, popt_next_val));\n\n\tfor (size_t ix = 0; !is_tableend (&opts[ix]); ix += 1) {\n\t\tstruct poptOption *result;\n\n\t\tassert (opts[ix].argInfo != POPT_ARG_CALLBACK);\n\n\t\tif (opts[ix].val == wanted)\n\t\t\treturn &opts[ix];\n\n\t\tswitch (opts[ix].argInfo) {\n\t\tcase POPT_ARG_INCLUDE_TABLE:\n\t\t\tresult = find_popt_option (opts[ix].arg, wanted);\n\t\t\tif (result)\n\t\t\t\treturn result;\n\t\t\tbreak;\n\t\tcase POPT_ARG_STRING:\n\t\tcase POPT_ARG_INT:\n\t\tcase POPT_ARG_LONG:\n\t\tcase POPT_ARG_FLOAT:\n\t\tcase POPT_ARG_DOUBLE:\n\t\tcase POPT_ARG_VAL:\n\t\tcase POPT_ARG_NONE:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tfatal (\"Unknown POPT option table argInfo type: %d\",\n\t\t\t                                opts[ix].argInfo);\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n/* Render the command line as interpreted by POPT. */\nstatic char *render_popt_command_line ()\n{\n\tint rc;\n\tlists_t_strs *cmdline;\n\tchar *result;\n\tconst char **rest;\n\tpoptContext ctx;\n\tstruct poptOption *null_opts;\n\n\tnull_opts = clone_popt_options (mocp_opts);\n\n\tctx = poptGetContext (\"mocp\", mocp_argc, mocp_argv, null_opts,\n\t                       POPT_CONTEXT_NO_EXEC);\n\n\tread_popt_config (ctx);\n\tprepend_mocp_opts (ctx);\n\n\tcmdline = lists_strs_new (mocp_argc * 2);\n\tlists_strs_append (cmdline, mocp_argv[0]);\n\n\twhile (1) {\n\t\tsize_t len;\n\t\tchar *str;\n\t\tconst char *arg;\n\t\tstruct poptOption *opt;\n\n\t\trc = poptGetNextOpt (ctx);\n\t\tif (rc == -1)\n\t\t\tbreak;\n\n\t\tif (rc == POPT_ERROR_BADOPT) {\n\t\t\tlists_strs_append (cmdline, poptBadOption (ctx, 0));\n\t\t\tcontinue;\n\t\t}\n\n\t\topt = find_popt_option (null_opts, rc);\n\t\tif (!opt) {\n\t\t\tresult = xstrdup (\"Couldn't find option in copied option table!\");\n\t\t\tgoto err;\n\t\t}\n\n\t\targ = poptGetOptArg (ctx);\n\n\t\tif (opt->longName) {\n\t\t\tlen = strlen (opt->longName) + 3;\n\t\t\tif (arg)\n\t\t\t\tlen += strlen (arg) + 3;\n\t\t\tstr = xmalloc (len);\n\n\t\t\tif (arg)\n\t\t\t\tsnprintf (str, len, \"--%s='%s'\", opt->longName, arg);\n\t\t\telse\n\t\t\t\tsnprintf (str, len, \"--%s\", opt->longName);\n\t\t}\n\t\telse {\n\t\t\tlen = 3;\n\t\t\tif (arg)\n\t\t\t\tlen += strlen (arg) + 3;\n\t\t\tstr = xmalloc (len);\n\n\t\t\tif (arg)\n\t\t\t\tsnprintf (str, len, \"-%c '%s'\", opt->shortName, arg);\n\t\t\telse\n\t\t\t\tsnprintf (str, len, \"-%c\", opt->shortName);\n\t\t}\n\n\t\tlists_strs_push (cmdline, str);\n\t\tfree ((void *) arg);\n\t}\n\n\trest = poptGetArgs (ctx);\n\tif (rest)\n\t\tlists_strs_load (cmdline, rest);\n\n\tresult = lists_strs_fmt (cmdline, \"%s \");\n\nerr:\n\tpoptFreeContext (ctx);\n\tfree_popt_clone (null_opts);\n\tlists_strs_free (cmdline);\n\n\treturn result;\n}\n\nstatic void override_config_option (const char *arg, lists_t_strs *deferred)\n{\n\tint len;\n\tbool append;\n\tchar *ptr, *name, *value;\n\tenum option_type type;\n\n\tassert (arg != NULL);\n\n\tptr = strchr (arg, '=');\n\tif (ptr == NULL)\n\t\tgoto error;\n\n\t/* Allow for list append operator (\"+=\"). */\n\tappend = (ptr > arg && *(ptr - 1) == '+');\n\n\tname = trim (arg, ptr - arg - (append ? 1 : 0));\n\tif (!name || !name[0])\n\t\tgoto error;\n\ttype = options_get_type (name);\n\n\tif (type == OPTION_LIST) {\n\t\tif (deferred) {\n\t\t\tlists_strs_append (deferred, arg);\n\t\t\tfree (name);\n\t\t\treturn;\n\t\t}\n\t}\n\telse if (append)\n\t\tgoto error;\n\n\tvalue = trim (ptr + 1, strlen (ptr + 1));\n\tif (!value || !value[0])\n\t\tgoto error;\n\n\tif (value[0] == '\\'' || value[0] == '\"') {\n\t\tlen = strlen (value);\n\t\tif (value[0] != value[len - 1])\n\t\t\tgoto error;\n\t\tif (strlen (value) < 2)\n\t\t\tgoto error;\n\t\tmemmove (value, value + 1, len - 2);\n\t\tvalue[len - 2] = 0x00;\n\t}\n\n\tif (!options_set_pair (name, value, append))\n\t\tgoto error;\n\toptions_ignore_config (name);\n\n\tfree (name);\n\tfree (value);\n\treturn;\n\nerror:\n\tfatal (\"Malformed override option: %s\", arg);\n}\n\nstatic long get_num_param (const char *p,const char ** last)\n{\n\tchar *e;\n\tlong val;\n\n\tval = strtol (p, &e, 10);\n\tif ((*e&&last==NULL)||e==p)\n\t\tfatal (\"The parameter should be a number!\");\n\n\tif (last)\n\t\t*last=e;\n\treturn val;\n}\n\n/* Process the command line options. */\nstatic void process_options (poptContext ctx, lists_t_strs *deferred)\n{\n\tint rc;\n\n\twhile ((rc = poptGetNextOpt (ctx)) >= 0) {\n\t\tconst char *jump_type, *arg;\n\n\t\targ = poptGetOptArg (ctx);\n\n\t\tswitch (rc) {\n\t\tcase CL_SDRIVER:\n\t\t\tif (!options_check_list (\"SoundDriver\", arg))\n\t\t\t\tfatal (\"No such sound driver: %s\", arg);\n\t\t\toptions_set_list (\"SoundDriver\", arg, false);\n\t\t\toptions_ignore_config (\"SoundDriver\");\n\t\t\tbreak;\n\t\tcase CL_MUSICDIR:\n\t\t\toptions_set_bool (\"StartInMusicDir\", true);\n\t\t\toptions_ignore_config (\"StartInMusicDir\");\n\t\t\tbreak;\n\t\tcase CL_NOIFACE:\n\t\t\tparams.allow_iface = 0;\n\t\t\tbreak;\n\t\tcase CL_THEME:\n\t\t\toptions_set_str (\"ForceTheme\", arg);\n\t\t\tbreak;\n\t\tcase CL_SETOPTION:\n\t\t\toverride_config_option (arg, deferred);\n\t\t\tbreak;\n\t\tcase CL_MOCDIR:\n\t\t\toptions_set_str (\"MOCDir\", arg);\n\t\t\toptions_ignore_config (\"MOCDir\");\n\t\t\tbreak;\n\t\tcase CL_SYNCPL:\n\t\t\toptions_set_bool (\"SyncPlaylist\", true);\n\t\t\toptions_ignore_config (\"SyncPlaylist\");\n\t\t\tbreak;\n\t\tcase CL_NOSYNC:\n\t\t\toptions_set_bool (\"SyncPlaylist\", false);\n\t\t\toptions_ignore_config (\"SyncPlaylist\");\n\t\t\tbreak;\n\t\tcase CL_ASCII:\n\t\t\toptions_set_bool (\"ASCIILines\", true);\n\t\t\toptions_ignore_config (\"ASCIILines\");\n\t\t\tbreak;\n\t\tcase CL_JUMP:\n\t\t\tparams.jump_to = get_num_param (arg, &jump_type);\n\t\t\tif (*jump_type)\n\t\t\t\tif (!jump_type[1])\n\t\t\t\t\tif (*jump_type == '%' || tolower (*jump_type) == 's') {\n\t\t\t\t\t\tparams.jump_type = tolower (*jump_type);\n\t\t\t\t\t\tparams.allow_iface = 0;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t//TODO: Add message explaining the error\n\t\t\tshow_usage (ctx);\n\t\t\texit (EXIT_FAILURE);\n\t\tcase CL_GETINFO:\n\t\t\tparams.get_formatted_info = 1;\n\t\t\tparams.allow_iface = 0;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tshow_usage (ctx);\n\t\t\texit (EXIT_FAILURE);\n\t\t}\n\n\t\tfree ((void *) arg);\n\t}\n\n\tif (rc < -1) {\n\t\tconst char *opt, *alias;\n\n\t\topt = poptBadOption (ctx, 0);\n\t\talias = poptBadOption (ctx, POPT_BADOPTION_NOALIAS);\n\n\t\t/* poptBadOption() with POPT_BADOPTION_NOALIAS fails to\n\t\t * return the correct option if poptStuffArgs() was used. */\n\t\tif (!strcmp (opt, alias) || getenv (\"MOCP_OPTS\"))\n\t\t\tfatal (\"%s: %s\", opt, poptStrerror (rc));\n\t\telse\n\t\t\tfatal (\"%s (aliased by %s): %s\", opt, alias, poptStrerror (rc));\n\t}\n\n\tif (params.config_file && params.no_config_file)\n\t\tfatal (\"Conflicting --config and --no-config options given!\");\n}\n\n/* Process the command line options and arguments. */\nstatic lists_t_strs *process_command_line (lists_t_strs *deferred)\n{\n\tconst char **rest;\n\tpoptContext ctx;\n\tlists_t_strs *result;\n\n\tassert (deferred != NULL);\n\n\tctx = poptGetContext (\"mocp\", mocp_argc, mocp_argv, mocp_opts, 0);\n\n\tread_popt_config (ctx);\n\tprepend_mocp_opts (ctx);\n\tprocess_options (ctx, deferred);\n\n\tif (params.foreground)\n\t\tparams.only_server = 1;\n\n\tresult = lists_strs_new (4);\n\trest = poptGetArgs (ctx);\n\tif (rest)\n\t\tlists_strs_load (result, rest);\n\n\tpoptFreeContext (ctx);\n\n\treturn result;\n}\n\nstatic void process_deferred_overrides (lists_t_strs *deferred)\n{\n\tint ix;\n\tbool cleared;\n\tconst char marker[] = \"*Marker*\";\n\tchar **config_decoders;\n\tlists_t_strs *decoders_option;\n\n\t/* We need to shuffle the PreferredDecoders list into the\n\t * right order as we load any deferred overriding options. */\n\n\tdecoders_option = options_get_list (\"PreferredDecoders\");\n\tlists_strs_reverse (decoders_option);\n\tconfig_decoders = lists_strs_save (decoders_option);\n\tlists_strs_clear (decoders_option);\n\tlists_strs_append (decoders_option, marker);\n\n\tfor (ix = 0; ix < lists_strs_size (deferred); ix += 1)\n\t\toverride_config_option (lists_strs_at (deferred, ix), NULL);\n\n\tcleared = lists_strs_empty (decoders_option) ||\n\t          strcmp (lists_strs_at (decoders_option, 0), marker) != 0;\n\tlists_strs_reverse (decoders_option);\n\tif (!cleared) {\n\t\tchar **override_decoders;\n\n\t\tfree (lists_strs_pop (decoders_option));\n\t\toverride_decoders = lists_strs_save (decoders_option);\n\t\tlists_strs_clear (decoders_option);\n\t\tlists_strs_load (decoders_option, (const char **)config_decoders);\n\t\tlists_strs_load (decoders_option, (const char **)override_decoders);\n\t\tfree (override_decoders);\n\t}\n\tfree (config_decoders);\n}\n\nstatic void log_environment_variables ()\n{\n#ifndef NDEBUG\n\tsize_t ix;\n\n\tfor (ix = 0; ix < ARRAY_SIZE(environment_variables); ix += 1) {\n\t\tchar *str;\n\n\t\tstr = getenv (environment_variables[ix].name);\n\t\tif (str)\n\t\t\tlogit (\"%s='%s'\", environment_variables[ix].name, str);\n\t}\n#endif\n}\n\n/* Log the command line which launched MOC. */\nstatic void log_command_line ()\n{\n#ifndef NDEBUG\n\tlists_t_strs *cmdline;\n\tchar *str;\n\n\tcmdline = lists_strs_new (mocp_argc);\n\tif (lists_strs_load (cmdline, mocp_argv) > 0)\n\t\tstr = lists_strs_fmt (cmdline, \"%s \");\n\telse\n\t\tstr = xstrdup (\"No command line available\");\n\tlogit (\"%s\", str);\n\tfree (str);\n\tlists_strs_free (cmdline);\n#endif\n}\n\n/* Log the command line as interpreted by POPT. */\nstatic void log_popt_command_line ()\n{\n#ifndef NDEBUG\n\tif (mocp_argc > 0) {\n\t\tchar *str;\n\n\t\tstr = render_popt_command_line ();\n\t\tlogit (\"%s\", str);\n\t\tfree (str);\n\t}\n#endif\n}\n\nint main (int argc, const char *argv[])\n{\n\tlists_t_strs *deferred_overrides, *args;\n\n\tassert (argc >= 0);\n\tassert (argv != NULL);\n\tassert (argv[argc] == NULL);\n\n\tmocp_argc = argc;\n\tmocp_argv = argv;\n\n#ifdef PACKAGE_REVISION\n\tlogit (\"This is Music On Console (revision %s)\", PACKAGE_REVISION);\n#else\n\tlogit (\"This is Music On Console (version %s)\", PACKAGE_VERSION);\n#endif\n\n#ifdef CONFIGURATION\n\tlogit (\"Configured:%s\", CONFIGURATION);\n#endif\n\n#if !defined(NDEBUG)\n\t{\n\t\tint rc;\n\t\tstruct utsname uts;\n\n\t\trc = uname (&uts);\n\t\tif (rc == 0)\n\t\t\tlogit (\"Running on: %s %s %s\", uts.sysname, uts.release, uts.machine);\n\t}\n#endif\n\n\tfiles_init ();\n\n\tif (get_home () == NULL)\n\t\tfatal (\"Could not determine user's home directory!\");\n\n\tmemset (&params, 0, sizeof(params));\n\tparams.allow_iface = 1;\n\toptions_init ();\n\tdeferred_overrides = lists_strs_new (4);\n\n\t/* set locale according to the environment variables */\n\tif (!setlocale(LC_ALL, \"\"))\n\t\tlogit (\"Could not set locale!\");\n\n\tlog_environment_variables ();\n\tlog_command_line ();\n\targs = process_command_line (deferred_overrides);\n\tlog_popt_command_line ();\n\n\tif (!params.allow_iface && params.only_server)\n\t\tfatal (\"Server command options can't be used with --server!\");\n\n\tif (!params.no_config_file) {\n\t\tif (params.config_file) {\n\t\t\tif (!can_read_file (params.config_file))\n\t\t\t\tfatal (\"Configuration file is not readable: %s\",\n\t\t\t\t        params.config_file);\n\t\t}\n\t\telse\n\t\t\tparams.config_file = create_file_name (\"config\");\n\t\toptions_parse (params.config_file);\n\t}\n\n\tprocess_deferred_overrides (deferred_overrides);\n\tlists_strs_free (deferred_overrides);\n\tdeferred_overrides = NULL;\n\n\tcheck_moc_dir ();\n\n\tio_init ();\n\trcc_init ();\n\tdecoder_init (params.debug);\n\tsrand (time(NULL));\n\n\tif (params.allow_iface)\n\t\tstart_moc (&params, args);\n\telse\n\t\tserver_command (&params, args);\n\n\tlists_strs_free (args);\n\toptions_free ();\n\tdecoder_cleanup ();\n\tio_cleanup ();\n\trcc_cleanup ();\n\tfiles_cleanup ();\n\tcommon_cleanup ();\n\n\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "md5.c",
    "content": "/* -*- buffer-read-only: t -*- vi: set ro: */\n/* DO NOT EDIT! GENERATED AUTOMATICALLY! */\n/* Functions to compute MD5 message digest of files or memory blocks.\n   according to the definition of MD5 in RFC 1321 from April 1992.\n   Copyright (C) 1995, 1996, 1997, 1999, 2000, 2001, 2005, 2006, 2008, 2009,\n   2010 Free Software Foundation, Inc.\n   This file is part of the GNU C Library.\n\n   This program is free software; you can redistribute it and/or modify it\n   under the terms of the GNU General Public License as published by the\n   Free Software Foundation; either version 3, or (at your option) any\n   later version.\n\n   This program is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this program; if not, write to the Free Software Foundation,\n   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */\n\n/* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.  */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include \"md5.h\"\n#include \"common.h\"\n\n#include <stddef.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/types.h>\n\n#if USE_UNLOCKED_IO\n# include \"unlocked-io.h\"\n#endif\n\n#ifdef _LIBC\n# include <endian.h>\n# if __BYTE_ORDER == __BIG_ENDIAN\n#  define WORDS_BIGENDIAN 1\n# endif\n/* We need to keep the namespace clean so define the MD5 function\n   protected using leading __ .  */\n# define md5_init_ctx __md5_init_ctx\n# define md5_process_block __md5_process_block\n# define md5_process_bytes __md5_process_bytes\n# define md5_finish_ctx __md5_finish_ctx\n# define md5_read_ctx __md5_read_ctx\n# define md5_stream __md5_stream\n# define md5_buffer __md5_buffer\n#endif\n\n#ifdef WORDS_BIGENDIAN\n# define SWAP(n)                                                        \\\n    (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))\n#else\n# define SWAP(n) (n)\n#endif\n\n#define BLOCKSIZE 32768\n#if BLOCKSIZE % 64 != 0\n# error \"invalid BLOCKSIZE\"\n#endif\n\n/* This array contains the bytes used to pad the buffer to the next\n   64-byte boundary.  (RFC 1321, 3.1: Step 1)  */\nstatic const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ...  */ };\n\n\n/* Initialize structure containing state of computation.\n   (RFC 1321, 3.3: Step 3)  */\nvoid\nmd5_init_ctx (struct md5_ctx *ctx)\n{\n  ctx->A = 0x67452301;\n  ctx->B = 0xefcdab89;\n  ctx->C = 0x98badcfe;\n  ctx->D = 0x10325476;\n\n  ctx->total[0] = ctx->total[1] = 0;\n  ctx->buflen = 0;\n}\n\n/* Copy the 4 byte value from v into the memory location pointed to by *cp,\n   If your architecture allows unaligned access this is equivalent to\n   * (uint32_t *) cp = v  */\nstatic inline void\nset_uint32 (char *cp, uint32_t v)\n{\n  memcpy (cp, &v, sizeof v);\n}\n\n/* Put result from CTX in first 16 bytes following RESBUF.  The result\n   must be in little endian byte order.  */\nvoid *\nmd5_read_ctx (const struct md5_ctx *ctx, void *resbuf)\n{\n  char *r = resbuf;\n  set_uint32 (r + 0 * sizeof ctx->A, SWAP (ctx->A));\n  set_uint32 (r + 1 * sizeof ctx->B, SWAP (ctx->B));\n  set_uint32 (r + 2 * sizeof ctx->C, SWAP (ctx->C));\n  set_uint32 (r + 3 * sizeof ctx->D, SWAP (ctx->D));\n\n  return resbuf;\n}\n\n/* Process the remaining bytes in the internal buffer and the usual\n   prolog according to the standard and write the result to RESBUF.  */\nvoid *\nmd5_finish_ctx (struct md5_ctx *ctx, void *resbuf)\n{\n  /* Take yet unprocessed bytes into account.  */\n  uint32_t bytes = ctx->buflen;\n  size_t size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4;\n\n  /* Now count remaining bytes.  */\n  ctx->total[0] += bytes;\n  if (ctx->total[0] < bytes)\n    ++ctx->total[1];\n\n  /* Put the 64-bit file length in *bits* at the end of the buffer.  */\n  ctx->buffer[size - 2] = SWAP (ctx->total[0] << 3);\n  ctx->buffer[size - 1] = SWAP ((ctx->total[1] << 3) | (ctx->total[0] >> 29));\n\n  memcpy (&((char *) ctx->buffer)[bytes], fillbuf, (size - 2) * 4 - bytes);\n\n  /* Process last bytes.  */\n  md5_process_block (ctx->buffer, size * 4, ctx);\n\n  return md5_read_ctx (ctx, resbuf);\n}\n\n/* Compute MD5 message digest for bytes read from STREAM.  The\n   resulting message digest number will be written into the 16 bytes\n   beginning at RESBLOCK.  */\nint\nmd5_stream (FILE *stream, void *resblock)\n{\n  struct md5_ctx ctx;\n  size_t sum;\n\n  char *buffer = xmalloc (BLOCKSIZE + 72);\n  if (!buffer)\n    return 1;\n\n  /* Initialize the computation context.  */\n  md5_init_ctx (&ctx);\n\n  /* Iterate over full file contents.  */\n  while (1)\n    {\n      /* We read the file in blocks of BLOCKSIZE bytes.  One call of the\n         computation function processes the whole buffer so that with the\n         next round of the loop another block can be read.  */\n      size_t n;\n      sum = 0;\n\n      /* Read block.  Take care for partial reads.  */\n      while (1)\n        {\n          n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream);\n\n          sum += n;\n\n          if (sum == BLOCKSIZE)\n            break;\n\n          if (n == 0)\n            {\n              /* Check for the error flag IFF N == 0, so that we don't\n                 exit the loop after a partial read due to e.g., EAGAIN\n                 or EWOULDBLOCK.  */\n              if (ferror (stream))\n                {\n                  free (buffer);\n                  return 1;\n                }\n              goto process_partial_block;\n            }\n\n          /* We've read at least one byte, so ignore errors.  But always\n             check for EOF, since feof may be true even though N > 0.\n             Otherwise, we could end up calling fread after EOF.  */\n          if (feof (stream))\n            goto process_partial_block;\n        }\n\n      /* Process buffer with BLOCKSIZE bytes.  Note that\n         BLOCKSIZE % 64 == 0\n       */\n      md5_process_block (buffer, BLOCKSIZE, &ctx);\n    }\n\nprocess_partial_block:\n\n  /* Process any remaining bytes.  */\n  if (sum > 0)\n    md5_process_bytes (buffer, sum, &ctx);\n\n  /* Construct result in desired memory.  */\n  md5_finish_ctx (&ctx, resblock);\n  free (buffer);\n  return 0;\n}\n\n/* Compute MD5 message digest for LEN bytes beginning at BUFFER.  The\n   result is always in little endian byte order, so that a byte-wise\n   output yields to the wanted ASCII representation of the message\n   digest.  */\nvoid *\nmd5_buffer (const char *buffer, size_t len, void *resblock)\n{\n  struct md5_ctx ctx;\n\n  /* Initialize the computation context.  */\n  md5_init_ctx (&ctx);\n\n  /* Process whole buffer but last len % 64 bytes.  */\n  md5_process_bytes (buffer, len, &ctx);\n\n  /* Put result in desired memory area.  */\n  return md5_finish_ctx (&ctx, resblock);\n}\n\n\nvoid\nmd5_process_bytes (const void *buffer, size_t len, struct md5_ctx *ctx)\n{\n  /* When we already have some bits in our internal buffer concatenate\n     both inputs first.  */\n  if (ctx->buflen != 0)\n    {\n      size_t left_over = ctx->buflen;\n      size_t add = 128 - left_over > len ? len : 128 - left_over;\n\n      memcpy (&((char *) ctx->buffer)[left_over], buffer, add);\n      ctx->buflen += add;\n\n      if (ctx->buflen > 64)\n        {\n          md5_process_block (ctx->buffer, ctx->buflen & ~63, ctx);\n\n          ctx->buflen &= 63;\n          /* The regions in the following copy operation cannot overlap.  */\n          memcpy (ctx->buffer,\n                  &((char *) ctx->buffer)[(left_over + add) & ~63],\n                  ctx->buflen);\n        }\n\n      buffer = (const char *) buffer + add;\n      len -= add;\n    }\n\n  /* Process available complete blocks.  */\n  if (len >= 64)\n    {\n#if !_STRING_ARCH_unaligned\n# define alignof(type) offsetof (struct { char c; type x; }, x)\n# define UNALIGNED_P(p) (((size_t) p) % alignof (uint32_t) != 0)\n      if (UNALIGNED_P (buffer))\n        while (len > 64)\n          {\n            md5_process_block (memcpy (ctx->buffer, buffer, 64), 64, ctx);\n            buffer = (const char *) buffer + 64;\n            len -= 64;\n          }\n      else\n#endif\n        {\n          md5_process_block (buffer, len & ~63, ctx);\n          buffer = (const char *) buffer + (len & ~63);\n          len &= 63;\n        }\n    }\n\n  /* Move remaining bytes in internal buffer.  */\n  if (len > 0)\n    {\n      size_t left_over = ctx->buflen;\n\n      memcpy (&((char *) ctx->buffer)[left_over], buffer, len);\n      left_over += len;\n      if (left_over >= 64)\n        {\n          md5_process_block (ctx->buffer, 64, ctx);\n          left_over -= 64;\n          memcpy (ctx->buffer, &ctx->buffer[16], left_over);\n        }\n      ctx->buflen = left_over;\n    }\n}\n\n\n/* These are the four functions used in the four steps of the MD5 algorithm\n   and defined in the RFC 1321.  The first function is a little bit optimized\n   (as found in Colin Plumbs public domain implementation).  */\n/* #define FF(b, c, d) ((b & c) | (~b & d)) */\n#define FF(b, c, d) (d ^ (b & (c ^ d)))\n#define FG(b, c, d) FF (d, b, c)\n#define FH(b, c, d) (b ^ c ^ d)\n#define FI(b, c, d) (c ^ (b | ~d))\n\n/* Process LEN bytes of BUFFER, accumulating context into CTX.\n   It is assumed that LEN % 64 == 0.  */\n\nvoid\nmd5_process_block (const void *buffer, size_t len, struct md5_ctx *ctx)\n{\n  uint32_t correct_words[16];\n  const uint32_t *words = buffer;\n  size_t nwords = len / sizeof (uint32_t);\n  const uint32_t *endp = words + nwords;\n  uint32_t A = ctx->A;\n  uint32_t B = ctx->B;\n  uint32_t C = ctx->C;\n  uint32_t D = ctx->D;\n\n  /* First increment the byte count.  RFC 1321 specifies the possible\n     length of the file up to 2^64 bits.  Here we only compute the\n     number of bytes.  Do a double word increment.  */\n  ctx->total[0] += len;\n  if (ctx->total[0] < len)\n    ++ctx->total[1];\n\n  /* Process all bytes in the buffer with 64 bytes in each round of\n     the loop.  */\n  while (words < endp)\n    {\n      uint32_t *cwp = correct_words;\n      uint32_t A_save = A;\n      uint32_t B_save = B;\n      uint32_t C_save = C;\n      uint32_t D_save = D;\n\n      /* First round: using the given function, the context and a constant\n         the next context is computed.  Because the algorithms processing\n         unit is a 32-bit word and it is determined to work on words in\n         little endian byte order we perhaps have to change the byte order\n         before the computation.  To reduce the work for the next steps\n         we store the swapped words in the array CORRECT_WORDS.  */\n\n#define OP(a, b, c, d, s, T)                                            \\\n      do                                                                \\\n        {                                                               \\\n          a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T;             \\\n          ++words;                                                      \\\n          CYCLIC (a, s);                                                \\\n          a += b;                                                       \\\n        }                                                               \\\n      while (0)\n\n      /* It is unfortunate that C does not provide an operator for\n         cyclic rotation.  Hope the C compiler is smart enough.  */\n#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))\n\n      /* Before we start, one word to the strange constants.\n         They are defined in RFC 1321 as\n\n         T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64\n\n         Here is an equivalent invocation using Perl:\n\n         perl -e 'foreach(1..64){printf \"0x%08x\\n\", int (4294967296 * abs (sin $_))}'\n       */\n\n      /* Round 1.  */\n      OP (A, B, C, D, 7, 0xd76aa478);\n      OP (D, A, B, C, 12, 0xe8c7b756);\n      OP (C, D, A, B, 17, 0x242070db);\n      OP (B, C, D, A, 22, 0xc1bdceee);\n      OP (A, B, C, D, 7, 0xf57c0faf);\n      OP (D, A, B, C, 12, 0x4787c62a);\n      OP (C, D, A, B, 17, 0xa8304613);\n      OP (B, C, D, A, 22, 0xfd469501);\n      OP (A, B, C, D, 7, 0x698098d8);\n      OP (D, A, B, C, 12, 0x8b44f7af);\n      OP (C, D, A, B, 17, 0xffff5bb1);\n      OP (B, C, D, A, 22, 0x895cd7be);\n      OP (A, B, C, D, 7, 0x6b901122);\n      OP (D, A, B, C, 12, 0xfd987193);\n      OP (C, D, A, B, 17, 0xa679438e);\n      OP (B, C, D, A, 22, 0x49b40821);\n\n      /* For the second to fourth round we have the possibly swapped words\n         in CORRECT_WORDS.  Redefine the macro to take an additional first\n         argument specifying the function to use.  */\n#undef OP\n#define OP(f, a, b, c, d, k, s, T)                                      \\\n      do                                                                \\\n        {                                                               \\\n          a += f (b, c, d) + correct_words[k] + T;                      \\\n          CYCLIC (a, s);                                                \\\n          a += b;                                                       \\\n        }                                                               \\\n      while (0)\n\n      /* Round 2.  */\n      OP (FG, A, B, C, D, 1, 5, 0xf61e2562);\n      OP (FG, D, A, B, C, 6, 9, 0xc040b340);\n      OP (FG, C, D, A, B, 11, 14, 0x265e5a51);\n      OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa);\n      OP (FG, A, B, C, D, 5, 5, 0xd62f105d);\n      OP (FG, D, A, B, C, 10, 9, 0x02441453);\n      OP (FG, C, D, A, B, 15, 14, 0xd8a1e681);\n      OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8);\n      OP (FG, A, B, C, D, 9, 5, 0x21e1cde6);\n      OP (FG, D, A, B, C, 14, 9, 0xc33707d6);\n      OP (FG, C, D, A, B, 3, 14, 0xf4d50d87);\n      OP (FG, B, C, D, A, 8, 20, 0x455a14ed);\n      OP (FG, A, B, C, D, 13, 5, 0xa9e3e905);\n      OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8);\n      OP (FG, C, D, A, B, 7, 14, 0x676f02d9);\n      OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a);\n\n      /* Round 3.  */\n      OP (FH, A, B, C, D, 5, 4, 0xfffa3942);\n      OP (FH, D, A, B, C, 8, 11, 0x8771f681);\n      OP (FH, C, D, A, B, 11, 16, 0x6d9d6122);\n      OP (FH, B, C, D, A, 14, 23, 0xfde5380c);\n      OP (FH, A, B, C, D, 1, 4, 0xa4beea44);\n      OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9);\n      OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60);\n      OP (FH, B, C, D, A, 10, 23, 0xbebfbc70);\n      OP (FH, A, B, C, D, 13, 4, 0x289b7ec6);\n      OP (FH, D, A, B, C, 0, 11, 0xeaa127fa);\n      OP (FH, C, D, A, B, 3, 16, 0xd4ef3085);\n      OP (FH, B, C, D, A, 6, 23, 0x04881d05);\n      OP (FH, A, B, C, D, 9, 4, 0xd9d4d039);\n      OP (FH, D, A, B, C, 12, 11, 0xe6db99e5);\n      OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8);\n      OP (FH, B, C, D, A, 2, 23, 0xc4ac5665);\n\n      /* Round 4.  */\n      OP (FI, A, B, C, D, 0, 6, 0xf4292244);\n      OP (FI, D, A, B, C, 7, 10, 0x432aff97);\n      OP (FI, C, D, A, B, 14, 15, 0xab9423a7);\n      OP (FI, B, C, D, A, 5, 21, 0xfc93a039);\n      OP (FI, A, B, C, D, 12, 6, 0x655b59c3);\n      OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92);\n      OP (FI, C, D, A, B, 10, 15, 0xffeff47d);\n      OP (FI, B, C, D, A, 1, 21, 0x85845dd1);\n      OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f);\n      OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0);\n      OP (FI, C, D, A, B, 6, 15, 0xa3014314);\n      OP (FI, B, C, D, A, 13, 21, 0x4e0811a1);\n      OP (FI, A, B, C, D, 4, 6, 0xf7537e82);\n      OP (FI, D, A, B, C, 11, 10, 0xbd3af235);\n      OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb);\n      OP (FI, B, C, D, A, 9, 21, 0xeb86d391);\n\n      /* Add the starting values of the context.  */\n      A += A_save;\n      B += B_save;\n      C += C_save;\n      D += D_save;\n    }\n\n  /* Put checksum in context given as argument.  */\n  ctx->A = A;\n  ctx->B = B;\n  ctx->C = C;\n  ctx->D = D;\n}\n"
  },
  {
    "path": "md5.h",
    "content": "/* -*- buffer-read-only: t -*- vi: set ro: */\n/* DO NOT EDIT! GENERATED AUTOMATICALLY! */\n/* Declaration of functions and data types used for MD5 sum computing\n   library functions.\n   Copyright (C) 1995-1997, 1999-2001, 2004-2006, 2008-2010 Free Software\n   Foundation, Inc.\n   This file is part of the GNU C Library.\n\n   This program is free software; you can redistribute it and/or modify it\n   under the terms of the GNU General Public License as published by the\n   Free Software Foundation; either version 3, or (at your option) any\n   later version.\n\n   This program is distributed in the hope that it will be useful,\n   but WITHOUT ANY WARRANTY; without even the implied warranty of\n   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n   GNU General Public License for more details.\n\n   You should have received a copy of the GNU General Public License\n   along with this program; if not, write to the Free Software Foundation,\n   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */\n\n#ifndef _MD5_H\n#define _MD5_H 1\n\n#include <stdio.h>\n#include <stdint.h>\n\n#define MD5_DIGEST_SIZE 16\n#define MD5_BLOCK_SIZE 64\n\n#ifndef __GNUC_PREREQ\n# if defined __GNUC__ && defined __GNUC_MINOR__\n#  define __GNUC_PREREQ(maj, min)                                       \\\n  ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))\n# else\n#  define __GNUC_PREREQ(maj, min) 0\n# endif\n#endif\n\n#ifndef __THROW\n# if defined __cplusplus && __GNUC_PREREQ (2,8)\n#  define __THROW       throw ()\n# else\n#  define __THROW\n# endif\n#endif\n\n#ifndef _LIBC\n# define __md5_buffer md5_buffer\n# define __md5_finish_ctx md5_finish_ctx\n# define __md5_init_ctx md5_init_ctx\n# define __md5_process_block md5_process_block\n# define __md5_process_bytes md5_process_bytes\n# define __md5_read_ctx md5_read_ctx\n# define __md5_stream md5_stream\n#endif\n\n# ifdef __cplusplus\nextern \"C\" {\n# endif\n\n/* Structure to save state of computation between the single steps.  */\nstruct md5_ctx\n{\n  uint32_t A;\n  uint32_t B;\n  uint32_t C;\n  uint32_t D;\n\n  uint32_t total[2];\n  uint32_t buflen;\n  uint32_t buffer[32];\n};\n\n/*\n * The following three functions are build up the low level used in\n * the functions `md5_stream' and `md5_buffer'.\n */\n\n/* Initialize structure containing state of computation.\n   (RFC 1321, 3.3: Step 3)  */\nextern void __md5_init_ctx (struct md5_ctx *ctx) __THROW;\n\n/* Starting with the result of former calls of this function (or the\n   initialization function update the context for the next LEN bytes\n   starting at BUFFER.\n   It is necessary that LEN is a multiple of 64!!! */\nextern void __md5_process_block (const void *buffer, size_t len,\n                                 struct md5_ctx *ctx) __THROW;\n\n/* Starting with the result of former calls of this function (or the\n   initialization function update the context for the next LEN bytes\n   starting at BUFFER.\n   It is NOT required that LEN is a multiple of 64.  */\nextern void __md5_process_bytes (const void *buffer, size_t len,\n                                 struct md5_ctx *ctx) __THROW;\n\n/* Process the remaining bytes in the buffer and put result from CTX\n   in first 16 bytes following RESBUF.  The result is always in little\n   endian byte order, so that a byte-wise output yields to the wanted\n   ASCII representation of the message digest.  */\nextern void *__md5_finish_ctx (struct md5_ctx *ctx, void *resbuf) __THROW;\n\n\n/* Put result from CTX in first 16 bytes following RESBUF.  The result is\n   always in little endian byte order, so that a byte-wise output yields\n   to the wanted ASCII representation of the message digest.  */\nextern void *__md5_read_ctx (const struct md5_ctx *ctx, void *resbuf) __THROW;\n\n\n/* Compute MD5 message digest for bytes read from STREAM.  The\n   resulting message digest number will be written into the 16 bytes\n   beginning at RESBLOCK.  */\nextern int __md5_stream (FILE *stream, void *resblock) __THROW;\n\n/* Compute MD5 message digest for LEN bytes beginning at BUFFER.  The\n   result is always in little endian byte order, so that a byte-wise\n   output yields to the wanted ASCII representation of the message\n   digest.  */\nextern void *__md5_buffer (const char *buffer, size_t len,\n                           void *resblock) __THROW;\n\n# ifdef __cplusplus\n}\n# endif\n\n#endif /* md5.h */\n"
  },
  {
    "path": "menu.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2002 - 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n#include \"config.h\"\n#endif\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n#include <assert.h>\n\n#include \"common.h\"\n#include \"options.h\"\n#include \"menu.h\"\n#include \"files.h\"\n#include \"rbtree.h\"\n#include \"utf8.h\"\n\n/* Draw menu item on a given position from the top of the menu. */\nstatic void draw_item (const struct menu *menu, const struct menu_item *mi,\n\t\tconst int pos, const int item_info_pos, int title_space,\n\t\tconst int number_space, const int draw_selected)\n{\n\tint title_width, queue_pos_len = 0;\n\tint ix, x;\n\tint y ATTR_UNUSED;\t\t/* OpenBSD flags this as unused. */\n\tchar buf[32];\n\n\tassert (menu != NULL);\n\tassert (mi != NULL);\n\tassert (pos >= 0);\n\tassert (item_info_pos > menu->posx\n\t\t\t|| (!menu->show_time && !menu->show_format));\n\tassert (title_space > 0);\n\tassert (number_space == 0 || number_space >= 2);\n\n\twmove (menu->win, pos, menu->posx);\n\n\tif (number_space) {\n\t\tif (draw_selected && mi == menu->selected && mi == menu->marked)\n\t\t\twattrset (menu->win, menu->info_attr_sel_marked);\n\t\telse if (draw_selected && mi == menu->selected)\n\t\t\twattrset (menu->win, menu->info_attr_sel);\n\t\telse if (mi == menu->marked)\n\t\t\twattrset (menu->win, menu->info_attr_marked);\n\t\telse\n\t\t\twattrset (menu->win, menu->info_attr_normal);\n\t\txwprintw (menu->win, \"%*d \", number_space - 1, mi->num + 1);\n\t}\n\n\t/* Set attributes */\n\tif (draw_selected && mi == menu->selected && mi == menu->marked)\n\t\twattrset (menu->win, mi->attr_sel_marked);\n\telse if (draw_selected && mi == menu->selected)\n\t\twattrset (menu->win, mi->attr_sel);\n\telse if (mi == menu->marked)\n\t\twattrset (menu->win, mi->attr_marked);\n\telse\n\t\twattrset (menu->win, mi->attr_normal);\n\n\t/* Compute the length of the queue position if nonzero */\n\tif (mi->queue_pos) {\n\t\tsprintf (buf, \"%d\", mi->queue_pos);\n\t\tqueue_pos_len = strlen(buf) + 2;\n\t\ttitle_space -= queue_pos_len;\n\t}\n\n\ttitle_width = strwidth (mi->title);\n\n\tgetyx (menu->win, y, x);\n\tif (title_width <= title_space || mi->align == MENU_ALIGN_LEFT)\n\t\txwaddnstr (menu->win, mi->title, title_space);\n\telse {\n\t\tchar *ptr;\n\n\t\tptr = xstrtail (mi->title, title_space);\n\t\txwaddstr (menu->win, ptr);\n\t\tfree (ptr);\n\t}\n\n\t/* Fill the remainder of the title field with spaces. */\n\tif (mi == menu->selected) {\n\t\tgetyx (menu->win, y, ix);\n\t\twhile (ix < x + title_space) {\n\t\t\twaddch (menu->win, ' ');\n\t\t\tix += 1;\n\t\t}\n\t}\n\n\t/* Description. */\n\tif (draw_selected && mi == menu->selected && mi == menu->marked)\n\t\twattrset (menu->win, menu->info_attr_sel_marked);\n\telse if (draw_selected && mi == menu->selected)\n\t\twattrset (menu->win, menu->info_attr_sel);\n\telse if (mi == menu->marked)\n\t\twattrset (menu->win, menu->info_attr_marked);\n\telse\n\t\twattrset (menu->win, menu->info_attr_normal);\n\twmove (menu->win, pos, item_info_pos - queue_pos_len);\n\n\t/* Position in queue. */\n\tif (mi->queue_pos) {\n\t\txwaddstr (menu->win, \"[\");\n\t\txwaddstr (menu->win, buf);\n\t\txwaddstr (menu->win, \"]\");\n\t}\n\n\tif (menu->show_time && menu->show_format\n\t\t\t&& (*mi->time || *mi->format))\n\t\txwprintw (menu->win, \"[%5s|%3s]\", mi->time, mi->format);\n\telse if (menu->show_time && mi->time[0])\n\t\txwprintw (menu->win, \"[%5s]\", mi->time);\n\telse if (menu->show_format && mi->format[0])\n\t\txwprintw (menu->win, \"[%3s]\", mi->format);\n}\n\nvoid menu_draw (const struct menu *menu, const int active)\n{\n\tstruct menu_item *mi;\n\tint title_width;\n\tint info_pos;\n\tint number_space = 0;\n\n\tassert (menu != NULL);\n\n\tif (menu->number_items) {\n\t\tint count = menu->nitems / 10;\n\n\t\tnumber_space = 2; /* begin from 1 digit and a space char */\n\t\twhile (count) {\n\t\t\tcount /= 10;\n\t\t\tnumber_space++;\n\t\t}\n\t}\n\telse\n\t\tnumber_space = 0;\n\n\tif (menu->show_time || menu->show_format) {\n\t\ttitle_width = menu->width - 2; /* -2 for brackets */\n\t\tif (menu->show_time)\n\t\t\ttitle_width -= 5; /* 00:00 */\n\t\tif (menu->show_format)\n\t\t\ttitle_width -= 3; /* MP3 */\n\t\tif (menu->show_time && menu->show_format)\n\t\t\ttitle_width--; /* for | */\n\t\tinfo_pos = title_width;\n\t}\n\telse {\n\t\ttitle_width = menu->width;\n\t\tinfo_pos = title_width;\n\t}\n\n\ttitle_width -= number_space;\n\n\tfor (mi = menu->top; mi && mi->num - menu->top->num < menu->height;\n\t\t\tmi = mi->next)\n\t\tdraw_item (menu, mi, mi->num - menu->top->num + menu->posy,\n\t\t\t\tmenu->posx + info_pos, title_width,\n\t\t\t\tnumber_space, active);\n}\n\n/* Move the cursor to the selected file. */\nvoid menu_set_cursor (const struct menu *m)\n{\n\tassert (m != NULL);\n\n\tif (m->selected)\n\t\twmove (m->win, m->selected->num - m->top->num + m->posy, m->posx);\n}\n\nstatic int rb_compare (const void *a, const void *b,\n                       const void *unused ATTR_UNUSED)\n{\n\tstruct menu_item *mia = (struct menu_item *)a;\n\tstruct menu_item *mib = (struct menu_item *)b;\n\n\treturn strcmp (mia->file, mib->file);\n}\n\nstatic int rb_fname_compare (const void *key, const void *data,\n                             const void *unused ATTR_UNUSED)\n{\n\tconst char *fname = (const char *)key;\n\tconst struct menu_item *mi = (const struct menu_item *)data;\n\n\treturn strcmp (fname, mi->file);\n}\n\n/* menu_items must be malloc()ed memory! */\nstruct menu *menu_new (WINDOW *win, const int posx, const int posy,\n\t\tconst int width, const int height)\n{\n\tstruct menu *menu;\n\n\tassert (win != NULL);\n\tassert (posx >= 0);\n\tassert (posy >= 0);\n\tassert (width > 0);\n\tassert (height > 0);\n\n\tmenu = (struct menu *)xmalloc (sizeof(struct menu));\n\n\tmenu->win = win;\n\tmenu->items = NULL;\n\tmenu->nitems = 0;\n\tmenu->top = NULL;\n\tmenu->last = NULL;\n\tmenu->selected = NULL;\n\tmenu->posx = posx;\n\tmenu->posy = posy;\n\tmenu->width = width;\n\tmenu->height = height;\n\tmenu->marked = NULL;\n\tmenu->show_time = 0;\n\tmenu->show_format = false;\n\tmenu->info_attr_normal = A_NORMAL;\n\tmenu->info_attr_sel = A_NORMAL;\n\tmenu->info_attr_marked = A_NORMAL;\n\tmenu->info_attr_sel_marked = A_NORMAL;\n\tmenu->number_items = 0;\n\n\tmenu->search_tree = rb_tree_new (rb_compare, rb_fname_compare, NULL);\n\n\treturn menu;\n}\n\nstruct menu_item *menu_add (struct menu *menu, const char *title,\n\t\tconst enum file_type type, const char *file)\n{\n\tstruct menu_item *mi;\n\n\tassert (menu != NULL);\n\tassert (title != NULL);\n\n\tmi = (struct menu_item *)xmalloc (sizeof(struct menu_item));\n\n\tmi->title = xstrdup (title);\n\tmi->type = type;\n\tmi->file = xstrdup (file);\n\tmi->num = menu->nitems;\n\n\tmi->attr_normal = A_NORMAL;\n\tmi->attr_sel = A_NORMAL;\n\tmi->attr_marked = A_NORMAL;\n\tmi->attr_sel_marked = A_NORMAL;\n\tmi->align = MENU_ALIGN_LEFT;\n\n\tmi->time[0] = 0;\n\tmi->format[0] = 0;\n\tmi->queue_pos = 0;\n\n\tmi->next = NULL;\n\tmi->prev = menu->last;\n\tif (menu->last)\n\t\tmenu->last->next = mi;\n\n\tif (!menu->items)\n\t\tmenu->items = mi;\n\tif (!menu->top)\n\t\tmenu->top = menu->items;\n\tif (!menu->selected)\n\t\tmenu->selected = menu->items;\n\n\tif (file)\n\t\trb_insert (menu->search_tree, (void *)mi);\n\n\tmenu->last = mi;\n\tmenu->nitems++;\n\n\treturn mi;\n}\n\nstatic struct menu_item *menu_add_from_item (struct menu *menu,\n\t\tconst struct menu_item *mi)\n{\n\tstruct menu_item *new;\n\n\tassert (menu != NULL);\n\tassert (mi != NULL);\n\n\tnew = menu_add (menu, mi->title, mi->type, mi->file);\n\n\tnew->attr_normal = mi->attr_normal;\n\tnew->attr_sel = mi->attr_sel;\n\tnew->attr_marked = mi->attr_marked;\n\tnew->attr_sel_marked = mi->attr_sel_marked;\n\n\tstrncpy(new->time, mi->time, FILE_TIME_STR_SZ);\n\tstrncpy(new->format, mi->format, FILE_FORMAT_SZ);\n\n\treturn new;\n}\n\nstatic struct menu_item *get_item_relative (struct menu_item *mi,\n\t\tint to_move)\n{\n\tassert (mi != NULL);\n\n\twhile (to_move) {\n\t\tstruct menu_item *prev = mi;\n\n\t\tif (to_move > 0) {\n\t\t\tmi = mi->next;\n\t\t\tto_move--;\n\t\t}\n\t\telse {\n\t\t\tmi = mi->prev;\n\t\t\tto_move++;\n\t\t}\n\n\t\tif (!mi) {\n\t\t\tmi = prev;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn mi;\n}\n\nvoid menu_update_size (struct menu *menu, const int posx, const int posy,\n\t\tconst int width, const int height)\n{\n\tassert (menu != NULL);\n\tassert (posx >= 0);\n\tassert (posy >= 0);\n\tassert (width > 0);\n\tassert (height > 0);\n\n\tmenu->posx = posx;\n\tmenu->posy = posy;\n\tmenu->width = width;\n\tmenu->height = height;\n\n\tif (menu->selected && menu->top\n\t\t\t&& menu->selected->num >= menu->top->num + menu->height)\n\t\tmenu->selected = get_item_relative (menu->top,\n\t\t\t\tmenu->height - 1);\n}\n\nstatic void menu_item_free (struct menu_item *mi)\n{\n\tassert (mi != NULL);\n\tassert (mi->title != NULL);\n\n\tfree (mi->title);\n\tif (mi->file)\n\t\tfree (mi->file);\n\n\tfree (mi);\n}\n\nvoid menu_free (struct menu *menu)\n{\n\tstruct menu_item *mi;\n\n\tassert (menu != NULL);\n\n\tmi = menu->items;\n\twhile (mi) {\n\t\tstruct menu_item *next = mi->next;\n\n\t\tmenu_item_free (mi);\n\t\tmi = next;\n\t}\n\n\trb_tree_free (menu->search_tree);\n\n\tfree (menu);\n}\n\nvoid menu_driver (struct menu *menu, const enum menu_request req)\n{\n\tassert (menu != NULL);\n\n\tif (menu->nitems == 0)\n\t\treturn;\n\n\tif (req == REQ_DOWN && menu->selected->next) {\n\t\tmenu->selected = menu->selected->next;\n\t\tif (menu->selected->num >= menu->top->num + menu->height) {\n\t\t\tmenu->top = get_item_relative (menu->selected,\n\t\t\t\t\t-menu->height / 2);\n\t\t\tif (menu->top->num > menu->nitems - menu->height)\n\t\t\t\tmenu->top = get_item_relative (menu->last,\n\t\t\t\t\t\t-menu->height + 1);\n\t\t}\n\t}\n\telse if (req == REQ_UP && menu->selected->prev) {\n\t\tmenu->selected = menu->selected->prev;\n\t\tif (menu->top->num > menu->selected->num)\n\t\t\tmenu->top = get_item_relative (menu->selected,\n\t\t\t\t\t-menu->height / 2);\n\t}\n\telse if (req == REQ_PGDOWN && menu->selected->num < menu->nitems - 1) {\n\t\tif (menu->selected->num + menu->height - 1 < menu->nitems - 1) {\n\t\t\tmenu->selected = get_item_relative (menu->selected,\n\t\t\t\t\tmenu->height - 1);\n\t\t\tmenu->top = get_item_relative (menu->top,\n\t\t\t\t\tmenu->height - 1);\n\t\t\tif (menu->top->num > menu->nitems - menu->height)\n\t\t\t\tmenu->top = get_item_relative (menu->last,\n\t\t\t\t\t\t-menu->height + 1);\n\t\t}\n\t\telse {\n\t\t\tmenu->selected = menu->last;\n\t\t\tmenu->top = get_item_relative (menu->last,\n\t\t\t\t\t-menu->height + 1);\n\t\t}\n\t}\n\telse if (req == REQ_PGUP && menu->selected->prev) {\n\t\tif (menu->selected->num - menu->height + 1 > 0) {\n\t\t\tmenu->selected = get_item_relative (menu->selected,\n\t\t\t\t\t-menu->height + 1);\n\t\t\tmenu->top = get_item_relative (menu->top,\n\t\t\t\t\t-menu->height + 1);\n\t\t}\n\t\telse {\n\t\t\tmenu->selected = menu->items;\n\t\t\tmenu->top = menu->items;\n\t\t}\n\t}\n\telse if (req == REQ_TOP) {\n\t\tmenu->selected = menu->items;\n\t\tmenu->top = menu->items;\n\t}\n\telse if (req == REQ_BOTTOM) {\n\t\tmenu->selected = menu->last;\n\t\tmenu->top = get_item_relative (menu->selected,\n\t\t\t\t-menu->height + 1);\n\t}\n}\n\n/* Return the index of the currently selected item. */\nstruct menu_item *menu_curritem (struct menu *menu)\n{\n\tassert (menu != NULL);\n\n\treturn menu->selected;\n}\n\nstatic void make_item_visible (struct menu *menu, struct menu_item *mi)\n{\n\tassert (menu != NULL);\n\tassert (mi != NULL);\n\n\tif (mi->num < menu->top->num || mi->num >= menu->top->num + menu->height) {\n\t\tmenu->top = get_item_relative(mi, -menu->height/2);\n\n\t\tif (menu->top->num > menu->nitems - menu->height)\n\t\t\tmenu->top = get_item_relative (menu->last,\n\t\t\t\t\t-menu->height + 1);\n\t}\n\n\tif (menu->selected) {\n\t\tif (menu->selected->num < menu->top->num ||\n\t\t\t\tmenu->selected->num >= menu->top->num + menu->height)\n\t\t\tmenu->selected = mi;\n\t}\n}\n\n/* Make this item selected */\nstatic void menu_setcurritem (struct menu *menu, struct menu_item *mi)\n{\n\tassert (menu != NULL);\n\tassert (mi != NULL);\n\n\tmenu->selected = mi;\n\tmake_item_visible (menu, mi);\n}\n\n/* Make the item with this title selected. */\nvoid menu_setcurritem_title (struct menu *menu, const char *title)\n{\n\tstruct menu_item *mi;\n\n\t/* Find it */\n\tfor (mi = menu->top; mi; mi = mi->next)\n\t\tif (!strcmp(mi->title, title))\n\t\t\tbreak;\n\n\tif (mi)\n\t\tmenu_setcurritem (menu, mi);\n}\n\nstatic struct menu_item *menu_find_by_position (struct menu *menu,\n\t\tconst int num)\n{\n\tstruct menu_item *mi;\n\n\tassert (menu != NULL);\n\n\tmi = menu->top;\n\twhile (mi && mi->num != num)\n\t\tmi = mi->next;\n\n\treturn mi;\n}\n\nvoid menu_set_state (struct menu *menu, const struct menu_state *st)\n{\n\tassert (menu != NULL);\n\n\tif (!(menu->selected = menu_find_by_position(menu, st->selected_item)))\n\t\tmenu->selected = menu->last;\n\n\tif (!(menu->top = menu_find_by_position(menu, st->top_item)))\n\t\tmenu->top = get_item_relative (menu->last, menu->height + 1);\n}\n\nvoid menu_set_items_numbering (struct menu *menu, const int number)\n{\n\tassert (menu != NULL);\n\n\tmenu->number_items = number;\n}\n\nvoid menu_get_state (const struct menu *menu, struct menu_state *st)\n{\n\tassert (menu != NULL);\n\n\tst->top_item = menu->top ? menu->top->num : -1;\n\tst->selected_item = menu->selected ? menu->selected->num : -1;\n}\n\nvoid menu_unmark_item (struct menu *menu)\n{\n\tassert (menu != NULL);\n\tmenu->marked = NULL;\n}\n\n/* Make a new menu from elements matching pattern. */\nstruct menu *menu_filter_pattern (const struct menu *menu, const char *pattern)\n{\n\tstruct menu *new;\n\tconst struct menu_item *mi;\n\n\tassert (menu != NULL);\n\tassert (pattern != NULL);\n\n\tnew = menu_new (menu->win, menu->posx, menu->posy, menu->width,\n\t\t\tmenu->height);\n\tmenu_set_show_time (new, menu->show_time);\n\tmenu_set_show_format (new, menu->show_format);\n\tmenu_set_info_attr_normal (new, menu->info_attr_normal);\n\tmenu_set_info_attr_sel (new, menu->info_attr_sel);\n\tmenu_set_info_attr_marked (new, menu->info_attr_marked);\n\tmenu_set_info_attr_sel_marked (new, menu->info_attr_sel_marked);\n\n\tfor (mi = menu->items; mi; mi = mi->next)\n\t\tif (strcasestr(mi->title, pattern))\n\t\t\tmenu_add_from_item (new, mi);\n\n\tif (menu->marked)\n\t\tmenu_mark_item (new, menu->marked->file);\n\n\treturn new;\n}\n\nvoid menu_item_set_attr_normal (struct menu_item *mi, const int attr)\n{\n\tassert (mi != NULL);\n\n\tmi->attr_normal = attr;\n}\n\nvoid menu_item_set_attr_sel (struct menu_item *mi, const int attr)\n{\n\tassert (mi != NULL);\n\n\tmi->attr_sel = attr;\n}\n\nvoid menu_item_set_attr_sel_marked (struct menu_item *mi, const int attr)\n{\n\tassert (mi != NULL);\n\n\tmi->attr_sel_marked = attr;\n}\n\nvoid menu_item_set_attr_marked (struct menu_item *mi, const int attr)\n{\n\tassert (mi != NULL);\n\n\tmi->attr_marked = attr;\n}\n\nvoid menu_item_set_time (struct menu_item *mi, const char *time)\n{\n\tassert (mi != NULL);\n\n\tmi->time[sizeof(mi->time)-1] = 0;\n\tstrncpy (mi->time, time, sizeof(mi->time));\n\tassert (mi->time[sizeof(mi->time)-1] == 0);\n}\n\nvoid menu_item_set_format (struct menu_item *mi, const char *format)\n{\n\tassert (mi != NULL);\n\tassert (format != NULL);\n\n\tmi->format[sizeof(mi->format)-1] = 0;\n\tstrncpy (mi->format, format,\n\t\t\tsizeof(mi->format));\n\tassert (mi->format[sizeof(mi->format)-1]\n\t\t\t== 0);\n}\n\nvoid menu_item_set_queue_pos (struct menu_item *mi, const int pos)\n{\n\tassert (mi != NULL);\n\n\tmi->queue_pos = pos;\n}\n\nvoid menu_set_show_time (struct menu *menu, const int t)\n{\n\tassert (menu != NULL);\n\n\tmenu->show_time = t;\n}\n\nvoid menu_set_show_format (struct menu *menu, const bool t)\n{\n\tassert (menu != NULL);\n\n\tmenu->show_format = t;\n}\n\nvoid menu_set_info_attr_normal (struct menu *menu, const int attr)\n{\n\tassert (menu != NULL);\n\n\tmenu->info_attr_normal = attr;\n}\n\nvoid menu_set_info_attr_sel (struct menu *menu, const int attr)\n{\n\tassert (menu != NULL);\n\n\tmenu->info_attr_sel = attr;\n}\n\nvoid menu_set_info_attr_marked (struct menu *menu, const int attr)\n{\n\tassert (menu != NULL);\n\n\tmenu->info_attr_marked = attr;\n}\n\nvoid menu_set_info_attr_sel_marked (struct menu *menu, const int attr)\n{\n\tassert (menu != NULL);\n\n\tmenu->info_attr_sel_marked = attr;\n}\n\nenum file_type menu_item_get_type (const struct menu_item *mi)\n{\n\tassert (mi != NULL);\n\n\treturn mi->type;\n}\n\nchar *menu_item_get_file (const struct menu_item *mi)\n{\n\tassert (mi != NULL);\n\n\treturn xstrdup (mi->file);\n}\n\nvoid menu_item_set_title (struct menu_item *mi, const char *title)\n{\n\tassert (mi != NULL);\n\n\tif (mi->title)\n\t\tfree (mi->title);\n\tmi->title = xstrdup (title);\n}\n\nint menu_nitems (const struct menu *menu)\n{\n\tassert (menu != NULL);\n\n\treturn menu->nitems;\n}\n\nstruct menu_item *menu_find (struct menu *menu, const char *fname)\n{\n\tstruct rb_node *x;\n\n\tassert (menu != NULL);\n\tassert (fname != NULL);\n\n\tx = rb_search (menu->search_tree, fname);\n\tif (rb_is_null(x))\n\t\treturn NULL;\n\n\treturn (struct menu_item *)rb_get_data (x);\n}\n\nvoid menu_mark_item (struct menu *menu, const char *file)\n{\n\tstruct menu_item *item;\n\n\tassert (menu != NULL);\n\tassert (file != NULL);\n\n\titem = menu_find (menu, file);\n\tif (item)\n\t\tmenu->marked = item;\n}\n\nstatic void menu_renumber_items (struct menu *menu)\n{\n\tint i = 0;\n\tstruct menu_item *mi;\n\n\tassert (menu != NULL);\n\n\tfor (mi = menu->items; mi; mi = mi->next)\n\t\tmi->num = i++;\n\n\tassert (i == menu->nitems);\n}\n\nstatic void menu_delete (struct menu *menu, struct menu_item *mi)\n{\n\tassert (menu != NULL);\n\tassert (mi != NULL);\n\n\tif (mi->prev)\n\t\tmi->prev->next = mi->next;\n\tif (mi->next)\n\t\tmi->next->prev = mi->prev;\n\n\tif (menu->items == mi)\n\t\tmenu->items = mi->next;\n\tif (menu->last == mi)\n\t\tmenu->last = mi->prev;\n\n\tif (menu->marked == mi)\n\t\tmenu->marked = NULL;\n\tif (menu->selected == mi)\n\t\tmenu->selected = mi->next ? mi->next : mi->prev;\n\tif (menu->top == mi)\n\t\tmenu->top = mi->next ? mi->next : mi->prev;\n\n\tif (mi->file)\n\t\trb_delete (menu->search_tree, mi->file);\n\n\tmenu->nitems--;\n\tmenu_renumber_items (menu);\n\n\tmenu_item_free (mi);\n}\n\nvoid menu_del_item (struct menu *menu, const char *fname)\n{\n\tstruct menu_item *mi;\n\n\tassert (menu != NULL);\n\tassert (fname != NULL);\n\n\tmi = menu_find (menu, fname);\n\tassert (mi != NULL);\n\n\tmenu_delete (menu, mi);\n}\n\nvoid menu_item_set_align (struct menu_item *mi, const enum menu_align align)\n{\n\tassert (mi != NULL);\n\n\tmi->align = align;\n}\n\nvoid menu_setcurritem_file (struct menu *menu, const char *file)\n{\n\tstruct menu_item *mi;\n\n\tassert (menu != NULL);\n\tassert (file != NULL);\n\n\tmi = menu_find (menu, file);\n\tif (mi)\n\t\tmenu_setcurritem (menu, mi);\n}\n/* Return non-zero value if the item in in the visible part of the menu. */\nint menu_is_visible (const struct menu *menu, const struct menu_item *mi)\n{\n\tassert (menu != NULL);\n\tassert (mi != NULL);\n\n\tif (mi->num >= menu->top->num\n\t\t\t&& mi->num < menu->top->num + menu->height)\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic void menu_items_swap (struct menu *menu, struct menu_item *mi1,\n\t\tstruct menu_item *mi2)\n{\n\tint t;\n\n\tassert (menu != NULL);\n\tassert (mi1 != NULL);\n\tassert (mi2 != NULL);\n\tassert (mi1 != mi2);\n\n\t/* if they are next to each other, change the pointers so that mi2\n\t * is the second one */\n\tif (mi2->next == mi1) {\n\t\tstruct menu_item *i = mi1;\n\n\t\tmi1 = mi2;\n\t\tmi2 = i;\n\t}\n\n\tif (mi1->next == mi2) {\n\t\tif (mi2->next)\n\t\t\tmi2->next->prev = mi1;\n\t\tif (mi1->prev)\n\t\t\tmi1->prev->next = mi2;\n\n\t\tmi1->next = mi2->next;\n\t\tmi2->prev = mi1->prev;\n\t\tmi1->prev = mi2;\n\t\tmi2->next = mi1;\n\t}\n\telse {\n\t\tif (mi2->next)\n\t\t\tmi2->next->prev = mi1;\n\t\tif (mi2->prev)\n\t\t\tmi2->prev->next = mi1;\n\t\tmi2->next = mi1->next;\n\t\tmi2->prev = mi1->prev;\n\n\t\tif (mi1->next)\n\t\t\tmi1->next->prev = mi2;\n\t\tif (mi1->prev)\n\t\t\tmi1->prev->next = mi2;\n\t\tmi1->next = mi2->next;\n\t\tmi1->prev = mi2->prev;\n\t}\n\n\tt = mi1->num;\n\tmi1->num = mi2->num;\n\tmi2->num = t;\n\n\tif (menu->top == mi1)\n\t\tmenu->top = mi2;\n\telse if (menu->top == mi2)\n\t\tmenu->top = mi1;\n\n\tif (menu->last == mi1)\n\t\tmenu->last = mi2;\n\telse if (menu->last == mi2)\n\t\tmenu->last = mi1;\n\n\tif (menu->items == mi1)\n\t\tmenu->items = mi2;\n\telse if (menu->items == mi2)\n\t\tmenu->items = mi1;\n}\n\nvoid menu_swap_items (struct menu *menu, const char *file1, const char *file2)\n{\n\tstruct menu_item *mi1, *mi2;\n\n\tassert (menu != NULL);\n\tassert (file1 != NULL);\n\tassert (file2 != NULL);\n\n\tif ((mi1 = menu_find(menu, file1)) && (mi2 = menu_find(menu, file2))\n\t\t\t&& mi1 != mi2) {\n\t\tmenu_items_swap (menu, mi1, mi2);\n\n\t\t/* make sure that the selected item is visible */\n\t\tmenu_setcurritem (menu, menu->selected);\n\t}\n}\n\n/* Make sure that this file is visible in the menu. */\nvoid menu_make_visible (struct menu *menu, const char *file)\n{\n\tstruct menu_item *mi;\n\n\tassert (menu != NULL);\n\tassert (file != NULL);\n\n\tif ((mi = menu_find(menu, file)))\n\t\tmake_item_visible (menu, mi);\n}\n"
  },
  {
    "path": "menu.h",
    "content": "#ifndef MENU_H\n#define MENU_H\n\n#if defined HAVE_NCURSESW_CURSES_H\n# include <ncursesw/curses.h>\n#elif defined HAVE_NCURSESW_H\n# include <ncursesw.h>\n#elif defined HAVE_NCURSES_CURSES_H\n# include <ncurses/curses.h>\n#elif defined HAVE_NCURSES_H\n# include <ncurses.h>\n#elif defined HAVE_CURSES_H\n# include <curses.h>\n#endif\n\n#include \"files.h\"\n#include \"rbtree.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nenum menu_request\n{\n\tREQ_UP,\n\tREQ_DOWN,\n\tREQ_PGUP,\n\tREQ_PGDOWN,\n\tREQ_TOP,\n\tREQ_BOTTOM\n};\n\nenum menu_align\n{\n\tMENU_ALIGN_RIGHT,\n\tMENU_ALIGN_LEFT\n};\n\n#define FILE_TIME_STR_SZ        6\n#define FILE_FORMAT_SZ          4\n\nstruct menu_item\n{\n\tchar *title;\t\t/* Title of the item */\n\tenum menu_align align;\t/* Align of the title */\n\tint num;\t\t/* Position of the item starting from 0. */\n\n\t/* Curses attributes in different states: */\n\tint attr_normal;\n\tint attr_sel;\n\tint attr_marked;\n\tint attr_sel_marked;\n\n\t/* Associated file: */\n\tchar *file;\n\tenum file_type type;\n\n\t/* Additional information shown: */\n\tchar time[FILE_TIME_STR_SZ];\t\t/* File time string */\n\tchar format[FILE_FORMAT_SZ];\t\t/* File format */\n\tint queue_pos;\t\t\t\t/* Position in the queue */\n\n\tstruct menu_item *next;\n\tstruct menu_item *prev;\n};\n\nstruct menu\n{\n\tWINDOW *win;\n\tstruct menu_item *items;\n\tint nitems;\t\t/* number of present items */\n\tstruct menu_item *top;\t/* first visible item */\n\tstruct menu_item *last;\t/* last item in the menu */\n\n\t/* position and size */\n\tint posx;\n\tint posy;\n\tint width;\n\tint height;\n\n\tstruct menu_item *selected;\t/* selected item */\n\tstruct menu_item *marked;\t/* index of the marked item or -1 */\n\n\t/* Flags for displaying information about the file. */\n\tint show_time;\n\tbool show_format;\n\n\tint info_attr_normal;\t/* attributes for information about the file */\n\tint info_attr_sel;\n\tint info_attr_marked;\n\tint info_attr_sel_marked;\n\tint number_items; /* display item number (position) */\n\n\tstruct rb_tree *search_tree; /* RB tree for searching by file name */\n};\n\n/* Menu state: relative (to the first item) positions of the top and selected\n * items. */\nstruct menu_state\n{\n\tint top_item;\n\tint selected_item;\n};\n\nstruct menu *menu_new (WINDOW *win, const int posx, const int posy,\n\t\tconst int width, const int height);\nstruct menu_item *menu_add (struct menu *menu, const char *title,\n\t\tconst enum file_type type, const char *file);\n\nvoid menu_item_set_attr_normal (struct menu_item *mi, const int attr);\nvoid menu_item_set_attr_sel (struct menu_item *mi, const int attr);\nvoid menu_item_set_attr_sel_marked (struct menu_item *mi, const int attr);\nvoid menu_item_set_attr_marked (struct menu_item *mi, const int attr);\n\nvoid menu_item_set_time (struct menu_item *mi, const char *time);\nvoid menu_item_set_format (struct menu_item *mi, const char *format);\nvoid menu_item_set_queue_pos (struct menu_item *mi, const int pos);\n\nvoid menu_free (struct menu *menu);\nvoid menu_driver (struct menu *menu, const enum menu_request req);\nvoid menu_setcurritem_title (struct menu *menu, const char *title);\nvoid menu_setcurritem_file (struct menu *menu, const char *file);\nvoid menu_draw (const struct menu *menu, const int active);\nvoid menu_mark_item (struct menu *menu, const char *file);\nvoid menu_set_state (struct menu *menu, const struct menu_state *st);\nvoid menu_get_state (const struct menu *menu, struct menu_state *st);\nvoid menu_update_size (struct menu *menu, const int posx, const int posy,\n\t\tconst int width, const int height);\nvoid menu_unmark_item (struct menu *menu);\nstruct menu *menu_filter_pattern (const struct menu *menu, const char *pattern);\nvoid menu_set_show_time (struct menu *menu, const int t);\nvoid menu_set_show_format (struct menu *menu, const bool t);\nvoid menu_set_info_attr_normal (struct menu *menu, const int attr);\nvoid menu_set_info_attr_sel (struct menu *menu, const int attr);\nvoid menu_set_info_attr_marked (struct menu *menu, const int attr);\nvoid menu_set_info_attr_sel_marked (struct menu *menu, const int attr);\nvoid menu_set_items_numbering (struct menu *menu, const int number);\nenum file_type menu_item_get_type (const struct menu_item *mi);\nchar *menu_item_get_file (const struct menu_item *mi);\nstruct menu_item *menu_curritem (struct menu *menu);\nvoid menu_item_set_title (struct menu_item *mi, const char *title);\nint menu_nitems (const struct menu *menu);\nstruct menu_item *menu_find (struct menu *menu, const char *fname);\nvoid menu_del_item (struct menu *menu, const char *fname);\nvoid menu_item_set_align (struct menu_item *mi, const enum menu_align align);\nint menu_is_visible (const struct menu *menu, const struct menu_item *mi);\nvoid menu_swap_items (struct menu *menu, const char *file1, const char *file2);\nvoid menu_make_visible (struct menu *menu, const char *file);\nvoid menu_set_cursor (const struct menu *m);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "mocp.1",
    "content": ".\\\" Start example.\n.de EX\n.  nr mE \\\\n(.f\n.  nf\n.  nh\n.  ft CW\n..\n.\n.\\\" End example.\n.de EE\n.  ft \\\\n(mE\n.  fi\n.  hy \\\\n(HY\n..\n.\n.TH MOC 1 \"16 November 2016\" \"Version 2.6-alpha3\" \"Music On Console\"\n.ad l\n.SH NAME\nMOC \\- Console audio player\n.LP\n.SH SYNOPSIS\n\\fBmocp\\fR [\\fIOPTIONS\\fR] [\\fIFILE\\fR|\\fIDIR\\fR ...]\n.LP\n.SH DESCRIPTION\nMOC is a console audio player with simple ncurses interface.  It supports\nOGG, WAV, MP3 and other formats.  Just run \\fBmocp\\fP, go to some directory\nusing the menu and press enter to start playing the file.  The program will\nautomatically play the rest of the files in the directory.\n.LP\nWith no options and no file arguments the program begins in current\ndirectory, or in \\fBMusicDir\\fP if the \\fBStartInMusicDir\\fP option is set\nin the configuration file.  If you give a directory on the command line,\nMOC will try to go there.  If a playlist is given, then it is loaded.\nWith multiple files, playlists or directories, everything will be added to\nthe playlist recursively (including the contents of any playlist given).\n(Note that relative paths in playlists are resolved with respect to the\ndirectory of the playlist, or of the symlink being used to reference it.)\n.LP\n.SH OPTIONS\nIf an option can also be set in the configuration file the command line\noverrides it (but see the \\fB\\-O\\fP option for the list-valued\nconfiguration file options exception).\n.LP\n.TP\n\\fB\\-D\\fP, \\fB\\-\\-debug\\fP\nRun MOC in debug mode.  The client and server log a lot of information\nto debug files.  Don't use this; the server log is large.  This is only\navailable if MOC was compiled without \\fB\\-\\-disable\\-debug\\fP.\n.LP\n.TP\n\\fB\\-S\\fP, \\fB\\-\\-server\\fP\nRun only the server and exit.\n.LP\n.TP\n\\fB\\-F\\fP, \\fB\\-\\-foreground\\fP\nImplies \\fB\\-S\\fP.  Run the server in foreground and log everything to stdout.\n.LP\n.TP\n\\fB\\-R\\fP \\fINAME\\fP[\\fB:\\fP...], \\\n\\fB\\-\\-sound\\-driver\\fP \\fINAME\\fP[\\fB:\\fP...]\nUse the specified sound driver(s).  They can be \\fBOSS\\fP, \\fBALSA\\fP,\n\\fBJACK\\fP, \\fBSNDIO\\fP or \\fBnull\\fP (for debugging).  Some of the drivers\nmay not have been compiled in.  This option is called \\fBSoundDriver\\fP in\nthe configuration file.\n.LP\n.TP\n\\fB\\-m\\fP, \\fB\\-\\-music\\-dir\\fP\nStart in \\fBMusicDir\\fP (set in the configuration file).  This can be also\nset in the configuration file as \\fBStartInMusicDir\\fP.\n.LP\n.TP\n\\fB\\-q\\fP, \\fB\\-\\-enqueue\\fP\nAdd files given after command line options to the queue.  Don't start the\ninterface.\n.LP\n.TP\n\\fB\\-a\\fP, \\fB\\-\\-append\\fP\nAppend files, directories (recursively) and playlists given after command\nline options to the playlist.  Don't start the interface.\n.LP\n.TP\n\\fB\\-c\\fP, \\fB\\-\\-clear\\fP\nClear the playlist.\n.LP\n.TP\n\\fB\\-p\\fP, \\fB\\-\\-play\\fP\nStart playing from the first item on the playlist.\n.LP\n.TP\n\\fB\\-l\\fP, \\fB\\-\\-playit\\fP\nPlay files given on the command line without modifying the clients'\nplaylists.\n.LP\n.TP\n\\fB\\-f\\fP, \\fB\\-\\-next\\fP\nRequest playing the next song from the server's playlist.\n.LP\n.TP\n\\fB\\-r\\fP, \\fB\\-\\-previous\\fP\nRequest playing the previous song from the server's playlist.\n.LP\n.TP\n\\fB\\-s\\fP, \\fB\\-\\-stop\\fP\nRequest the server to stop playing.\n.LP\n.TP\n\\fB\\-x\\fP, \\fB\\-\\-exit\\fP\nBring down the server.\n.LP\n.TP\n\\fB\\-P\\fP, \\fB\\-\\-pause\\fP\nRequest the server to pause playing.\n.LP\n.TP\n\\fB\\-U\\fP, \\fB\\-\\-unpause\\fP\nRequest the server to resume playing when paused.\n.LP\n.TP\n\\fB\\-G\\fP, \\fB\\-\\-toggle\\-pause\\fP\nToggle between play and pause.\n.LP\n.TP\n\\fB\\-k\\fP [\\fB+\\fP|\\fB\\-\\fP]\\fIN\\fP, \\\n\\fB\\-\\-seek \\fP[\\fB+\\fP|\\fB\\-\\fP]\\fIN\\fP\nSeek forward (positive) or backward (negative) by \\fIN\\fP seconds in the\nfile currently being played.\n.LP\n.TP\n\\fB\\-T\\fP \\fITHEME\\fP, \\fB\\-\\-theme\\fP \\fITHEME\\fP\nUse a theme file.  If the path is not absolute, the file will be searched\nfor in \\fB/usr/share/moc/themes/\\fP (depends on installation prefix),\n\\fB~/.moc/themes/\\fP and the current directory.\n.LP\n.TP\n\\fB\\-C\\fP \\fIFILE\\fP, \\fB\\-\\-config\\fP \\fIFILE\\fP\nUse the specified configuration file (which must be readable) instead of the\ndefault.  As this file can specify commands which invoke other applications\nMOC will refuse to start if it is not owned by either root or the current\nuser, or if it is writable by anyone other than its owner.\n.LP\n.TP\n\\fB\\-\\-no\\-config\\fP\nDo not read any configuration file but use the built-in defaults.\n.LP\n.TP\n\\fB\\-O\\fP \\fINAME\\fP[\\fB+\\fP]\\fB=\\fP\\fIVALUE\\fP, \\\n\\fB\\-\\-set\\-option\\fP \\fINAME\\fP[\\fB+\\fP]\\fB=\\fP\\fIVALUE\\fP\nOverride configuration file option NAME with VALUE.  This option can be\nrepeated as many times as needed and the option name is not case sensitive.\nMost option values are set before the configuration file is processed (which\nallows the new values to be picked up by substitutions); however, list-valued\noptions are overridden afterwards (which gives the choice of whether the\nconfigured values are replaced or added to).\n.IP\nSee the example configuration file (\\fBconfig.example\\fP) for a description\nof the options available.\n.LP\n.RS\n.EX\nExamples: \\fB\\-O AutoNext=no\\fP\n          \\fB\\-O messagelingertime=1 \\-O XTerms+=xxt:xwt\\fP\n.EE\n.RE\n.IP\nNote that MOC does not perform variable substitution as it does for values\nread from the configuration file.\n.LP\n.TP\n\\fB\\-M\\fP \\fIDIR\\fP, \\fB\\-\\-moc\\-dir\\fP \\fIDIR\\fP\nUse the specified MOC directory instead of the default.  This also causes\nthe configuration file from that directory to be used.  This can also be\nspecified in the configuration file using the \\fBMOCDir\\fP option.\n.LP\n.TP\n\\fB\\-y\\fP, \\fB\\-\\-sync\\fP\nThis copy of the interface will synchronize its playlist with other clients.\nThis option is called \\fBSyncPlaylist\\fP in the configuration file.\n.LP\n.TP\n\\fB\\-n\\fP, \\fB\\-\\-nosync\\fP\nThis copy of the interface will not synchronize its playlist with other\nclients (see above).\n.LP\n.TP\n\\fB\\-A\\fP, \\fB\\-\\-ascii\\fP\nUse ASCII characters to draw lines.  (This helps on some terminals.)\n.LP\n.TP\n\\fB\\-i\\fP, \\fB\\-\\-info\\fP\nPrint the information about the file currently being played.\n.LP\n.TP\n\\fB\\-Q\\fP \\fIFORMAT_STRING\\fP, \\fB\\-\\-format\\fP \\fIFORMAT_STRING\\fP\nPrint information about the file currently being played using a format\nstring.  Replace string sequences with the actual information:\n.IP\n.RS 16\n.EX\n\\fB%state\\fP     State\n\\fB%file\\fP      File\n\\fB%title\\fP     Title\n\\fB%artist\\fP    Artist\n\\fB%song\\fP      SongTitle\n\\fB%album\\fP     Album\n\\fB%tt\\fP        TotalTime\n\\fB%tl\\fP        TimeLeft\n\\fB%ts\\fP        TotalSec\n\\fB%ct\\fP        CurrentTime\n\\fB%cs\\fP        CurrentSec\n\\fB%b\\fP         Bitrate\n\\fB%r\\fP         Rate\n.EE\n.RE\n.IP\nIt is also possible to use variables from the \\fBFormatString\\fP\nconfiguration file option.\n.LP\n.TP\n\\fB\\-e\\fP, \\fB\\-\\-recursively\\fP\nAlias of \\fB\\-a\\fP for backward compatibility.\n.LP\n.TP\n\\fB\\-h\\fP, \\fB\\-\\-help\\fP\nPrint a list of options with short descriptions and exit.\n.LP\n.TP\n\\fB\\-\\-usage\\fP\nPrint a synopsis of the mocp command and exit.\n.LP\n.TP\n\\fB\\-V\\fP, \\fB\\-\\-version\\fP\nPrint the program version and exit.\n.LP\n.TP\n\\fB\\-\\-echo-args\\fP\nPrint the POPT-interpreted command line arguments and exit.\n.LP\n.TP\n\\fB\\-v\\fP [\\fB+\\fP|\\fB\\-\\fP]\\fIN\\fP, \\\n\\fB\\-\\-volume\\fP [\\fB+\\fP|\\fB\\-\\fP]\\fIN\\fP\nAdjust the mixer volume.  You can set (\\fB\\-v 50\\fP) or adjust\n(\\fB\\-v +10\\fP, \\fB\\-v \\-10\\fP).\n.LP\n.TP\n\\fB\\-t\\fP \\fIOPTION\\fP[\\fB,\\fP...], \\fB\\-\\-toggle\\fP \\fIOPTION\\fP[\\fB,\\fP...]\n.TQ\n\\fB\\-o\\fP \\fIOPTION\\fP[\\fB,\\fP...], \\fB\\-\\-on\\fP \\fIOPTION\\fP[\\fB,\\fP...]\n.TQ\n\\fB\\-u\\fP \\fIOPTION\\fP[\\fB,\\fP...], \\fB\\-\\-off\\fP \\fIOPTION\\fP[\\fB,\\fP...]\nFollowed by a list of identifiers, these will control MOC's playlist\noptions.  Valid identifiers are \\fBshuffle\\fP, \\fBrepeat\\fP and \\fBautonext\\fP.\nThey can be shortened to '\\fBs\\fP', '\\fBr\\fP' and '\\fBn\\fP' respectively.\nBoth the identifiers and short forms are case insensitive.\n.LP\n.RS\n.EX\nExample: \\fB\\-t shuffle,R,n\\fP\n.EE\n         would toggle shuffle, repeat and autonext all at once.\n.RE\n.LP\n.TP\n\\fB\\-j\\fP \\fIN\\fP{\\fBs\\fP|\\fB%\\fP}, \\fB\\-\\-jump\\fP \\fIN\\fP{\\fBs\\fP|\\fB%\\fP}\nJump to some position in the current file.  \\fIN\\fP is the number of seconds\n(when followed by an '\\fBs\\fP') or the percent of total file time (when\nfollowed by a '\\fB%\\fP').\n.LP\n.RS\n.EX\nExamples: \\fB\\-j 10s\\fP, \\fB\\-j 50%\\fP\n.EE\n.RE\n.LP\n.SH USING POPT ALIASES\nMOC uses the POPT library to process its command line.  This allows\nusers to assign MOC options and arguments to an alias of their choosing.\nThe aliases are just lines in the \\fB~/.popt\\fP text file and have the\ngeneral form:\n.IP\n.RS\n.EX\n.BI mocp \" \" alias \" \" \\fInewoption\\fP \" \" \\fIexpansion\\fP\n.EE\n.RE\n.LP\nThis works as if \\fIexpansion\\fP textually replaces \\fInewoption\\fP\non the command line.  The replacement is recursive; that is,\nother \\fInewoption\\fPs can be embedded in the \\fIexpansion\\fP.\nThe \\fIexpansion\\fP is parsed similarly to a shell command, which\nallows \\\\, \", and ' to be used for quoting.  If a backslash is the\nfinal character on a line, the next line in the file is assumed to be a\nlogical continuation of the line containing the backslash, just as in\nthe shell.  The \\fInewoption\\fP can be either a short or long option,\nand any syntactically valid name the user wishes to use.\n.LP\nIf you add a description for the new option and/or for any argument by\nappending the special POPT options \\fB\\-\\-POPTdesc\\fP and \\fB\\-\\-POPTargs\\fP,\nthen the option will be displayed in the output of \\fB\\-\\-help\\fP and\n\\fB\\-\\-usage\\fP.  The value for these two options are strings of the form\n\\fB$\"\\fP\\fIstring\\fP\\fB\"\\fP.\n.LP\nSo, for example:\n.IP\n.RS\n.EX\n\\fBmocp alias \\-\\-single \\-D \\-\\-set\\-option autonext=no \\\\\\fP\n\\fB           \\-\\-POPTdesc=$\"Play just the file selected\"\n.EE\n.RE\n.LP\nwould allow the user to turn on logging (\\fB\\-D\\fP) and override\nthe configuration file's \\fBAutoNext\\fP option setting just by using\n\\fB\\-\\-single\\fP as an option to the mocp command.\n.LP\nSometimes you may wish to provide values to aliased options from the\ncommand line.  If just one aliased option has such a value, then it's\na simple matter of placing it last:\n.LP\n.IP\n.RS\n.EX\n\\fBmocp alias \\-\\-yours \\-\\-sound-driver OSS \\-\\-theme\n.EE\n.RE\n.LP\nwhen used like this:\n.LP\n.IP\n.RS\n.EX\n\\fBmocp \\-\\-yours your_theme\n.EE\n.RE\n.LP\nwould result in:\n.LP\n.IP\n.RS\n.EX\n\\fBmocp \\-\\-sound-driver OSS \\-\\-theme your_theme\n.EE\n.RE\n.LP\nBut aliasing multiple options with such values means making use of the\nspecial construct \\fB!#:+\\fP (and quoting carefully):\n.LP\n.IP\n.RS\n.EX\n\\fBmocp alias \\-1 \"\\-R !#:+\" \"\\-T my_theme\" \"\\-O !#:+\"\n.EE\n.RE\n.LP\nwhen used like this:\n.LP\n.IP\n.RS\n.EX\n\\fBmocp \\-1 OSS shuffle=yes ~/my_music\n.EE\n.RE\n.LP\nwould result in:\n.LP\n.IP\n.RS\n.EX\n\\fBmocp \\-R OSS \\-T my_theme \\-O shuffle=yes ~/my_music\n.EE\n.RE\n.LP\nThere is also a \\fB~/.popt\\fP entry which allows for the execution of\na different program when the associated option is used.  For this, an\n\\fBexec\\fP is used in place of the \\fBalias\\fP and the \\fIexpansion\\fP\nis the program to be executed:\n.LP\n.IP\n.RS\n.EX\n\\fBmocp exec \\-\\-help /usr/bin/man 1 mocp \\\\\n\\fB           POPTdesc=$\"Provide the man page instead of help\"\n.EE\n.RE\n.LP\nThis would override the usual MOC \\fB\\-\\-help\\fP output and use the\nsystem's \\fBman\\fP program to present this man page instead.\n.LP\nNote that while \\fB~/.popt\\fP (or \\fB/etc/popt\\fP) is the default POPT\nconfiguration file, you can nominate specific file(s) to be used instead\nvia the \\fBMOCP_POPTRC\\fP environment variable.\n.LP\n.SH ENVIRONMENT VARIABLES\nThe following environment variables are used directly by MOC.  Additional\nvariables may be relevant to the libraries MOC uses.  Also, any environment\nenvironment variable may be substituted into a configuration file option\nvalue (see the 'config.example' file for details).\n.LP\n.TP\n.B ESCDELAY\nAn ncurses(3X) variable which specifies the delay (in milliseconds)\nafter which it will treat an ESC as a standalone key and not part of\nan escaped character sequence (such as is generated by function keys).\nMOC sets this value to 25ms by default, which is sufficient for most\nsystems.\n.LP\n.TP\n.B HOME\nTells MOC where your home directory is located and is used for various\npurposes, including the default location of the MOC directory.\n.LP\n.TP\n.B MOCP_OPTS\nThe value of this variable will be prepended to the command line options\nbefore they are processed.\n.LP\n.TP\n.B MOCP_POPTRC\nA colon-separated list of POPT configuration files which will be loaded in\nsequence by MOC during initialisation.  If the variable is unset then the\ndefault POPT configuration file will be used.  If the variable is set but\nempty then no POPT configuration file will be loaded.  If the variable is\nset then those files which exist will be loaded and those which don't will\nbe skipped.\n.IP\nAs these files can specify commands which invoke other applications, MOC\nwill refuse to start if they are not owned by root or the current user,\nor they are writable by anyone other than their owner.\n.LP\n.TP\n.B TERM \\fPand\\fB WINDOW\nUsed by MOC to distinguish between X-terminals, screen(1) and console\nterminals.  MOC uses the configuration file options \\fBXTerms\\fP and\n\\fBScreenTerms\\fP to help make this determination.\n.LP\n.SH FILES\n.TP\n.B ~/.moc\nMOC directory for the configuration file, socket, the pid file and other data.\n.LP\n.TP\n.B ~/.moc/config\nConfiguration file for MOC.  The format is very simple; to see how to use\nit look at the example configuration file (\\fBconfig.example\\fP) distributed\nwith the program.  The example file fully describes all the configuration\noptions, and so is a useful reference when using the \\fB\\-O\\fP option.  As\nthis file can specify commands which invoke other applications MOC will\nrefuse to start if it is not owned by either root or the current user, or if\nit is writable by anyone other than its owner.\n.LP\n.TP\n.B ~/.popt\n.TQ\n.B /etc/popt\nThe default files POPT reads to obtain aliased options.  As these files can\nspecify commands which invoke other applications, MOC will refuse to start\nif it is not owned by root or the current user, or if it is writable by\nanyone other than its owner.  (Also see the \\fBMOCP_POPTRC\\fP environment\nvariable above.)\n.LP\n.TP\n.B ~/.moc/themes\n.TQ\n.B /usr/share/moc/themes\nDefault directories for the theme files.\n.LP\n.TP\n.B /usr/share/moc/decoder_plugins\nDefault directories for the audio decoder plugins.\n.LP\n.TP\n.B mocp_client_log\n.TQ\n.B mocp_server_log\nClient and server log files.  These files are created in the directory in\nwhich the client and server are started.  (Also see the \\fB\\-D\\fP option.)\n.LP\n.SH BUGS\nCommand line options that affect the server behaviour (like\n\\fB\\-\\-sound\\-driver\\fP) are ignored if the server is already running at\nthe time of executing \\fBmocp\\fP.  The user is not warned about this.\n.LP\n.SH HOMEPAGE\nhttp://moc.daper.net/\n.LP\n.SH AUTHOR\nDamian Pietras     <daper@daper.net>\n.br\nMOC Maintainer(s)  <mocmaint@daper.net>\n"
  },
  {
    "path": "null_out.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* Fake output device - only for testing. */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <unistd.h>\n\n#include \"common.h\"\n#include \"audio.h\"\n\nstatic struct sound_params params = { 0, 0, 0 };\n\nstatic int null_open (struct sound_params *sound_params)\n{\n\tparams = *sound_params;\n\treturn 1;\n}\n\nstatic void null_close ()\n{\n\tparams.rate = 0;\n}\n\nstatic int null_play (const char *unused ATTR_UNUSED, const size_t size)\n{\n\txsleep (size, audio_get_bps ());\n\treturn size;\n}\n\nstatic int null_read_mixer ()\n{\n\treturn 100;\n}\n\nstatic void null_set_mixer (int unused ATTR_UNUSED)\n{\n}\n\nstatic int null_get_buff_fill ()\n{\n\treturn 0;\n}\n\nstatic int null_reset ()\n{\n\treturn 1;\n}\n\nstatic int null_init (struct output_driver_caps *caps)\n{\n\tcaps->formats = SFMT_S8 | SFMT_S16 | SFMT_LE;\n\tcaps->min_channels = 1;\n\tcaps->max_channels = 2;\n\n\treturn 1;\n}\n\nstatic int null_get_rate ()\n{\n\treturn params.rate;\n}\n\nstatic void null_toggle_mixer_channel ()\n{\n}\n\nstatic char *null_get_mixer_channel_name ()\n{\n\treturn xstrdup (\"FakeMixer\");\n}\n\nvoid null_funcs (struct hw_funcs *funcs)\n{\n\tfuncs->init = null_init;\n\tfuncs->open = null_open;\n\tfuncs->close = null_close;\n\tfuncs->play = null_play;\n\tfuncs->read_mixer = null_read_mixer;\n\tfuncs->set_mixer = null_set_mixer;\n\tfuncs->get_buff_fill = null_get_buff_fill;\n\tfuncs->reset = null_reset;\n\tfuncs->get_rate = null_get_rate;\n\tfuncs->toggle_mixer_channel = null_toggle_mixer_channel;\n\tfuncs->get_mixer_channel_name = null_get_mixer_channel_name;\n}\n"
  },
  {
    "path": "null_out.h",
    "content": "#ifndef NULL_OUT_H\n#define NULL_OUT_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid null_funcs (struct hw_funcs *funcs);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "options.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 - 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <assert.h>\n#include <string.h>\n#include <strings.h>\n#include <ctype.h>\n#include <errno.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <limits.h>\n#include <stdarg.h>\n#include <sys/types.h>\n#include <regex.h>\n\n#include \"common.h\"\n#include \"files.h\"\n#include \"log.h\"\n#include \"options.h\"\n#include \"lists.h\"\n\n#define OPTIONS_MAX\t181\n#define OPTION_NAME_MAX\t32\n\ntypedef int options_t_check (int, ...);\n\nunion option_value\n{\n\tchar *str;\n\tint num;\n\tbool boolean;\n\tlists_t_strs *list;\n};\n\nstruct option\n{\n\tchar name[OPTION_NAME_MAX];\n\tenum option_type type;\n\tunion option_value value;\n\tint ignore_in_config;\n\tint set_in_config;\n\tunsigned int hash;\n\toptions_t_check *check;\n\tint count;\n\tvoid *constraints;\n};\n\nstatic struct option options[OPTIONS_MAX];\nstatic int options_num = 0;\n\n\n/* Returns the str's hash using djb2 algorithm. */\nstatic unsigned int hash (const char * str)\n{\n\tunsigned int hash = 5381;\n\n\twhile (*str)\n\t\thash = ((hash << 5) + hash) + tolower(*(str++));\n\treturn hash;\n}\n\n/* Return an index to an option in the options hashtable.\n * If there is no such option return -1. */\nstatic int find_option (const char *name, enum option_type type)\n{\n\tunsigned int h = hash (name), i, init_pos = h % OPTIONS_MAX;\n\n\tfor (i = init_pos; i < OPTIONS_MAX; i += 1) {\n\t\tif (options[i].type == OPTION_FREE)\n\t\t\treturn -1;\n\n\t\tif (h == options[i].hash && type & options[i].type) {\n\t\t\tif (!strcasecmp (name, options[i].name))\n\t\t\t\treturn i;\n\t\t}\n\t}\n\n\tfor (i = 0; i < init_pos; i += 1) {\n\t\tif (options[i].type == OPTION_FREE)\n\t\t\treturn -1;\n\n\t\tif (h == options[i].hash && type & options[i].type) {\n\t\t\tif (!strcasecmp (name, options[i].name))\n\t\t    \treturn i;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\n/* Return an index of a free slot in the options hashtable.\n * If there is no such slot return -1. */\nstatic int find_free (unsigned int h)\n{\n\tunsigned int i;\n\n\tassert (options_num < OPTIONS_MAX);\n\th %= OPTIONS_MAX;\n\n\tfor (i = h; i < OPTIONS_MAX; i += 1) {\n\t\tif (options[i].type == OPTION_FREE)\n\t\t\treturn i;\n\t}\n\n\tfor (i = 0; i < h; i += 1) {\n\t\tif (options[i].type == OPTION_FREE)\n\t\t\treturn i;\n\t}\n\n\treturn -1;\n}\n\n/* Check that a value falls within the specified range(s). */\nstatic int check_range (int opt, ...)\n{\n\tint rc, ix, int_val;\n\tchar *str_val;\n\tva_list va;\n\n\tassert (opt != -1);\n\tassert (options[opt].count % 2 == 0);\n\tassert (options[opt].type & (OPTION_INT | OPTION_STR | OPTION_LIST));\n\n\trc = 0;\n\tva_start (va, opt);\n\tswitch (options[opt].type) {\n\n\t\tcase OPTION_INT:\n\t\t\tint_val = va_arg (va, int);\n\t\t\tfor (ix = 0; ix < options[opt].count; ix += 2) {\n\t\t\t\tif (int_val >= ((int *) options[opt].constraints)[ix] &&\n\t\t    \t\tint_val <= ((int *) options[opt].constraints)[ix + 1]) {\n\t\t\t\t\trc = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OPTION_STR:\n\t\tcase OPTION_LIST:\n\t\t\tstr_val = va_arg (va, char *);\n\t\t\tfor (ix = 0; ix < options[opt].count; ix += 2) {\n\t\t\t\tif (strcasecmp (str_val, (((char **) options[opt].constraints)[ix])) >= 0 &&\n\t\t\t\t    strcasecmp (str_val, (((char **) options[opt].constraints)[ix + 1])) <= 0) {\n\t\t\t\t\trc = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OPTION_BOOL:\n\t\tcase OPTION_SYMB:\n\t\tcase OPTION_ANY:\n\t\tcase OPTION_FREE:\n\t\t\tbreak;\n\t}\n\tva_end (va);\n\n\treturn rc;\n}\n\n/* Check that a value is one of the specified values. */\nstatic int check_discrete (int opt, ...)\n{\n\tint rc, ix, int_val;\n\tchar *str_val;\n\tva_list va;\n\n\tassert (opt != -1);\n\tassert (options[opt].type & (OPTION_INT | OPTION_SYMB | OPTION_LIST));\n\n\trc = 0;\n\tva_start (va, opt);\n\tswitch (options[opt].type) {\n\n\t\tcase OPTION_INT:\n\t\t\tint_val = va_arg (va, int);\n\t\t\tfor (ix = 0; ix < options[opt].count; ix += 1) {\n\t\t\t\tif (int_val == ((int *) options[opt].constraints)[ix]) {\n\t\t\t\t\trc = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OPTION_SYMB:\n\t\tcase OPTION_LIST:\n\t\t\tstr_val = va_arg (va, char *);\n\t\t\tfor (ix = 0; ix < options[opt].count; ix += 1) {\n\t\t\t\tif (!strcasecmp(str_val, (((char **) options[opt].constraints)[ix]))) {\n\t\t\t\t\trc = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase OPTION_BOOL:\n\t\tcase OPTION_STR:\n\t\tcase OPTION_ANY:\n\t\tcase OPTION_FREE:\n\t\t\tbreak;\n\t}\n\tva_end (va);\n\n\treturn rc;\n}\n\n/* Check that a string length falls within the specified range(s). */\nstatic int check_length (int opt, ...)\n{\n\tint rc, ix, str_len;\n\tva_list va;\n\n\tassert (opt != -1);\n\tassert (options[opt].count % 2 == 0);\n\tassert (options[opt].type & (OPTION_STR | OPTION_LIST));\n\n\trc = 0;\n\tva_start (va, opt);\n\tstr_len = strlen (va_arg (va, char *));\n\tfor (ix = 0; ix < options[opt].count; ix += 2) {\n\t\tif (str_len >= ((int *) options[opt].constraints)[ix] &&\n    \t\tstr_len <= ((int *) options[opt].constraints)[ix + 1]) {\n\t\t\trc = 1;\n\t\t\tbreak;\n\t\t}\n\t}\n\tva_end (va);\n\n\treturn rc;\n}\n\n/* Check that a string has a function-like syntax. */\nstatic int check_function (int opt, ...)\n{\n\tint rc;\n\tconst char *str;\n\tconst char regex[] = \"^[a-z0-9/-]+\\\\([^,) ]*(,[^,) ]*)*\\\\)$\";\n\tstatic regex_t *preg = NULL;\n\tva_list va;\n\n\tassert (opt != -1);\n\tassert (options[opt].count == 0);\n\tassert (options[opt].type & (OPTION_STR | OPTION_LIST));\n\n\tif (preg == NULL) {\n\t\tpreg = (regex_t *)xmalloc (sizeof (regex_t));\n\t\trc = regcomp (preg, regex, REG_EXTENDED | REG_ICASE | REG_NOSUB);\n\t\tassert (rc == 0);\n\t}\n\n\tva_start (va, opt);\n\tstr = va_arg (va, const char *);\n\trc = regexec (preg, str, 0, NULL, 0);\n\tva_end (va);\n\n\treturn (rc == 0) ? 1 : 0;\n}\n\n/* Always pass a value as valid. */\nstatic int check_true (int unused ATTR_UNUSED, ...)\n{\n\treturn 1;\n}\n\n/* Initializes a position on the options table. This is intended to be used at\n * initialization to make a table of valid options and its default values. */\nstatic int init_option (const char *name, enum option_type type)\n{\n\tunsigned int h=hash(name);\n\tint pos=find_free(h);\n\n\tassert (strlen(name) < OPTION_NAME_MAX);\n\tassert (is_valid_symbol (name));\n\tassert(pos>=0);\n\n\tstrcpy (options[pos].name, name);\n\toptions[pos].hash=h;\n\toptions[pos].type = type;\n\toptions[pos].ignore_in_config = 0;\n\toptions[pos].set_in_config = 0;\n\toptions[pos].check = check_true;\n\toptions[pos].count = 0;\n\toptions[pos].constraints = NULL;\n\n\toptions_num++;\n\treturn pos;\n}\n\n/* Add an integer option to the options table. This is intended to be used at\n * initialization to make a table of valid options and their default values. */\nstatic void add_int (const char *name, const int value, options_t_check *check, const int count, ...)\n{\n\tint ix, pos;\n\tva_list va;\n\n\tpos = init_option (name, OPTION_INT);\n\toptions[pos].value.num = value;\n\toptions[pos].check = check;\n\toptions[pos].count = count;\n\tif (count > 0) {\n\t\toptions[pos].constraints = xcalloc (count, sizeof (int));\n\t\tva_start (va, count);\n\t\tfor (ix = 0; ix < count; ix += 1)\n\t\t\t((int *) options[pos].constraints)[ix] = va_arg (va, int);\n\t\tva_end (va);\n\t}\n}\n\n/* Add a boolean option to the options table. This is intended to be used at\n * initialization to make a table of valid options and their default values. */\nstatic void add_bool (const char *name, const bool value)\n{\n\tint pos;\n\n\tpos = init_option (name, OPTION_BOOL);\n\toptions[pos].value.boolean = value;\n}\n\n/* Add a string option to the options table. This is intended to be used at\n * initialization to make a table of valid options and their default values. */\nstatic void add_str (const char *name, const char *value, options_t_check *check, const int count, ...)\n{\n\tint ix, pos;\n\tva_list va;\n\n\tpos = init_option (name, OPTION_STR);\n\toptions[pos].value.str = xstrdup (value);\n\toptions[pos].check = check;\n\toptions[pos].count = count;\n\tif (count > 0) {\n\t\tva_start (va, count);\n\t\tif (check == check_length) {\n\t\t\toptions[pos].constraints = xcalloc (count, sizeof (int));\n\t\t\tfor (ix = 0; ix < count; ix += 1)\n\t\t\t\t((int *) options[pos].constraints)[ix] = va_arg (va, int);\n\t\t} else {\n\t\t\toptions[pos].constraints = xcalloc (count, sizeof (char *));\n\t\t\tfor (ix = 0; ix < count; ix += 1)\n\t\t\t\t((char **) options[pos].constraints)[ix] = xstrdup (va_arg (va, char *));\n\t\t}\n\t\tva_end (va);\n\t}\n}\n\n/* Add a symbol option to the options table. This is intended to be used at\n * initialization to make a table of valid options and their default values. */\nstatic void add_symb (const char *name, const char *value, const int count, ...)\n{\n\tint ix, pos;\n\tva_list va;\n\n\tassert (name != NULL);\n\tassert (value != NULL);\n\tassert (count > 0);\n\n\tpos = init_option (name, OPTION_SYMB);\n\toptions[pos].value.str = NULL;\n\toptions[pos].check = check_discrete;\n\toptions[pos].count = count;\n\tva_start (va, count);\n\toptions[pos].constraints = xcalloc (count, sizeof (char *));\n\tfor (ix = 0; ix < count; ix += 1) {\n\t\tchar *val = va_arg (va, char *);\n\t\tif (!is_valid_symbol (val))\n\t\t\tfatal (\"Invalid symbol in '%s' constraint list!\", name);\n\t\t((char **) options[pos].constraints)[ix] = xstrdup (val);\n\t\tif (!strcasecmp (val, value))\n\t\t\toptions[pos].value.str = ((char **) options[pos].constraints)[ix];\n\t}\n\tif (!options[pos].value.str)\n\t\tfatal (\"Invalid default value symbol in '%s'!\", name);\n\tva_end (va);\n}\n\n/* Add a list option to the options table. This is intended to be used at\n * initialization to make a table of valid options and their default values. */\nstatic void add_list (const char *name, const char *value, options_t_check *check, const int count, ...)\n{\n\tint ix, pos;\n\tva_list va;\n\n\tpos = init_option (name, OPTION_LIST);\n\toptions[pos].value.list = lists_strs_new (8);\n\tif (value)\n\t\tlists_strs_split (options[pos].value.list, value, \":\");\n\toptions[pos].check = check;\n\toptions[pos].count = count;\n\tif (count > 0) {\n\t\tva_start (va, count);\n\t\tif (check == check_length) {\n\t\t\toptions[pos].constraints = xcalloc (count, sizeof (int));\n\t\t\tfor (ix = 0; ix < count; ix += 1)\n\t\t\t\t((int *) options[pos].constraints)[ix] = va_arg (va, int);\n\t\t} else {\n\t\t\toptions[pos].constraints = xcalloc (count, sizeof (char *));\n\t\t\tfor (ix = 0; ix < count; ix += 1)\n\t\t\t\t((char **) options[pos].constraints)[ix] = xstrdup (va_arg (va, char *));\n\t\t}\n\t\tva_end (va);\n\t}\n}\n\n/* Set an integer option to the value. */\nvoid options_set_int (const char *name, const int value)\n{\n\tint i = find_option (name, OPTION_INT);\n\n\tif (i == -1)\n\t\tfatal (\"Tried to set wrong option '%s'!\", name);\n\toptions[i].value.num = value;\n}\n\n/* Set a boolean option to the value. */\nvoid options_set_bool (const char *name, const bool value)\n{\n\tint i = find_option (name, OPTION_BOOL);\n\n\tif (i == -1)\n\t\tfatal (\"Tried to set wrong option '%s'!\", name);\n\toptions[i].value.boolean = value;\n}\n\n/* Set a symbol option to the value. */\nvoid options_set_symb (const char *name, const char *value)\n{\n\tint opt, ix;\n\n\topt = find_option (name, OPTION_SYMB);\n\tif (opt == -1)\n\t\tfatal (\"Tried to set wrong option '%s'!\", name);\n\n\toptions[opt].value.str = NULL;\n\tfor (ix = 0; ix < options[opt].count; ix += 1) {\n\t\tif (!strcasecmp(value, (((char **) options[opt].constraints)[ix])))\n\t\t\toptions[opt].value.str = ((char **) options[opt].constraints)[ix];\n\t}\n\tif (!options[opt].value.str)\n\t\tfatal (\"Tried to set '%s' to unknown symbol '%s'!\", name, value);\n}\n\n/* Set a string option to the value. The string is duplicated. */\nvoid options_set_str (const char *name, const char *value)\n{\n\tint opt = find_option (name, OPTION_STR);\n\n\tif (opt == -1)\n\t\tfatal (\"Tried to set wrong option '%s'!\", name);\n\n\tif (options[opt].value.str)\n\t\tfree (options[opt].value.str);\n\toptions[opt].value.str = xstrdup (value);\n}\n\n/* Set list option values to the colon separated value. */\nvoid options_set_list (const char *name, const char *value, bool append)\n{\n\tint opt;\n\n\topt = find_option (name, OPTION_LIST);\n\tif (opt == -1)\n\t\tfatal (\"Tried to set wrong option '%s'!\", name);\n\n\tif (!append && !lists_strs_empty (options[opt].value.list))\n\t\tlists_strs_clear (options[opt].value.list);\n\tlists_strs_split (options[opt].value.list, value, \":\");\n}\n\n/* Given a type, a name and a value, set that option's value.\n * Return false on error. */\nbool options_set_pair (const char *name, const char *value, bool append)\n{\n\tint num;\n\tchar *end;\n\tbool val;\n\n\tswitch (options_get_type (name)) {\n\n\t\tcase OPTION_INT:\n\t\t\tnum = strtol (value, &end, 10);\n\t\t\tif (*end)\n\t\t\t\treturn false;\n\t\t\tif (!options_check_int (name, num))\n\t\t\t\treturn false;\n\t\t\toptions_set_int (name, num);\n\t\t\tbreak;\n\n\t\tcase OPTION_BOOL:\n\t\t\tif (!strcasecmp (value, \"yes\"))\n\t\t\t\tval = true;\n\t\t\telse if (!strcasecmp (value, \"no\"))\n\t\t\t\tval = false;\n\t\t\telse\n\t\t\t\treturn false;\n\t\t\toptions_set_bool (name, val);\n\t\t\tbreak;\n\n\t\tcase OPTION_STR:\n\t\t\tif (!options_check_str (name, value))\n\t\t\t\treturn false;\n\t\t\toptions_set_str (name, value);\n\t\t\tbreak;\n\n\t\tcase OPTION_SYMB:\n\t\t\tif (!options_check_symb (name, value))\n\t\t\t\treturn false;\n\t\t\toptions_set_symb (name, value);\n\t\t\tbreak;\n\n\t\tcase OPTION_LIST:\n\t\t\tif (!options_check_list (name, value))\n\t\t\t\treturn false;\n\t\t\toptions_set_list (name, value, append);\n\t\t\tbreak;\n\n\t\tcase OPTION_FREE:\n\t\tcase OPTION_ANY:\n\t\t\treturn false;\n\t}\n\n\treturn true;\n}\n\nvoid options_ignore_config (const char *name)\n{\n\tint opt = find_option (name, OPTION_ANY);\n\n\tif (opt == -1)\n\t\tfatal (\"Tried to set wrong option '%s'!\", name);\n\n\toptions[opt].ignore_in_config = 1;\n}\n\n#define CHECK_DISCRETE(c)   check_discrete, (c)\n#define CHECK_RANGE(c)      check_range, (2 * (c))\n#define CHECK_LENGTH(c)     check_length, (2 * (c))\n#define CHECK_SYMBOL(c)     (c)\n#define CHECK_FUNCTION      check_function, 0\n#define CHECK_NONE          check_true, 0\n\n/* Make a table of options and its default values. */\nvoid options_init ()\n{\n\tmemset (options, 0, sizeof(options));\n\n\tadd_bool (\"ReadTags\", true);\n\tadd_str  (\"MusicDir\", NULL, CHECK_NONE);\n\tadd_bool (\"StartInMusicDir\", false);\n\tadd_int  (\"CircularLogSize\", 0, CHECK_RANGE(1), 0, INT_MAX);\n\tadd_symb (\"Sort\", \"FileName\", CHECK_SYMBOL(1), \"FileName\");\n\tadd_bool (\"ShowStreamErrors\", false);\n\tadd_bool (\"MP3IgnoreCRCErrors\", true);\n\tadd_bool (\"Repeat\", false);\n\tadd_bool (\"Shuffle\", false);\n\tadd_bool (\"AutoNext\", true);\n\tadd_str  (\"FormatString\",\n\t          \"%(n:%n :)%(a:%a - :)%(t:%t:)%(A: \\\\(%A\\\\):)\", CHECK_NONE);\n\tadd_int  (\"InputBuffer\", 512, CHECK_RANGE(1), 32, INT_MAX);\n\tadd_int  (\"OutputBuffer\", 512, CHECK_RANGE(1), 128, INT_MAX);\n\tadd_int  (\"Prebuffering\", 64, CHECK_RANGE(1), 0, INT_MAX);\n\tadd_str  (\"HTTPProxy\", NULL, CHECK_NONE);\n\n#ifdef OPENBSD\n\tadd_list (\"SoundDriver\", \"SNDIO:JACK:OSS\",\n\t          CHECK_DISCRETE(5), \"SNDIO\", \"Jack\", \"ALSA\", \"OSS\", \"null\");\n#else\n\tadd_list (\"SoundDriver\", \"Jack:ALSA:OSS\",\n\t          CHECK_DISCRETE(5), \"SNDIO\", \"Jack\", \"ALSA\", \"OSS\", \"null\");\n#endif\n\n\tadd_str  (\"JackClientName\", \"moc\", CHECK_NONE);\n\tadd_bool (\"JackStartServer\", false);\n\tadd_str  (\"JackOutLeft\", \"system:playback_1\", CHECK_NONE);\n\tadd_str  (\"JackOutRight\", \"system:playback_2\", CHECK_NONE);\n\n\tadd_str  (\"OSSDevice\", \"/dev/dsp\", CHECK_NONE);\n\tadd_str  (\"OSSMixerDevice\", \"/dev/mixer\", CHECK_NONE);\n\tadd_symb (\"OSSMixerChannel1\", \"pcm\",\n\t          CHECK_SYMBOL(3), \"pcm\", \"master\", \"speaker\");\n\tadd_symb (\"OSSMixerChannel2\", \"master\",\n\t          CHECK_SYMBOL(3), \"pcm\", \"master\", \"speaker\");\n\n\tadd_str  (\"ALSADevice\", \"default\", CHECK_NONE);\n\tadd_str  (\"ALSAMixer1\", \"PCM\", CHECK_NONE);\n\tadd_str  (\"ALSAMixer2\", \"Master\", CHECK_NONE);\n\tadd_bool (\"ALSAStutterDefeat\", false);\n\n\tadd_bool (\"Softmixer_SaveState\", true);\n\tadd_bool (\"Equalizer_SaveState\", true);\n\n\tadd_bool (\"ShowHiddenFiles\", false);\n\tadd_bool (\"HideFileExtension\", false);\n\tadd_bool (\"ShowFormat\", true);\n\tadd_symb (\"ShowTime\", \"IfAvailable\",\n\t                 CHECK_SYMBOL(3), \"yes\", \"no\", \"IfAvailable\");\n\tadd_bool (\"ShowTimePercent\", false);\n\n\tadd_list (\"ScreenTerms\", \"screen:screen-w:vt100\", CHECK_NONE);\n\n\tadd_list (\"XTerms\", \"xterm:\"\n\t                    \"xterm-colour:xterm-color:\"\n\t                    \"xterm-256colour:xterm-256color:\"\n\t                    \"rxvt:rxvt-unicode:\"\n\t                    \"rxvt-unicode-256colour:rxvt-unicode-256color:\"\n\t                    \"eterm\", CHECK_NONE);\n\n\tadd_str  (\"Theme\", NULL, CHECK_NONE);\n\tadd_str  (\"XTermTheme\", NULL, CHECK_NONE);\n\tadd_str  (\"ForceTheme\", NULL, CHECK_NONE); /* Used when -T is set */\n\tadd_bool (\"AutoLoadLyrics\", true);\n\tadd_str  (\"MOCDir\", \"~/.moc\", CHECK_NONE);\n\tadd_bool (\"UseMMap\", false);\n\tadd_bool (\"UseMimeMagic\", false);\n\tadd_str  (\"ID3v1TagsEncoding\", \"WINDOWS-1250\", CHECK_NONE);\n\tadd_bool (\"UseRCC\", true);\n\tadd_bool (\"UseRCCForFilesystem\", true);\n\tadd_bool (\"EnforceTagsEncoding\", false);\n\tadd_bool (\"FileNamesIconv\", false);\n\tadd_bool (\"NonUTFXterm\", false);\n\tadd_bool (\"Precache\", true);\n\tadd_bool (\"SavePlaylist\", true);\n\tadd_bool (\"SyncPlaylist\", true);\n\tadd_str  (\"Keymap\", NULL, CHECK_NONE);\n\tadd_bool (\"ASCIILines\", false);\n\n\tadd_str  (\"FastDir1\", NULL, CHECK_NONE);\n\tadd_str  (\"FastDir2\", NULL, CHECK_NONE);\n\tadd_str  (\"FastDir3\", NULL, CHECK_NONE);\n\tadd_str  (\"FastDir4\", NULL, CHECK_NONE);\n\tadd_str  (\"FastDir5\", NULL, CHECK_NONE);\n\tadd_str  (\"FastDir6\", NULL, CHECK_NONE);\n\tadd_str  (\"FastDir7\", NULL, CHECK_NONE);\n\tadd_str  (\"FastDir8\", NULL, CHECK_NONE);\n\tadd_str  (\"FastDir9\", NULL, CHECK_NONE);\n\tadd_str  (\"FastDir10\", NULL, CHECK_NONE);\n\n\tadd_int  (\"SeekTime\", 1, CHECK_RANGE(1), 1, INT_MAX);\n\tadd_int  (\"SilentSeekTime\", 5, CHECK_RANGE(1), 1, INT_MAX);\n\n\tadd_list (\"PreferredDecoders\",\n\t                 \"aac(aac,ffmpeg):m4a(ffmpeg):\"\n\t                 \"mpc(musepack,*,ffmpeg):mpc8(musepack,*,ffmpeg):\"\n\t                 \"sid(sidplay2):mus(sidplay2):\"\n\t                 \"wav(sndfile,*,ffmpeg):\"\n\t                 \"wv(wavpack,*,ffmpeg):\"\n\t                 \"audio/aac(aac):audio/aacp(aac):audio/m4a(ffmpeg):\"\n\t                 \"audio/wav(sndfile,*):\"\n\t                 \"ogg(vorbis,*,ffmpeg):oga(vorbis,*,ffmpeg):ogv(ffmpeg):\"\n\t                 \"application/ogg(vorbis):audio/ogg(vorbis):\"\n\t                 \"flac(flac,*,ffmpeg):\"\n\t                 \"opus(ffmpeg):\"\n\t                 \"spx(speex)\",\n\t                 CHECK_FUNCTION);\n\n\tadd_symb (\"ResampleMethod\", \"Linear\",\n\t                 CHECK_SYMBOL(5), \"SincBestQuality\", \"SincMediumQuality\",\n\t                                  \"SincFastest\", \"ZeroOrderHold\", \"Linear\");\n\tadd_int  (\"ForceSampleRate\", 0, CHECK_RANGE(1), 0, 500000);\n\tadd_bool (\"Allow24bitOutput\", false);\n\tadd_bool (\"UseRealtimePriority\", false);\n\tadd_int  (\"TagsCacheSize\", 256, CHECK_RANGE(1), 0, INT_MAX);\n\tadd_bool (\"PlaylistNumbering\", true);\n\n\tadd_list (\"Layout1\", \"directory(0,0,50%,100%):playlist(50%,0,FILL,100%)\",\n\t                     CHECK_FUNCTION);\n\tadd_list (\"Layout2\", \"directory(0,0,100%,100%):playlist(0,0,100%,100%)\",\n\t                     CHECK_FUNCTION);\n\tadd_list (\"Layout3\", NULL, CHECK_FUNCTION);\n\n\tadd_bool (\"FollowPlayedFile\", true);\n\tadd_bool (\"CanStartInPlaylist\", true);\n\tadd_str  (\"ExecCommand1\", NULL, CHECK_NONE);\n\tadd_str  (\"ExecCommand2\", NULL, CHECK_NONE);\n\tadd_str  (\"ExecCommand3\", NULL, CHECK_NONE);\n\tadd_str  (\"ExecCommand4\", NULL, CHECK_NONE);\n\tadd_str  (\"ExecCommand5\", NULL, CHECK_NONE);\n\tadd_str  (\"ExecCommand6\", NULL, CHECK_NONE);\n\tadd_str  (\"ExecCommand7\", NULL, CHECK_NONE);\n\tadd_str  (\"ExecCommand8\", NULL, CHECK_NONE);\n\tadd_str  (\"ExecCommand9\", NULL, CHECK_NONE);\n\tadd_str  (\"ExecCommand10\", NULL, CHECK_NONE);\n\n\tadd_bool (\"UseCursorSelection\", false);\n\tadd_bool (\"SetXtermTitle\", true);\n\tadd_bool (\"SetScreenTitle\", true);\n\tadd_bool (\"PlaylistFullPaths\", true);\n\n\tadd_str  (\"BlockDecorators\", \"`\\\"'\", CHECK_LENGTH(1), 3, 3);\n\tadd_int  (\"MessageLingerTime\", 3, CHECK_RANGE(1), 0, INT_MAX);\n\tadd_bool (\"PrefixQueuedMessages\", true);\n\tadd_str  (\"ErrorMessagesQueued\", \"!\", CHECK_NONE);\n\n\tadd_bool (\"ModPlug_Oversampling\", true);\n\tadd_bool (\"ModPlug_NoiseReduction\", true);\n\tadd_bool (\"ModPlug_Reverb\", false);\n\tadd_bool (\"ModPlug_MegaBass\", false);\n\tadd_bool (\"ModPlug_Surround\", false);\n\tadd_symb (\"ModPlug_ResamplingMode\", \"FIR\",\n\t                 CHECK_SYMBOL(4), \"FIR\", \"SPLINE\", \"LINEAR\", \"NEAREST\");\n\tadd_int  (\"ModPlug_Channels\", 2, CHECK_DISCRETE(2), 1, 2);\n\tadd_int  (\"ModPlug_Bits\", 16, CHECK_DISCRETE(3), 8, 16, 32);\n\tadd_int  (\"ModPlug_Frequency\", 44100,\n\t                 CHECK_DISCRETE(4), 11025, 22050, 44100, 48000);\n\tadd_int  (\"ModPlug_ReverbDepth\", 0, CHECK_RANGE(1), 0, 100);\n\tadd_int  (\"ModPlug_ReverbDelay\", 0, CHECK_RANGE(1), 0, INT_MAX);\n\tadd_int  (\"ModPlug_BassAmount\", 0, CHECK_RANGE(1), 0, 100);\n\tadd_int  (\"ModPlug_BassRange\", 10, CHECK_RANGE(1), 10, 100);\n\tadd_int  (\"ModPlug_SurroundDepth\", 0, CHECK_RANGE(1), 0, 100);\n\tadd_int  (\"ModPlug_SurroundDelay\", 0, CHECK_RANGE(1), 0, INT_MAX);\n\tadd_int  (\"ModPlug_LoopCount\", 0, CHECK_RANGE(1), -1, INT_MAX);\n\n\tadd_int  (\"TiMidity_Rate\", 44100, CHECK_RANGE(1), 8000, 48000);\n\t\t// not sure about the limits... I like 44100\n\tadd_int  (\"TiMidity_Bits\", 16, CHECK_DISCRETE(2), 8, 16);\n\tadd_int  (\"TiMidity_Channels\", 2, CHECK_DISCRETE(2), 1, 2);\n\tadd_int  (\"TiMidity_Volume\", 100, CHECK_RANGE(1), 0, 800);\n\tadd_str  (\"TiMidity_Config\", NULL, CHECK_NONE);\n\n\tadd_int  (\"SidPlay2_DefaultSongLength\", 180,\n\t                 CHECK_RANGE(1), 0, INT_MAX);\n\tadd_int  (\"SidPlay2_MinimumSongLength\", 0,\n\t                 CHECK_RANGE(1), 0, INT_MAX);\n\tadd_str  (\"SidPlay2_Database\", NULL, CHECK_NONE);\n\tadd_int  (\"SidPlay2_Frequency\", 44100, CHECK_RANGE(1), 4000, 48000);\n\tadd_int  (\"SidPlay2_Bits\", 16, CHECK_DISCRETE(2), 8, 16);\n\tadd_int  (\"SidPlay2_Optimisation\", 0, CHECK_RANGE(1), 0, 2);\n\tadd_symb (\"SidPlay2_PlayMode\", \"M\",\n\t                 CHECK_SYMBOL(4), \"M\", \"S\", \"L\", \"R\");\n\tadd_bool (\"SidPlay2_StartAtStart\", true);\n\tadd_bool (\"SidPlay2_PlaySubTunes\", true);\n\n\tadd_str  (\"OnSongChange\", NULL, CHECK_NONE);\n\tadd_bool (\"RepeatSongChange\", false);\n\tadd_str  (\"OnServerStart\", NULL, CHECK_NONE);\n\tadd_str  (\"OnServerStop\", NULL, CHECK_NONE);\n\tadd_str  (\"OnStop\", NULL, CHECK_NONE);\n\n\tadd_bool (\"QueueNextSongReturn\", false);\n}\n\n/* Return 1 if a parameter to an integer option is valid. */\nint options_check_int (const char *name, const int val)\n{\n\tint opt;\n\n\topt = find_option (name, OPTION_INT);\n\tif (opt == -1)\n\t\treturn 0;\n\treturn options[opt].check (opt, val);\n}\n\n/* Return 1 if a parameter to a boolean option is valid.  This may seem\n * pointless but it provides a consistant interface, ensures the existence\n * of the option and checks the value where true booleans are emulated with\n * other types. */\nint options_check_bool (const char *name, const bool val)\n{\n\tint opt, result = 0;\n\n\topt = find_option (name, OPTION_BOOL);\n\tif (opt == -1)\n\t\treturn 0;\n\tif (val == true || val == false)\n\t\tresult = 1;\n\treturn result;\n}\n\n/* Return 1 if a parameter to a string option is valid. */\nint options_check_str (const char *name, const char *val)\n{\n\tint opt;\n\n\topt = find_option (name, OPTION_STR);\n\tif (opt == -1)\n\t\treturn 0;\n\treturn options[opt].check (opt, val);\n}\n\n/* Return 1 if a parameter to a symbol option is valid. */\nint options_check_symb (const char *name, const char *val)\n{\n\tint opt;\n\n\topt = find_option (name, OPTION_SYMB);\n\tif (opt == -1)\n\t\treturn 0;\n\treturn check_discrete (opt, val);\n}\n\n/* Return 1 if a parameter to a list option is valid. */\nint options_check_list (const char *name, const char *val)\n{\n\tint opt, size, ix, result;\n\tlists_t_strs *list;\n\n\tassert (name);\n\tassert (val);\n\n\topt = find_option (name, OPTION_LIST);\n\tif (opt == -1)\n\t\treturn 0;\n\n\tlist = lists_strs_new (8);\n\tsize = lists_strs_split (list, val, \":\");\n\tresult = 1;\n\tfor (ix = 0; ix < size; ix += 1) {\n\t\tif (!options[opt].check (opt, lists_strs_at (list, ix))) {\n\t\t\tresult = 0;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tlists_strs_free (list);\n\n\treturn result;\n}\n\n/* Return 1 if the named option was defaulted. */\nint options_was_defaulted (const char *name)\n{\n\tint opt, result = 0;\n\n\tassert (name);\n\n\topt = find_option (name, OPTION_ANY);\n\tif (opt == -1)\n\t\treturn 0;\n\n\tif (!options[opt].set_in_config && !options[opt].ignore_in_config)\n\t\tresult = 1;\n\n\treturn result;\n}\n\n/* Find and substitute variables enclosed by '${...}'.  Variables are\n * substituted first from the environment then, if not found, from\n * the configuration options.  Strings of the form '$${' are reduced to\n * '${' and not substituted.  The result is returned as a new string. */\nstatic char *substitute_variable (const char *name_in, const char *value_in)\n{\n\tsize_t len;\n\tchar *dollar, *result, *ptr, *name, *value, *dflt, *end;\n\tstatic const char accept[] = \"abcdefghijklmnopqrstuvwxyz\"\n\t                             \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\t                             \"0123456789_\";\n\tlists_t_strs *strs;\n\n\tresult = xstrdup (value_in);\n\tptr = result;\n\tstrs = lists_strs_new (5);\n\tdollar = strstr (result, \"${\");\n\twhile (dollar) {\n\n\t\t/* Escape \"$${\". */\n\t\tif (dollar > ptr && dollar[-1] == '$') {\n\t\t\tdollar[-1] = 0x00;\n\t\t\tlists_strs_append (strs, ptr);\n\t\t\tptr = dollar;\n\t\t\tdollar = strstr (&dollar[2], \"${\");\n\t\t\tcontinue;\n\t\t}\n\n\t\t/* Copy up to this point verbatim. */\n\t\tdollar[0] = 0x00;\n\t\tlists_strs_append (strs, ptr);\n\n\t\t/* Find where the substitution variable name ends. */\n\t\tname = &dollar[2];\n\t\tlen = strspn (name, accept);\n\t\tif (len == 0)\n\t\t\tfatal (\"Error in config file option '%s':\\n\"\n\t\t\t       \"             substitution variable name is missing!\",\n\t\t\t       name_in);\n\n\t\t/* Find default substitution or closing brace. */\n\t\tdflt = NULL;\n\t\tif (name[len] == '}') {\n\t\t\tend = &name[len];\n\t\t\tend[0] = 0x00;\n\t\t}\n\t\telse if (strncmp (&name[len], \":-\", 2) == 0) {\n\t\t\tname[len] = 0x00;\n\t\t\tdflt = &name[len + 2];\n\t\t\tend = strchr (dflt, '}');\n\t\t\tif (end == NULL)\n\t\t\t\tfatal (\"Error in config file option '%s': \"\n\t\t\t\t       \"unterminated '${%s:-'!\",\n\t\t\t\t       name_in, name);\n\t\t\tend[0] = 0x00;\n\t\t}\n\t\telse if (name[len] == 0x00) {\n\t\t\tfatal (\"Error in config file option '%s': \"\n\t\t\t       \"unterminated '${'!\",\n\t\t\t       name_in);\n\t\t}\n\t\telse {\n\t\t\tfatal (\"Error in config file option '%s':\\n\"\n\t\t\t       \"             expecting  ':-' or '}' found '%c'!\",\n\t\t\t       name_in, name[len]);\n\t\t}\n\n\t\t/* Fetch environment variable or configuration option value. */\n\t\tvalue = xstrdup (getenv (name));\n\t\tif (value == NULL && find_option (name, OPTION_ANY) != -1) {\n\t\t\tchar buf[16];\n\t\t\tlists_t_strs *list;\n\n\t\t\tswitch (options_get_type (name)) {\n\t\t\tcase OPTION_INT:\n\t\t\t\tsnprintf (buf, sizeof (buf), \"%d\", options_get_int (name));\n\t\t\t\tvalue = xstrdup (buf);\n\t\t\t\tbreak;\n\t\t\tcase OPTION_BOOL:\n\t\t\t\tvalue = xstrdup (options_get_bool (name) ? \"yes\" : \"no\");\n\t\t\t\tbreak;\n\t\t\tcase OPTION_STR:\n\t\t\t\tvalue = xstrdup (options_get_str (name));\n\t\t\t\tbreak;\n\t\t\tcase OPTION_SYMB:\n\t\t\t\tvalue = xstrdup (options_get_symb (name));\n\t\t\t\tbreak;\n\t\t\tcase OPTION_LIST:\n\t\t\t\tlist = options_get_list (name);\n\t\t\t\tif (!lists_strs_empty (list)) {\n\t\t\t\t\tvalue = lists_strs_fmt (list, \"%s:\");\n\t\t\t\t\tvalue[strlen (value) - 1] = 0x00;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase OPTION_FREE:\n\t\t\tcase OPTION_ANY:\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (value && value[0])\n\t\t\tlists_strs_append (strs, value);\n\t\telse if (dflt)\n\t\t\tlists_strs_append (strs, dflt);\n\t\telse\n\t\t\tfatal (\"Error in config file option '%s':\\n\"\n\t\t\t       \"             substitution variable '%s' not set or null!\",\n\t\t           name_in, &dollar[2]);\n\t\tfree (value);\n\n\t\t/* Go look for another substitution. */\n\t\tptr = &end[1];\n\t\tdollar = strstr (ptr, \"${\");\n\t}\n\n\t/* If anything changed copy segments to result. */\n\tif (!lists_strs_empty (strs)) {\n\t\tlists_strs_append (strs, ptr);\n\t\tfree (result);\n\t\tresult = lists_strs_cat (strs);\n\t}\n\tlists_strs_free (strs);\n\n\treturn result;\n}\n\n/* Set an option read from the configuration file. Return false on error. */\nstatic bool set_option (const char *name, const char *value_in, bool append)\n{\n\tint i;\n\tchar *value;\n\n\ti = find_option (name, OPTION_ANY);\n\tif (i == -1) {\n\t\tfprintf (stderr, \"Wrong option name: '%s'.\", name);\n\t\treturn false;\n\t}\n\n\tif (options[i].ignore_in_config)\n\t\treturn true;\n\n\tif (append && options[i].type != OPTION_LIST) {\n\t\tfprintf (stderr,\n\t\t         \"Only list valued options can be appended to ('%s').\",\n\t\t         name);\n\t\treturn false;\n\t}\n\n\tif (!append && options[i].set_in_config) {\n\t\tfprintf (stderr, \"Tried to set an option that has been already \"\n\t\t                 \"set in the config file ('%s').\", name);\n\t\treturn false;\n\t}\n\n\toptions[i].set_in_config = 1;\n\n\t/* Substitute environmental variables. */\n\tvalue = substitute_variable (name, value_in);\n\n\tif (!options_set_pair (name, value, append))\n\t\treturn false;\n\n\tfree (value);\n\treturn true;\n}\n\n/* Check if values of options make sense. This only checks options that can't\n * be checked without parsing the whole file. */\nstatic void sanity_check ()\n{\n\tif (options_get_int (\"Prebuffering\") > options_get_int (\"InputBuffer\"))\n\t\tfatal (\"Prebuffering is set to a value greater than InputBuffer!\");\n}\n\n/* Parse the configuration file. */\nvoid options_parse (const char *config_file)\n{\n\tint ch;\n\tint comm = 0; /* comment? */\n\tint eq = 0; /* equal character appeared? */\n\tint quote = 0; /* are we in quotes? */\n\tint esc = 0;\n\tbool plus = false; /* plus character appeared? */\n\tbool append = false; /* += (list append) appeared */\n\tbool sp = false; /* first post-name space detected */\n\tchar opt_name[30];\n\tchar opt_value[512];\n\tint line = 1;\n\tint name_pos = 0;\n\tint value_pos = 0;\n\tFILE *file;\n\n\tif (!is_secure (config_file))\n\t\tfatal (\"Configuration file is not secure: %s\", config_file);\n\n\tif (!(file = fopen(config_file, \"r\"))) {\n\t\tlog_errno (\"Can't open config file\", errno);\n\t\treturn;\n\t}\n\n\twhile ((ch = getc(file)) != EOF) {\n\n\t\t/* Skip comment */\n\t\tif (comm && ch != '\\n')\n\t\t\tcontinue;\n\n\t\t/* Check for \"+=\" (list append) */\n\t\tif (ch != '=' && plus)\n\t\t\tfatal (\"Error in config file: stray '+' on line %d!\", line);\n\n\t\t/* Interpret parameter */\n\t\tif (ch == '\\n') {\n\t\t\tcomm = 0;\n\n\t\t\topt_name[name_pos] = 0;\n\t\t\topt_value[value_pos] = 0;\n\n\t\t\tif (name_pos) {\n\t\t\t\tif (value_pos == 0 && strncasecmp (opt_name, \"Layout\", 6))\n\t\t\t\t\tfatal (\"Error in config file: \"\n\t\t\t\t\t       \"missing option value on line %d!\", line);\n\t\t\t\tif (!set_option (opt_name, opt_value, append))\n\t\t\t\t\tfatal (\"Error in config file on line %d!\", line);\n\t\t\t}\n\n\t\t\tname_pos = 0;\n\t\t\tvalue_pos = 0;\n\t\t\teq = 0;\n\t\t\tquote = 0;\n\t\t\tesc = 0;\n\t\t\tappend = false;\n\t\t\tsp = false;\n\n\t\t\tline++;\n\t\t}\n\n\t\t/* Turn on comment */\n\t\telse if (ch == '#' && !quote)\n\t\t\tcomm = 1;\n\n\t\t/* Turn on quote */\n\t\telse if (!quote && !esc && (ch == '\"'))\n\t\t\tquote = 1;\n\n\t\t/* Turn off quote */\n\t\telse if (!esc && quote && ch == '\"')\n\t\t\tquote = 0;\n\n\t\telse if (!esc && !eq && ch == '+')\n\t\t\tplus = true;\n\n\t\telse if (ch == '=' && !quote) {\n\t\t\tif (eq)\n\t\t\t\tfatal (\"Error in config file: stray '=' on line %d!\", line);\n\t\t\tif (name_pos == 0)\n\t\t\t\tfatal (\"Error in config file: \"\n\t\t\t\t       \"missing option name on line %d!\", line);\n\t\t\tappend = plus;\n\t\t\tplus = false;\n\t\t\teq = 1;\n\t\t}\n\n\t\t/* Turn on escape */\n\t\telse if (ch == '\\\\' && !esc)\n\t\t\tesc = 1;\n\n\t\t/* Embedded blank detection */\n\t\telse if (!eq && name_pos && isblank(ch))\n\t\t\tsp = true;\n\t\telse if (!eq && sp && !isblank(ch))\n\t\t\tfatal (\"Error in config file: \"\n\t\t\t       \"embedded blank in option name on line %d!\", line);\n\n\t\t/* Add char to parameter value */\n\t\telse if ((!isblank(ch) || quote) && eq) {\n\t\t\tif (esc && ch != '\"') {\n\t\t\t\tif (sizeof(opt_value) == value_pos)\n\t\t\t\t\tfatal (\"Error in config file: \"\n\t\t\t\t\t       \"option value on line %d is too long!\", line);\n\t\t\t\topt_value[value_pos++] = '\\\\';\n\t\t\t}\n\n\t\t\tif (sizeof(opt_value) == value_pos)\n\t\t\t\tfatal (\"Error in config file: \"\n\t\t\t\t       \"option value on line %d is too long!\", line);\n\t\t\topt_value[value_pos++] = ch;\n\t\t\tesc = 0;\n\t\t}\n\n\t\t/* Add char to parameter name */\n\t\telse if (!isblank(ch) || quote) {\n\t\t\tif (sizeof(opt_name) == name_pos)\n\t\t\t\tfatal (\"Error in config file: \"\n\t\t\t\t       \"option name on line %d is too long!\", line);\n\t\t\topt_name[name_pos++] = ch;\n\t\t\tesc = 0;\n\t\t}\n\t}\n\n\tif (name_pos || value_pos)\n\t\tfatal (\"Parse error at the end of the config file (need end of \"\n\t\t\t\t\"line?)!\");\n\n\tsanity_check ();\n\n\tfclose (file);\n}\n\nvoid options_free ()\n{\n\tint i, ix;\n\n\tfor (i = 0; i < options_num; i++) {\n\t\tif (options[i].type == OPTION_STR && options[i].value.str) {\n\t\t\tfree (options[i].value.str);\n\t\t\toptions[i].value.str = NULL;\n\t\t}\n\t\telse if (options[i].type == OPTION_LIST) {\n\t\t\tlists_strs_free (options[i].value.list);\n\t\t\toptions[i].value.list = NULL;\n\t\t\tfor (ix = 0; ix < options[i].count; ix += 1)\n\t\t\t\tfree (((char **) options[i].constraints)[ix]);\n\t\t}\n\t\telse if (options[i].type == OPTION_SYMB)\n\t\t\toptions[i].value.str = NULL;\n\t\tif (options[i].type & (OPTION_STR | OPTION_SYMB)) {\n\t\t\tif (options[i].check != check_length) {\n\t\t\t\tfor (ix = 0; ix < options[i].count; ix += 1)\n\t\t\t\t\tfree (((char **) options[i].constraints)[ix]);\n\t\t\t}\n\t\t}\n\t\toptions[i].check = check_true;\n\t\toptions[i].count = 0;\n\t\tif (options[i].constraints)\n\t\t\tfree (options[i].constraints);\n\t\toptions[i].constraints = NULL;\n\t}\n}\n\nint options_get_int (const char *name)\n{\n\tint i = find_option (name, OPTION_INT);\n\n\tif (i == -1)\n\t\tfatal (\"Tried to get wrong option '%s'!\", name);\n\n\treturn options[i].value.num;\n}\n\nbool options_get_bool (const char *name)\n{\n\tint i = find_option (name, OPTION_BOOL);\n\n\tif (i == -1)\n\t\tfatal (\"Tried to get wrong option '%s'!\", name);\n\n\treturn options[i].value.boolean;\n}\n\nchar *options_get_str (const char *name)\n{\n\tint i = find_option (name, OPTION_STR);\n\n\tif (i == -1)\n\t\tfatal (\"Tried to get wrong option '%s'!\", name);\n\n\treturn options[i].value.str;\n}\n\nchar *options_get_symb (const char *name)\n{\n\tint i = find_option (name, OPTION_SYMB);\n\n\tif (i == -1)\n\t\tfatal (\"Tried to get wrong option '%s'!\", name);\n\n\treturn options[i].value.str;\n}\n\nlists_t_strs *options_get_list (const char *name)\n{\n\tint i = find_option (name, OPTION_LIST);\n\n\tif (i == -1)\n\t\tfatal (\"Tried to get wrong option '%s'!\", name);\n\n\treturn options[i].value.list;\n}\n\nenum option_type options_get_type (const char *name)\n{\n\tint i = find_option (name, OPTION_ANY);\n\n\tif (i == -1)\n\t\treturn OPTION_FREE;\n\n\treturn options[i].type;\n}\n"
  },
  {
    "path": "options.h",
    "content": "#ifndef OPTIONS_H\n#define OPTIONS_H\n\n#include \"lists.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nenum option_type\n{\n\tOPTION_FREE = 0,\n\tOPTION_INT  = 1,\n\tOPTION_BOOL = 2,\n\tOPTION_STR  = 4,\n\tOPTION_SYMB = 8,\n\tOPTION_LIST = 16,\n\tOPTION_ANY  = 255\n};\n\nint options_get_int (const char *name);\nbool options_get_bool (const char *name);\nchar *options_get_str (const char *name);\nchar *options_get_symb (const char *name);\nlists_t_strs *options_get_list (const char *name);\nvoid options_set_int (const char *name, const int value);\nvoid options_set_bool (const char *name, const bool value);\nvoid options_set_str (const char *name, const char *value);\nvoid options_set_symb (const char *name, const char *value);\nvoid options_set_list (const char *name, const char *value, bool append);\nbool options_set_pair (const char *name, const char *value, bool append);\nvoid options_init ();\nvoid options_parse (const char *config_file);\nvoid options_free ();\nvoid options_ignore_config (const char *name);\nint options_check_str (const char *name, const char *val);\nint options_check_symb (const char *name, const char *val);\nint options_check_int (const char *name, const int val);\nint options_check_bool (const char *name, const bool val);\nint options_check_list (const char *name, const char *val);\nint options_was_defaulted (const char *name);\nenum option_type options_get_type (const char *name);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "oss.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2003 - 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <sys/ioctl.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <fcntl.h>\n#include <errno.h>\n#include <string.h>\n#include <strings.h>\n#include <unistd.h>\n#include <assert.h>\n\n#ifdef HAVE_SYS_SOUNDCARD_H\n# include <sys/soundcard.h>\n#else\n# include <soundcard.h>\n#endif\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"audio.h\"\n#include \"log.h\"\n#include \"options.h\"\n\n#if OSS_VERSION >= 0x40000 || SOUND_VERSION >= 0x40000\n#define OSSv4_MIXER\n#else\n#define OSSv3_MIXER\n#endif\n\nstatic bool started = false;\nstatic int volatile dsp_fd = -1;\n#ifdef OSSv3_MIXER\nstatic int mixer_fd = -1;\nstatic int mixer_channel1 = -1;\nstatic int mixer_channel2 = -1;\nstatic int mixer_channel_current;\n#endif\n\nstatic struct sound_params params = { 0, 0, 0 };\n\nstatic const struct {\n\tconst char *name;\n\tconst int num;\n} mixer_channels[] = {\n\t{ \"pcm\", SOUND_MIXER_PCM },\n\t{ \"master\", SOUND_MIXER_VOLUME },\n\t{ \"speaker\", SOUND_MIXER_SPEAKER }\n};\n\n#define MIXER_CHANNELS_NUM\t(ARRAY_SIZE(mixer_channels))\n\nstatic int open_dev ()\n{\n\tif ((dsp_fd = open (options_get_str (\"OSSDevice\"), O_WRONLY)) == -1) {\n\t\tchar *err = xstrerror (errno);\n\t\terror (\"Can't open %s: %s\", options_get_str (\"OSSDevice\"), err);\n\t\tfree (err);\n\t\treturn 0;\n\t}\n\n\tlogit (\"Audio device opened\");\n\n\treturn 1;\n}\n\n/* Fill caps with the device capabilities.  Return 0 on error. */\nstatic int set_capabilities (struct output_driver_caps *caps)\n{\n\tint format_mask;\n\n\tif (!open_dev ()) {\n\t\terror (\"Can't open the device.\");\n\t\treturn 0;\n\t}\n\n\tif (ioctl (dsp_fd, SNDCTL_DSP_GETFMTS, &format_mask) == -1) {\n\t\terror_errno (\"Can't get supported audio formats\", errno);\n\t\tclose (dsp_fd);\n\t\treturn 0;\n\t}\n\n\tcaps->formats = 0;\n\tif (format_mask & AFMT_S8)\n\t\tcaps->formats |= SFMT_S8;\n\tif (format_mask & AFMT_U8)\n\t\tcaps->formats |= SFMT_U8;\n\n\tif (format_mask & AFMT_S16_LE)\n\t\tcaps->formats |= SFMT_S16 | SFMT_LE;\n\tif (format_mask & AFMT_S16_BE)\n\t\tcaps->formats |= SFMT_S16 | SFMT_BE;\n\n#if defined(AFMT_S32_LE) && defined(AFMT_S32_BE)\n\tif (format_mask & AFMT_S32_LE)\n\t\tcaps->formats |= SFMT_S32 | SFMT_LE;\n\tif (format_mask & AFMT_S32_BE)\n\t\tcaps->formats |= SFMT_S32 | SFMT_BE;\n#endif\n\n\tif (!caps->formats) {\n\t\t/* Workaround for vmix which lies that it doesn't support any\n\t\t * format. */\n\t\terror (\"The driver claims that no format known to me is \"\n\t\t       \"supported. I will assume that SFMT_S8 and \"\n\t\t       \"SFMT_S16 (native endian) are supported.\");\n\t\tcaps->formats = SFMT_S8 | SFMT_S16 | SFMT_NE;\n\t}\n\n\tcaps->min_channels = caps->max_channels = 1;\n\tif (ioctl (dsp_fd, SNDCTL_DSP_CHANNELS, &caps->min_channels)) {\n\t\terror_errno (\"Can't set number of channels\", errno);\n\t\tclose (dsp_fd);\n\t\treturn 0;\n\t}\n\n\tclose (dsp_fd);\n\tif (!open_dev ()) {\n\t\terror (\"Can't open the device.\");\n\t\treturn 0;\n\t}\n\n\tif (caps->min_channels != 1)\n\t\tcaps->min_channels = 2;\n\tcaps->max_channels = 2;\n\tif (ioctl (dsp_fd, SNDCTL_DSP_CHANNELS, &caps->max_channels)) {\n\t\terror_errno (\"Can't set number of channels\", errno);\n\t\tclose (dsp_fd);\n\t\treturn 0;\n\t}\n\n\tif (caps->max_channels != 2) {\n\t\tif (caps->min_channels == 2) {\n\t\t\terror (\"Can't get any supported number of channels.\");\n\t\t\tclose (dsp_fd);\n\t\t\treturn 0;\n\t\t}\n\t\tcaps->max_channels = 1;\n\t}\n\n\tclose (dsp_fd);\n\n\treturn 1;\n}\n\n/* Get PCM volume.  Return -1 on error. */\nstatic int oss_read_mixer ()\n{\n\tint vol;\n\n\tif (!started)\n\t\treturn -1;\n\n#ifdef OSSv3_MIXER\n\tif (mixer_fd != -1 && mixer_channel_current != -1) {\n\t\tif (ioctl (mixer_fd, MIXER_READ(mixer_channel_current), &vol) == -1)\n#else\n\tif (dsp_fd != -1) {\n\t\tif (ioctl (dsp_fd, SNDCTL_DSP_GETPLAYVOL, &vol) == -1)\n#endif\n\t\t{\n\t\t\terror (\"Can't read from mixer\");\n\t\t}\n\t\telse {\n\t\t\t/* Average between left and right */\n\t\t\treturn ((vol & 0xFF) + ((vol >> 8) & 0xFF)) / 2;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\nstatic int oss_mixer_name_to_channel (const char *name)\n{\n\tsize_t ix;\n\n\tfor (ix = 0; ix < MIXER_CHANNELS_NUM; ix += 1) {\n\t\tif (!strcasecmp (mixer_channels[ix].name, name))\n\t\t\treturn ix;\n\t}\n\n\treturn -1;\n}\n\nstatic int oss_init (struct output_driver_caps *caps)\n{\n#ifdef OSSv3_MIXER\n\t/* Open the mixer device */\n\tmixer_fd = open (options_get_str (\"OSSMixerDevice\"), O_RDWR);\n\tif (mixer_fd == -1) {\n\t\tchar *err = xstrerror (errno);\n\t\terror (\"Can't open mixer device %s: %s\",\n\t\t        options_get_str (\"OSSMixerDevice\"), err);\n\t\tfree (err);\n\t}\n\telse {\n\t\tmixer_channel1 = oss_mixer_name_to_channel (\n\t\t\t\toptions_get_symb (\"OSSMixerChannel1\"));\n\t\tmixer_channel2 = oss_mixer_name_to_channel (\n\t\t\t\toptions_get_symb (\"OSSMixerChannel2\"));\n\n\t\tif (mixer_channel1 == -1)\n\t\t\tfatal (\"Bad first OSS mixer channel!\");\n\t\tif (mixer_channel2 == -1)\n\t\t\tfatal (\"Bad second OSS mixer channel!\");\n\n\t\t/* test mixer channels */\n\t\tmixer_channel_current = mixer_channel1;\n\t\tif (oss_read_mixer () == -1)\n\t\t\tmixer_channel1 = -1;\n\n\t\tmixer_channel_current = mixer_channel2;\n\t\tif (oss_read_mixer () == -1)\n\t\t\tmixer_channel2 = -1;\n\n\t\tif (mixer_channel1 != -1)\n\t\t\tmixer_channel_current = mixer_channel1;\n\t}\n#endif\n\n\treturn set_capabilities (caps);\n}\n\nstatic void oss_shutdown ()\n{\n#ifdef OSSv3_MIXER\n\tif (mixer_fd != -1) {\n\t\tclose (mixer_fd);\n\t\tmixer_fd = -1;\n\t}\n#endif\n}\n\nstatic void oss_close ()\n{\n\tif (dsp_fd != -1) {\n\t\tclose (dsp_fd);\n\t\tdsp_fd = -1;\n\t\tlogit (\"Audio device closed\");\n\t}\n\n\tstarted = false;\n\tparams.channels = 0;\n\tparams.rate = 0;\n\tparams.fmt = 0;\n}\n\n/* Return 0 on error. */\nstatic int oss_set_params ()\n{\n\tint req_format;\n\tint req_channels;\n\tchar fmt_name[SFMT_STR_MAX];\n\n\t/* Set format */\n\tswitch (params.fmt & SFMT_MASK_FORMAT) {\n\t\tcase SFMT_S8:\n\t\t\treq_format = AFMT_S8;\n\t\t\tbreak;\n\t\tcase SFMT_U8:\n\t\t\treq_format = AFMT_U8;\n\t\t\tbreak;\n\t\tcase SFMT_S16:\n\t\t\tif (params.fmt & SFMT_LE)\n\t\t\t\treq_format = AFMT_S16_LE;\n\t\t\telse\n\t\t\t\treq_format = AFMT_S16_BE;\n\t\t\tbreak;\n#if defined(AFMT_S32_LE) && defined(AFMT_S32_BE)\n\t\tcase SFMT_S32:\n\t\t\tif (params.fmt & SFMT_LE)\n\t\t\t\treq_format = AFMT_S32_LE;\n\t\t\telse\n\t\t\t\treq_format = AFMT_S32_BE;\n\t\t\tbreak;\n#endif\n\t\tdefault:\n\t\t\terror (\"Format %s is not supported by the device\",\n\t\t\t        sfmt_str (params.fmt, fmt_name, sizeof (fmt_name)));\n\t\t\treturn 0;\n\t}\n\n\tif (ioctl (dsp_fd, SNDCTL_DSP_SETFMT, &req_format) == -1) {\n\t\terror_errno (\"Can't set audio format\", errno);\n\t\toss_close ();\n\t\treturn 0;\n\t}\n\n\t/* Set number of channels */\n\treq_channels = params.channels;\n\tif (ioctl (dsp_fd, SNDCTL_DSP_CHANNELS, &req_channels) == -1) {\n\t\tchar *err = xstrerror (errno);\n\t\terror (\"Can't set number of channels to %d: %s\",\n\t\t        params.channels, err);\n\t\tfree (err);\n\t\toss_close ();\n\t\treturn 0;\n\t}\n\tif (params.channels != req_channels) {\n\t\terror (\"Can't set number of channels to %d, \"\n\t\t       \"device doesn't support this value\",\n\t\t\t\tparams.channels);\n\t\toss_close ();\n\t\treturn 0;\n\t}\n\n\t/* Set sample rate */\n\tif (ioctl (dsp_fd, SNDCTL_DSP_SPEED, &params.rate) == -1) {\n\t\tchar *err = xstrerror (errno);\n\t\terror (\"Can't set sampling rate to %d: %s\", params.rate, err);\n\t\tfree (err);\n\t\toss_close ();\n\t\treturn 0;\n\t}\n\n\tlogit (\"Audio parameters set to: %s, %d channels, %dHz\",\n\t        sfmt_str (params.fmt, fmt_name, sizeof (fmt_name)),\n\t        params.channels, params.rate);\n\n\treturn 1;\n}\n\n/* Return 0 on failure. */\nstatic int oss_open (struct sound_params *sound_params)\n{\n\tparams = *sound_params;\n\n\tif (!open_dev ())\n\t\treturn 0;\n\n\tif (!oss_set_params ()) {\n\t\toss_close ();\n\t\treturn 0;\n\t}\n\n\tstarted = true;\n\n\treturn 1;\n}\n\n/* Return -1 on error, or number of bytes played when okay. */\nstatic int oss_play (const char *buff, const size_t size)\n{\n\tssize_t ssize = (ssize_t) size;\n\tssize_t count = 0;\n\n\tif (dsp_fd == -1) {\n\t\terror (\"Can't play: audio device isn't opened!\");\n\t\treturn -1;\n\t}\n\n\twhile (count < ssize) {\n\t\tssize_t rc;\n\n\t\trc = write (dsp_fd, buff + count, ssize - count);\n\t\tif (rc == -1) {\n\t\t\terror_errno (\"Error writing pcm sound\", errno);\n\t\t\treturn -1;\n\t\t}\n\n\t\tcount += rc;\n\t}\n\n\treturn count;\n}\n\n/* Set PCM volume */\nstatic void oss_set_mixer (int vol)\n{\n#ifdef OSSv3_MIXER\n\tif (mixer_fd != -1)\n#else\n\tif (dsp_fd != -1)\n#endif\n\t{\n\t\tvol = CLAMP(0, vol, 100);\n\t\tvol |= vol << 8;\n#ifdef OSSv3_MIXER\n\t\tif (ioctl (mixer_fd, MIXER_WRITE(mixer_channel_current), &vol) == -1)\n#else\n\t\tif (ioctl (dsp_fd, SNDCTL_DSP_SETPLAYVOL, &vol) == -1)\n#endif\n\t\t{\n\t\t\terror (\"Can't set mixer: ioctl() failed\");\n\t\t}\n\t}\n}\n\n/* Return number of bytes in device buffer. */\nstatic int oss_get_buff_fill ()\n{\n\taudio_buf_info buff_info;\n\n\tif (dsp_fd == -1)\n\t\treturn 0;\n\n\tif (ioctl (dsp_fd, SNDCTL_DSP_GETOSPACE, &buff_info) == -1) {\n\t\terror (\"SNDCTL_DSP_GETOSPACE failed\");\n\t\treturn 0;\n\t}\n\n\treturn (buff_info.fragstotal * buff_info.fragsize) - buff_info.bytes;\n}\n\n/* Reset device buffer and stop playing immediately.  Return 0 on error. */\nstatic int oss_reset ()\n{\n\tif (dsp_fd == -1) {\n\t\tlogit (\"Reset when audio device is not opened\");\n\t\treturn 0;\n\t}\n\n\tlogit (\"Resetting audio device\");\n\n\tif (ioctl (dsp_fd, SNDCTL_DSP_RESET, NULL) == -1)\n\t\terror (\"Resetting audio device failed\");\n\tclose (dsp_fd);\n\tdsp_fd = -1;\n\tif (!open_dev () || !oss_set_params ()) {\n\t\terror (\"Failed to open audio device after resetting\");\n\t\treturn 0;\n\t}\n\n\treturn 1;\n}\n\nstatic void oss_toggle_mixer_channel ()\n{\n#ifdef OSSv3_MIXER\n\tif (mixer_channel_current == mixer_channel1 && mixer_channel2 != -1)\n\t\tmixer_channel_current = mixer_channel2;\n\telse if (mixer_channel1 != -1)\n\t\tmixer_channel_current = mixer_channel1;\n#endif\n}\n\nstatic char *oss_get_mixer_channel_name ()\n{\n#ifdef OSSv3_MIXER\n\tif (mixer_channel_current == mixer_channel1)\n\t\treturn xstrdup (options_get_symb (\"OSSMixerChannel1\"));\n\treturn xstrdup (options_get_symb (\"OSSMixerChannel2\"));\n#else\n\treturn xstrdup (\"moc\");\n#endif\n}\n\nstatic int oss_get_rate ()\n{\n\treturn params.rate;\n}\n\nvoid oss_funcs (struct hw_funcs *funcs)\n{\n\tfuncs->init = oss_init;\n\tfuncs->shutdown = oss_shutdown;\n\tfuncs->open = oss_open;\n\tfuncs->close = oss_close;\n\tfuncs->play = oss_play;\n\tfuncs->read_mixer = oss_read_mixer;\n\tfuncs->set_mixer = oss_set_mixer;\n\tfuncs->get_buff_fill = oss_get_buff_fill;\n\tfuncs->reset = oss_reset;\n\tfuncs->get_rate = oss_get_rate;\n\tfuncs->toggle_mixer_channel = oss_toggle_mixer_channel;\n\tfuncs->get_mixer_channel_name = oss_get_mixer_channel_name;\n}\n"
  },
  {
    "path": "oss.h",
    "content": "#ifndef OSS_H\n#define OSS_H\n\n#include \"audio.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid oss_funcs (struct hw_funcs *funcs);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "out_buf.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004,2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n/* Defining OUT_TEST causes the raw audio samples to be written\n * to the file 'out_test' in the current directory for debugging. */\n/*#define OUT_TEST*/\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <assert.h>\n#include <pthread.h>\n#include <string.h>\n#include <errno.h>\n\n#ifdef OUT_TEST\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <fcntl.h>\n#endif\n\n/*#define DEBUG*/\n\n#include \"common.h\"\n#include \"audio.h\"\n#include \"log.h\"\n#include \"fifo_buf.h\"\n#include \"out_buf.h\"\n#include \"options.h\"\n\nstruct out_buf\n{\n\tstruct fifo_buf *buf;\n\tpthread_mutex_t\tmutex;\n\tpthread_t tid;\t/* Thread id of the reading thread. */\n\n\t/* Signals. */\n\tpthread_cond_t play_cond;\t/* Something was written to the buffer. */\n\tpthread_cond_t ready_cond;\t/* There is some space in the buffer. */\n\n\t/* Optional callback called when there is some free space in\n\t * the buffer. */\n\tout_buf_free_callback *free_callback;\n\n\t/* State flags of the buffer. */\n\tint pause;\n\tint exit;\t/* Exit when the buffer is empty. */\n\tint stop;\t/* Don't play anything. */\n\n\tint reset_dev;\t/* Request to the reading thread to reset the audio\n\t\t\t   device. */\n\n\tfloat time;\t/* Time of played sound. */\n\tint hardware_buf_fill;\t/* How the sound card buffer is filled. */\n\n\tint read_thread_waiting; /* Is the read thread waiting for data? */\n};\n\n/* Don't play more than this value (in seconds) in one audio_play().\n * This prevents locking. */\n#define AUDIO_MAX_PLAY\t\t0.1\n#define AUDIO_MAX_PLAY_BYTES\t32768\n\n#ifdef OUT_TEST\nstatic int fd;\n#endif\n\nstatic void set_realtime_prio ()\n{\n#ifdef HAVE_SCHED_GET_PRIORITY_MAX\n\tint rc;\n\n\tif (options_get_bool(\"UseRealtimePriority\")) {\n\t\tstruct sched_param param;\n\n\t\tparam.sched_priority = sched_get_priority_max(SCHED_RR);\n\t\trc = pthread_setschedparam (pthread_self (), SCHED_RR, &param);\n\t\tif (rc != 0)\n\t\t\tlog_errno (\"Can't set realtime priority\", rc);\n\t}\n#else\n\tlogit (\"No sched_get_priority_max() function: \"\n\t                  \"realtime priority not used.\");\n#endif\n}\n\n/* Reading thread of the buffer. */\nstatic void *read_thread (void *arg)\n{\n\tstruct out_buf *buf = (struct out_buf *)arg;\n\tint audio_dev_closed = 0;\n\n\tlogit (\"entering output buffer thread\");\n\n\tset_realtime_prio ();\n\n\tLOCK (buf->mutex);\n\n\twhile (1) {\n\t\tint played = 0;\n\t\tchar play_buf[AUDIO_MAX_PLAY_BYTES];\n\t\tint play_buf_fill;\n\t\tint play_buf_pos = 0;\n\n\t\tif (buf->reset_dev && !audio_dev_closed) {\n\t\t\taudio_reset ();\n\t\t\tbuf->reset_dev = 0;\n\t\t}\n\n\t\tif (buf->stop)\n\t\t\tfifo_buf_clear (buf->buf);\n\n\t\tif (buf->free_callback) {\n\t\t\t/* unlock the mutex to make calls to out_buf functions\n\t\t\t * possible in the callback */\n\t\t\tUNLOCK (buf->mutex);\n\t\t\tbuf->free_callback ();\n\t\t\tLOCK (buf->mutex);\n\t\t}\n\n\t\tdebug (\"sending the signal\");\n\t\tpthread_cond_broadcast (&buf->ready_cond);\n\n\t\tif ((fifo_buf_get_fill(buf->buf) == 0 || buf->pause || buf->stop)\n\t\t\t\t&& !buf->exit) {\n\t\t\tif (buf->pause && !audio_dev_closed) {\n\t\t\t\tlogit (\"Closing the device due to pause\");\n\t\t\t\taudio_close ();\n\t\t\t\taudio_dev_closed = 1;\n\t\t\t}\n\n\t\t\tdebug (\"waiting for something in the buffer\");\n\t\t\tbuf->read_thread_waiting = 1;\n\t\t\tpthread_cond_wait (&buf->play_cond, &buf->mutex);\n\t\t\tdebug (\"something appeared in the buffer\");\n\t\t}\n\n\t\tbuf->read_thread_waiting = 0;\n\n\t\tif (audio_dev_closed && !buf->pause) {\n\t\t\tlogit (\"Opening the device again after pause\");\n\t\t\tif (!audio_open(NULL)) {\n\t\t\t\tlogit (\"Can't reopen the device! sleeping...\");\n\t\t\t\txsleep (1, 1); /* there is no way to exit :( */\n\t\t\t}\n\t\t\telse\n\t\t\t\taudio_dev_closed = 0;\n\t\t}\n\n\t\tif (fifo_buf_get_fill(buf->buf) == 0) {\n\t\t\tif (buf->exit) {\n\t\t\t\tlogit (\"exit\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tlogit (\"buffer empty\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (buf->pause) {\n\t\t\tlogit (\"paused\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (buf->stop) {\n\t\t\tlogit (\"stopped\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!audio_dev_closed) {\n\t\t\tint audio_bpf;\n\t\t\tsize_t play_buf_frames;\n\n\t\t\taudio_bpf = audio_get_bpf();\n\t\t\tplay_buf_frames = MIN(audio_get_bps() * AUDIO_MAX_PLAY,\n\t\t\t                      AUDIO_MAX_PLAY_BYTES) / audio_bpf;\n\t\t\tplay_buf_fill = fifo_buf_get(buf->buf, play_buf,\n\t\t\t                             play_buf_frames * audio_bpf);\n\t\t\tUNLOCK (buf->mutex);\n\n\t\t\tdebug (\"playing %d bytes\", play_buf_fill);\n\n\t\t\twhile (play_buf_pos < play_buf_fill) {\n\t\t\t\tplayed = audio_send_pcm (\n\t\t\t\t\t\tplay_buf + play_buf_pos,\n\t\t\t\t\t\tplay_buf_fill - play_buf_pos);\n\n#ifdef OUT_TEST\n\t\t\t\twrite (fd, play_buf + play_buf_pos, played);\n#endif\n\n\t\t\t\tplay_buf_pos += played;\n\t\t\t}\n\n\t\t\t/*logit (\"done sending PCM\");*/\n\n\t\t\tLOCK (buf->mutex);\n\n\t\t\t/* Update time */\n\t\t\tif (play_buf_fill && audio_get_bps())\n\t\t\t\tbuf->time += play_buf_fill / (float)audio_get_bps();\n\t\t\tbuf->hardware_buf_fill = audio_get_buf_fill();\n\t\t}\n\t}\n\n\tUNLOCK (buf->mutex);\n\n\tlogit (\"exiting\");\n\n\treturn NULL;\n}\n\n/* Allocate and initialize the buf structure, size is the buffer size. */\nstruct out_buf *out_buf_new (int size)\n{\n\tint rc;\n\tstruct out_buf *buf;\n\n\tassert (size > 0);\n\n\tbuf = xmalloc (sizeof (struct out_buf));\n\n\tbuf->buf = fifo_buf_new (size);\n\tbuf->exit = 0;\n\tbuf->pause = 0;\n\tbuf->stop = 0;\n\tbuf->time = 0.0;\n\tbuf->reset_dev = 0;\n\tbuf->hardware_buf_fill = 0;\n\tbuf->read_thread_waiting = 0;\n\tbuf->free_callback = NULL;\n\n\tpthread_mutex_init (&buf->mutex, NULL);\n\tpthread_cond_init (&buf->play_cond, NULL);\n\tpthread_cond_init (&buf->ready_cond, NULL);\n\n#ifdef OUT_TEST\n\tfd = open (\"out_test\", O_CREAT | O_TRUNC | O_WRONLY, 0600);\n#endif\n\n\trc = pthread_create (&buf->tid, NULL, read_thread, buf);\n\tif (rc != 0)\n\t\tfatal (\"Can't create buffer thread: %s\", xstrerror (rc));\n\n\treturn buf;\n}\n\n/* Wait for empty buffer, end playing, free resources allocated for the buf\n * structure.  Can be used only if nothing is played. */\nvoid out_buf_free (struct out_buf *buf)\n{\n\tint rc;\n\n\tassert (buf != NULL);\n\n\tLOCK (buf->mutex);\n\tbuf->exit = 1;\n\tpthread_cond_signal (&buf->play_cond);\n\tUNLOCK (buf->mutex);\n\n\tpthread_join (buf->tid, NULL);\n\n\t/* Let other threads using this buffer know that the state of the\n\t * buffer has changed. */\n\tLOCK (buf->mutex);\n\tfifo_buf_clear (buf->buf);\n\tpthread_cond_broadcast (&buf->ready_cond);\n\tUNLOCK (buf->mutex);\n\n\tfifo_buf_free (buf->buf);\n\tbuf->buf = NULL;\n\trc = pthread_mutex_destroy (&buf->mutex);\n\tif (rc != 0)\n\t\tlog_errno (\"Destroying buffer mutex failed\", rc);\n\trc = pthread_cond_destroy (&buf->play_cond);\n\tif (rc != 0)\n\t\tlog_errno (\"Destroying buffer play condition failed\", rc);\n\trc = pthread_cond_destroy (&buf->ready_cond);\n\tif (rc != 0)\n\t\tlog_errno (\"Destroying buffer ready condition failed\", rc);\n\n\tfree (buf);\n\n\tlogit (\"buffer destroyed\");\n\n#ifdef OUT_TEST\n\tclose (fd);\n#endif\n}\n\n/* Put data at the end of the buffer, return 0 if nothing was put. */\nint out_buf_put (struct out_buf *buf, const char *data, int size)\n{\n\tint pos = 0;\n\n\t/*logit (\"got %d bytes to play\", size);*/\n\n\twhile (size) {\n\t\tint written;\n\n\t\tLOCK (buf->mutex);\n\n\t\tif (fifo_buf_get_space(buf->buf) == 0 && !buf->stop) {\n\t\t\t/*logit (\"buffer full, waiting for the signal\");*/\n\t\t\tpthread_cond_wait (&buf->ready_cond, &buf->mutex);\n\t\t\t/*logit (\"buffer ready\");*/\n\t\t}\n\n\t\tif (buf->stop) {\n\t\t\tlogit (\"the buffer is stopped, refusing to write to the buffer\");\n\t\t\tUNLOCK (buf->mutex);\n\t\t\treturn 0;\n\t\t}\n\n\t\twritten = fifo_buf_put (buf->buf, data + pos, size);\n\n\t\tif (written) {\n\t\t\tpthread_cond_signal (&buf->play_cond);\n\t\t\tsize -= written;\n\t\t\tpos += written;\n\t\t}\n\n\t\tUNLOCK (buf->mutex);\n\t}\n\n\treturn 1;\n}\n\nvoid out_buf_pause (struct out_buf *buf)\n{\n\tLOCK (buf->mutex);\n\tbuf->pause = 1;\n\tbuf->reset_dev = 1;\n\tUNLOCK (buf->mutex);\n}\n\nvoid out_buf_unpause (struct out_buf *buf)\n{\n\tLOCK (buf->mutex);\n\tbuf->pause = 0;\n\tpthread_cond_signal (&buf->play_cond);\n\tUNLOCK (buf->mutex);\n}\n\n/* Stop playing, after that buffer will refuse to play anything and ignore data\n * sent by buf_put(). */\nvoid out_buf_stop (struct out_buf *buf)\n{\n\tlogit (\"stopping the buffer\");\n\tLOCK (buf->mutex);\n\tbuf->stop = 1;\n\tbuf->pause = 0;\n\tbuf->reset_dev = 1;\n\tlogit (\"sending signal\");\n\tpthread_cond_signal (&buf->play_cond);\n\tlogit (\"waiting for signal\");\n\tpthread_cond_wait (&buf->ready_cond, &buf->mutex);\n\tlogit (\"done\");\n\tUNLOCK (buf->mutex);\n}\n\n/* Reset the buffer state: this can by called ONLY when the buffer is stopped\n * and buf_put is not used! */\nvoid out_buf_reset (struct out_buf *buf)\n{\n\tlogit (\"resetting the buffer\");\n\n\tLOCK (buf->mutex);\n\tfifo_buf_clear (buf->buf);\n\tbuf->stop = 0;\n\tbuf->pause = 0;\n\tbuf->reset_dev = 0;\n\tbuf->hardware_buf_fill = 0;\n\tUNLOCK (buf->mutex);\n}\n\nvoid out_buf_time_set (struct out_buf *buf, const float time)\n{\n\tLOCK (buf->mutex);\n\tbuf->time = time;\n\tUNLOCK (buf->mutex);\n}\n\n/* Return the time in the audio which the user is currently hearing.\n * If unplayed samples still remain in the hardware buffer from the\n * previous audio then the value returned may be negative and it is\n * up to the caller to handle this appropriately in the context of\n * its own processing. */\nint out_buf_time_get (struct out_buf *buf)\n{\n\tint time;\n\tint bps = audio_get_bps ();\n\n\tLOCK (buf->mutex);\n\ttime = buf->time - (bps ? buf->hardware_buf_fill / (float)bps : 0);\n\tUNLOCK (buf->mutex);\n\n\treturn time;\n}\n\nvoid out_buf_set_free_callback (struct out_buf *buf,\n\t\tout_buf_free_callback callback)\n{\n\tassert (buf != NULL);\n\n\tLOCK (buf->mutex);\n\tbuf->free_callback = callback;\n\tUNLOCK (buf->mutex);\n}\n\nint out_buf_get_free (struct out_buf *buf)\n{\n\tint space;\n\n\tassert (buf != NULL);\n\n\tLOCK (buf->mutex);\n\tspace = fifo_buf_get_space (buf->buf);\n\tUNLOCK (buf->mutex);\n\n\treturn space;\n}\n\nint out_buf_get_fill (struct out_buf *buf)\n{\n\tint fill;\n\n\tassert (buf != NULL);\n\n\tLOCK (buf->mutex);\n\tfill = fifo_buf_get_fill (buf->buf);\n\tUNLOCK (buf->mutex);\n\n\treturn fill;\n}\n\n/* Wait until the read thread will stop and wait for data to come.\n * This makes sure that the audio device isn't used (of course only if you\n * don't put anything in the buffer). */\nvoid out_buf_wait (struct out_buf *buf)\n{\n\tassert (buf != NULL);\n\n\tlogit (\"Waiting for read thread to suspend...\");\n\n\tLOCK (buf->mutex);\n\twhile (!buf->read_thread_waiting) {\n\t\tdebug (\"waiting....\");\n\t\tpthread_cond_wait (&buf->ready_cond, &buf->mutex);\n\t}\n\tUNLOCK (buf->mutex);\n\n\tlogit (\"done\");\n}\n"
  },
  {
    "path": "out_buf.h",
    "content": "#ifndef BUF_H\n#define BUF_H\n\n#include \"fifo_buf.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef void out_buf_free_callback ();\n\nstruct out_buf;\n\nstruct out_buf *out_buf_new (int size);\nvoid out_buf_free (struct out_buf *buf);\nint out_buf_put (struct out_buf *buf, const char *data, int size);\nvoid out_buf_pause (struct out_buf *buf);\nvoid out_buf_unpause (struct out_buf *buf);\nvoid out_buf_stop (struct out_buf *buf);\nvoid out_buf_reset (struct out_buf *buf);\nvoid out_buf_time_set (struct out_buf *buf, const float time);\nint out_buf_time_get (struct out_buf *buf);\nvoid out_buf_set_free_callback (struct out_buf *buf,\n\t\tout_buf_free_callback callback);\nint out_buf_get_free (struct out_buf *buf);\nint out_buf_get_fill (struct out_buf *buf);\nvoid out_buf_wait (struct out_buf *buf);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "player.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004-2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <pthread.h>\n#include <string.h>\n#include <stdint.h>\n#include <errno.h>\n#include <assert.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"decoder.h\"\n#include \"audio.h\"\n#include \"out_buf.h\"\n#include \"server.h\"\n#include \"options.h\"\n#include \"player.h\"\n#include \"files.h\"\n#include \"playlist.h\"\n#include \"md5.h\"\n\n#define PCM_BUF_SIZE\t\t(36 * 1024)\n#define PREBUFFER_THRESHOLD\t(18 * 1024)\n\nenum request\n{\n\tREQ_NOTHING,\n\tREQ_SEEK,\n\tREQ_STOP,\n\tREQ_PAUSE,\n\tREQ_UNPAUSE\n};\n\nstruct bitrate_list_node\n{\n\tstruct bitrate_list_node *next;\n\tint time;\n\tint bitrate;\n};\n\n/* List of points where bitrate has changed. We use it to show bitrate at the\n * right time when playing, because the output buffer may be big and decoding\n * may be many seconds ahead of what the user can hear. */\nstruct bitrate_list\n{\n\tstruct bitrate_list_node *head;\n\tstruct bitrate_list_node *tail;\n\tpthread_mutex_t mtx;\n};\n\nstruct md5_data {\n\tbool okay;\n\tlong len;\n\tstruct md5_ctx ctx;\n};\n\nstruct precache\n{\n\tchar *file; /* the file to precache */\n\tchar buf[2 * PCM_BUF_SIZE]; /* PCM buffer with precached data */\n\tint buf_fill;\n\tint ok; /* 1 if precache succeed */\n\tstruct sound_params sound_params; /* of the sound in the buffer */\n\tstruct decoder *f; /* decoder functions for precached file */\n\tvoid *decoder_data;\n\tint running; /* if the precache thread is running */\n\tpthread_t tid; /* tid of the precache thread */\n\tstruct bitrate_list bitrate_list;\n\tint decoded_time; /* how much sound we decoded in seconds */\n};\n\nstruct precache precache;\n\n/* Request conditional and mutex. */\nstatic pthread_cond_t request_cond = PTHREAD_COND_INITIALIZER;\nstatic pthread_mutex_t request_cond_mtx = PTHREAD_MUTEX_INITIALIZER;\n\nstatic enum request request = REQ_NOTHING;\nstatic int req_seek;\n\n/* Source of the played stream tags. */\nstatic enum\n{\n\tTAGS_SOURCE_DECODER,\t/* tags from the stream (e.g., id3tags, vorbis comments) */\n\tTAGS_SOURCE_METADATA\t/* tags from icecast metadata */\n} tags_source;\n\n/* Tags of the currently played file. */\nstatic struct file_tags *curr_tags = NULL;\n\n/* Mutex for curr_tags and tags_source. */\nstatic pthread_mutex_t curr_tags_mtx = PTHREAD_MUTEX_INITIALIZER;\n\n/* Stream associated with the currently playing decoder. */\nstatic struct io_stream *decoder_stream = NULL;\nstatic pthread_mutex_t decoder_stream_mtx = PTHREAD_MUTEX_INITIALIZER;\n\nstatic int prebuffering = 0; /* are we prebuffering now? */\n\nstatic struct bitrate_list bitrate_list;\n\nstatic void bitrate_list_init (struct bitrate_list *b)\n{\n\tassert (b != NULL);\n\n\tb->head = NULL;\n\tb->tail = NULL;\n\tpthread_mutex_init (&b->mtx, NULL);\n}\n\nstatic void bitrate_list_empty (struct bitrate_list *b)\n{\n\tassert (b != NULL);\n\n\tLOCK (b->mtx);\n\tif (b->head) {\n\t\twhile (b->head) {\n\t\t\tstruct bitrate_list_node *t = b->head->next;\n\n\t\t\tfree (b->head);\n\t\t\tb->head = t;\n\t\t}\n\n\t\tb->tail = NULL;\n\t}\n\n\tdebug (\"Bitrate list elements removed.\");\n\n\tUNLOCK (b->mtx);\n}\n\nstatic void bitrate_list_destroy (struct bitrate_list *b)\n{\n\tint rc;\n\n\tassert (b != NULL);\n\n\tbitrate_list_empty (b);\n\n\trc = pthread_mutex_destroy (&b->mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy bitrate list mutex\", rc);\n}\n\nstatic void bitrate_list_add (struct bitrate_list *b, const int time,\n\t\tconst int bitrate)\n{\n\tassert (b != NULL);\n\n\tLOCK (b->mtx);\n\tif (!b->tail) {\n\t\tb->head = b->tail = (struct bitrate_list_node *)xmalloc (\n\t\t\t\tsizeof(struct bitrate_list_node));\n\t\tb->tail->next = NULL;\n\t\tb->tail->time = time;\n\t\tb->tail->bitrate = bitrate;\n\n\t\tdebug (\"Adding bitrate %d at time %d\", bitrate, time);\n\t}\n\telse if (b->tail->bitrate != bitrate && b->tail->time != time) {\n\t\tassert (b->tail->time < time);\n\n\t\tb->tail->next = (struct bitrate_list_node *)xmalloc (\n\t\t\t\tsizeof(struct bitrate_list_node));\n\t\tb->tail = b->tail->next;\n\t\tb->tail->next = NULL;\n\t\tb->tail->time = time;\n\t\tb->tail->bitrate = bitrate;\n\n\t\tdebug (\"Appending bitrate %d at time %d\", bitrate, time);\n\t}\n\telse if (b->tail->bitrate == bitrate)\n\t\tdebug (\"Not adding bitrate %d at time %d because the bitrate\"\n\t\t\t\t\" hasn't changed\", bitrate, time);\n\telse\n\t\tdebug (\"Not adding bitrate %d at time %d because it is for\"\n\t\t\t\t\" the same time as the last bitrate\", bitrate, time);\n\tUNLOCK (b->mtx);\n}\n\nstatic int bitrate_list_get (struct bitrate_list *b, const int time)\n{\n\tint bitrate = -1;\n\n\tassert (b != NULL);\n\n\tLOCK (b->mtx);\n\tif (b->head) {\n\t\twhile (b->head->next && b->head->next->time <= time) {\n\t\t\tstruct bitrate_list_node *o = b->head;\n\n\t\t\tb->head = o->next;\n\t\t\tdebug (\"Removing old bitrate %d for time %d\", o->bitrate, o->time);\n\t\t\tfree (o);\n\t\t}\n\n\t\tbitrate = b->head->bitrate /*b->head->time + 1000*/;\n\t\tdebug (\"Getting bitrate for time %d (%d)\", time, bitrate);\n\t}\n\telse {\n\t\tdebug (\"Getting bitrate for time %d (no bitrate information)\", time);\n\t\tbitrate = -1;\n\t}\n\tUNLOCK (b->mtx);\n\n\treturn bitrate;\n}\n\nstatic void update_time ()\n{\n\tstatic int last_time = 0;\n\tint ctime = audio_get_time ();\n\n\tif (ctime >= 0 && ctime != last_time) {\n\t\tlast_time = ctime;\n\t\tctime_change ();\n\t\tset_info_bitrate (bitrate_list_get (&bitrate_list, ctime));\n\t}\n}\n\nstatic void *precache_thread (void *data)\n{\n\tstruct precache *precache = (struct precache *)data;\n\tint decoded;\n\tstruct sound_params new_sound_params;\n\tstruct decoder_error err;\n\n\tprecache->buf_fill = 0;\n\tprecache->sound_params.channels = 0; /* mark that sound_params were not\n\t\t\t\t\t\tyet filled. */\n\tprecache->decoded_time = 0.0;\n\tprecache->f = get_decoder (precache->file);\n\tassert (precache->f != NULL);\n\n\tprecache->decoder_data = precache->f->open(precache->file);\n\tprecache->f->get_error(precache->decoder_data, &err);\n\tif (err.type != ERROR_OK) {\n\t\tlogit (\"Failed to open the file for precache: %s\", err.err);\n\t\tdecoder_error_clear (&err);\n\t\tprecache->f->close (precache->decoder_data);\n\t\treturn NULL;\n\t}\n\n\taudio_plist_set_time (precache->file,\n\t\t\tprecache->f->get_duration(precache->decoder_data));\n\n\t/* Stop at PCM_BUF_SIZE, because when we decode too much, there is no\n\t * place where we can put the data that doesn't fit into the buffer. */\n\twhile (precache->buf_fill < PCM_BUF_SIZE) {\n\t\tdecoded = precache->f->decode (precache->decoder_data,\n\t\t\t\tprecache->buf + precache->buf_fill,\n\t\t\t\tPCM_BUF_SIZE, &new_sound_params);\n\n\t\tif (!decoded) {\n\n\t\t\t/* EOF so fast? We can't pass this information\n\t\t\t * in precache, so give up. */\n\t\t\tlogit (\"EOF when precaching.\");\n\t\t\tprecache->f->close (precache->decoder_data);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tprecache->f->get_error (precache->decoder_data, &err);\n\n\t\tif (err.type == ERROR_FATAL) {\n\t\t\tlogit (\"Error reading file for precache: %s\", err.err);\n\t\t\tdecoder_error_clear (&err);\n\t\t\tprecache->f->close (precache->decoder_data);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif (!precache->sound_params.channels)\n\t\t\tprecache->sound_params = new_sound_params;\n\t\telse if (!sound_params_eq(precache->sound_params,\n\t\t\t\t\tnew_sound_params)) {\n\n\t\t\t/* There is no way to store sound with two different\n\t\t\t * parameters in the buffer, give up with\n\t\t\t * precaching. (this should never happen). */\n\t\t\tlogit (\"Sound parameters have changed when precaching.\");\n\t\t\tdecoder_error_clear (&err);\n\t\t\tprecache->f->close (precache->decoder_data);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tbitrate_list_add (&precache->bitrate_list,\n\t\t\t\tprecache->decoded_time,\n\t\t\t\tprecache->f->get_bitrate(\n\t\t\t\t\tprecache->decoder_data));\n\n\t\tprecache->buf_fill += decoded;\n\t\tprecache->decoded_time += decoded / (float)(sfmt_Bps(\n\t\t\t\t\tnew_sound_params.fmt) *\n\t\t\t\tnew_sound_params.rate *\n\t\t\t\tnew_sound_params.channels);\n\n\t\tif (err.type != ERROR_OK) {\n\t\t\tdecoder_error_clear (&err);\n\t\t\tbreak; /* Don't lose the error message */\n\t\t}\n\t}\n\n\tprecache->ok = 1;\n\tlogit (\"Successfully precached file (%d bytes)\", precache->buf_fill);\n\treturn NULL;\n}\n\nstatic void start_precache (struct precache *precache, const char *file)\n{\n\tint rc;\n\n\tassert (!precache->running);\n\tassert (file != NULL);\n\n\tprecache->file = xstrdup (file);\n\tbitrate_list_init (&precache->bitrate_list);\n\tlogit (\"Precaching file %s\", file);\n\tprecache->ok = 0;\n\trc = pthread_create (&precache->tid, NULL, precache_thread, precache);\n\tif (rc != 0)\n\t\tlog_errno (\"Could not run precache thread\", rc);\n\telse\n\t\tprecache->running = 1;\n}\n\nstatic void precache_wait (struct precache *precache)\n{\n\tint rc;\n\n\tif (precache->running) {\n\t\tdebug (\"Waiting for precache thread...\");\n\t\trc = pthread_join (precache->tid, NULL);\n\t\tif (rc != 0)\n\t\t\tfatal (\"pthread_join() for precache thread failed: %s\",\n\t\t\t        xstrerror (rc));\n\t\tprecache->running = 0;\n\t\tdebug (\"done\");\n\t}\n\telse\n\t\tdebug (\"Precache thread is not running\");\n}\n\nstatic void precache_reset (struct precache *precache)\n{\n\tassert (!precache->running);\n\tprecache->ok = 0;\n\tif (precache->file) {\n\t\tfree (precache->file);\n\t\tprecache->file = NULL;\n\t\tbitrate_list_destroy (&precache->bitrate_list);\n\t}\n}\n\nvoid player_init ()\n{\n\tprecache.file = NULL;\n\tprecache.running = 0;\n\tprecache.ok = 0;\n}\n\nstatic void show_tags (const struct file_tags *tags DEBUG_ONLY)\n{\n\tdebug (\"TAG[title]: %s\", tags->title ? tags->title : \"N/A\");\n\tdebug (\"TAG[album]: %s\", tags->album ? tags->album : \"N/A\");\n\tdebug (\"TAG[artist]: %s\", tags->artist ? tags->artist : \"N/A\");\n\tdebug (\"TAG[track]: %d\", tags->track);\n}\n\n/* Update tags if tags from the decoder or the stream are available. */\nstatic void update_tags (const struct decoder *f, void *decoder_data,\n\t\tstruct io_stream *s)\n{\n\tchar *stream_title = NULL;\n\tint tags_changed = 0;\n\tstruct file_tags *new_tags;\n\n\tnew_tags = tags_new ();\n\n\tLOCK (curr_tags_mtx);\n\tif (f->current_tags && f->current_tags(decoder_data, new_tags)\n\t\t\t&& new_tags->title) {\n\t\ttags_changed = 1;\n\t\ttags_copy (curr_tags, new_tags);\n\t\tlogit (\"Tags change from the decoder\");\n\t\ttags_source = TAGS_SOURCE_DECODER;\n\t\tshow_tags (curr_tags);\n\t}\n\telse if (s && (stream_title = io_get_metadata_title(s))) {\n\t\tif (curr_tags && curr_tags->title\n\t\t\t\t&& tags_source == TAGS_SOURCE_DECODER) {\n\t\t\tlogit (\"New IO stream tags, ignored because there are \"\n\t\t\t\t\t\"decoder tags present\");\n\t\t\tfree (stream_title);\n\t\t}\n\t\telse {\n\t\t\ttags_clear (curr_tags);\n\t\t\tcurr_tags->title = stream_title;\n\t\t\tshow_tags (curr_tags);\n\t\t\ttags_changed = 1;\n\t\t\tlogit (\"New IO stream tags\");\n\t\t\ttags_source = TAGS_SOURCE_METADATA;\n\t\t}\n\t}\n\n\tif (tags_changed)\n\t\ttags_change ();\n\n\ttags_free (new_tags);\n\n\tUNLOCK (curr_tags_mtx);\n}\n\n/* Called when some free space in the output buffer appears. */\nstatic void buf_free_cb ()\n{\n\tLOCK (request_cond_mtx);\n\tpthread_cond_broadcast (&request_cond);\n\tUNLOCK (request_cond_mtx);\n\n\tupdate_time ();\n}\n\n/* Decoder loop for already opened and probably running for some time decoder.\n * next_file will be precached at eof. */\nstatic void decode_loop (const struct decoder *f, void *decoder_data,\n\t\tconst char *next_file, struct out_buf *out_buf,\n\t\tstruct sound_params *sound_params, struct md5_data *md5,\n\t\tconst float already_decoded_sec)\n{\n\tbool eof = false;\n\tbool stopped = false;\n\tchar buf[PCM_BUF_SIZE];\n\tint decoded = 0;\n\tstruct sound_params new_sound_params;\n\tbool sound_params_change = false;\n\tfloat decode_time = already_decoded_sec; /* the position of the decoder\n\t                                            (in seconds) */\n\n\tout_buf_set_free_callback (out_buf, buf_free_cb);\n\n\tLOCK (curr_tags_mtx);\n\tcurr_tags = tags_new ();\n\tUNLOCK (curr_tags_mtx);\n\n\tif (f->get_stream) {\n\t\tLOCK (decoder_stream_mtx);\n\t\tdecoder_stream = f->get_stream (decoder_data);\n\t\tUNLOCK (decoder_stream_mtx);\n\t}\n\telse\n\t\tlogit (\"No get_stream() function\");\n\n\tstatus_msg (\"Playing...\");\n\n\twhile (1) {\n\t\tdebug (\"loop...\");\n\n\t\tLOCK (request_cond_mtx);\n\t\tif (!eof && !decoded) {\n\t\t\tstruct decoder_error err;\n\n\t\t\tUNLOCK (request_cond_mtx);\n\n\t\t\tif (decoder_stream && out_buf_get_fill(out_buf)\n\t\t\t\t\t< PREBUFFER_THRESHOLD) {\n\t\t\t\tprebuffering = 1;\n\t\t\t\tio_prebuffer (decoder_stream,\n\t\t\t\t\t\toptions_get_int(\"Prebuffering\")\n\t\t\t\t\t\t* 1024);\n\t\t\t\tprebuffering = 0;\n\t\t\t\tstatus_msg (\"Playing...\");\n\t\t\t}\n\n\t\t\tdecoded = f->decode (decoder_data, buf, sizeof(buf),\n\t\t\t\t\t&new_sound_params);\n\n\t\t\tif (decoded)\n\t\t\t\tdecode_time += decoded / (float)(sfmt_Bps(\n\t\t\t\t\t\t\tnew_sound_params.fmt) *\n\t\t\t\t\t\tnew_sound_params.rate *\n\t\t\t\t\t\tnew_sound_params.channels);\n\n\t\t\tf->get_error (decoder_data, &err);\n\t\t\tif (err.type != ERROR_OK) {\n\t\t\t\tmd5->okay = false;\n\t\t\t\tif (err.type != ERROR_STREAM ||\n\t\t\t\t    options_get_bool (\"ShowStreamErrors\"))\n\t\t\t\t\terror (\"%s\", err.err);\n\t\t\t\tdecoder_error_clear (&err);\n\t\t\t}\n\n\t\t\tif (!decoded) {\n\t\t\t\teof = true;\n\t\t\t\tlogit (\"EOF from decoder\");\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdebug (\"decoded %d bytes\", decoded);\n\t\t\t\tif (!sound_params_eq(new_sound_params, *sound_params))\n\t\t\t\t\tsound_params_change = true;\n\n\t\t\t\tbitrate_list_add (&bitrate_list, decode_time,\n\t\t\t\t\t\tf->get_bitrate(decoder_data));\n\t\t\t\tupdate_tags (f, decoder_data, decoder_stream);\n\t\t\t}\n\t\t}\n\n\t\t/* Wait, if there is no space in the buffer to put the decoded\n\t\t * data or EOF occurred and there is something in the buffer. */\n\t\telse if (decoded > out_buf_get_free(out_buf)\n\t\t\t\t\t|| (eof && out_buf_get_fill(out_buf))) {\n\t\t\tdebug (\"waiting...\");\n\t\t\tif (eof && !precache.file && next_file\n\t\t\t\t\t&& file_type(next_file) == F_SOUND\n\t\t\t\t\t&& options_get_bool(\"Precache\")\n\t\t\t\t\t&& options_get_bool(\"AutoNext\"))\n\t\t\t\tstart_precache (&precache, next_file);\n\t\t\tpthread_cond_wait (&request_cond, &request_cond_mtx);\n\t\t\tUNLOCK (request_cond_mtx);\n\t\t}\n\t\telse\n\t\t\tUNLOCK (request_cond_mtx);\n\n\t\t/* When clearing request, we must make sure, that another\n\t\t * request will not arrive at the moment, so we check if\n\t\t * the request has changed. */\n\t\tif (request == REQ_STOP) {\n\t\t\tlogit (\"stop\");\n\t\t\tstopped = true;\n\t\t\tmd5->okay = false;\n\t\t\tout_buf_stop (out_buf);\n\n\t\t\tLOCK (request_cond_mtx);\n\t\t\tif (request == REQ_STOP)\n\t\t\t\trequest = REQ_NOTHING;\n\t\t\tUNLOCK (request_cond_mtx);\n\n\t\t\tbreak;\n\t\t}\n\t\telse if (request == REQ_SEEK) {\n\t\t\tint decoder_seek;\n\n\t\t\tlogit (\"seeking\");\n\t\t\tmd5->okay = false;\n\t\t\treq_seek = MAX(0, req_seek);\n\t\t\tif ((decoder_seek = f->seek(decoder_data, req_seek)) == -1)\n\t\t\t\tlogit (\"error when seeking\");\n\t\t\telse {\n\t\t\t\tout_buf_stop (out_buf);\n\t\t\t\tout_buf_reset (out_buf);\n\t\t\t\tout_buf_time_set (out_buf, decoder_seek);\n\t\t\t\tbitrate_list_empty (&bitrate_list);\n\t\t\t\tdecode_time = decoder_seek;\n\t\t\t\teof = false;\n\t\t\t\tdecoded = 0;\n\t\t\t}\n\n\t\t\tLOCK (request_cond_mtx);\n\t\t\tif (request == REQ_SEEK)\n\t\t\t\trequest = REQ_NOTHING;\n\t\t\tUNLOCK (request_cond_mtx);\n\n\t\t}\n\t\telse if (!eof && decoded <= out_buf_get_free(out_buf)\n\t\t\t\t&& !sound_params_change) {\n\t\t\tdebug (\"putting into the buffer %d bytes\", decoded);\n#if !defined(NDEBUG) && defined(DEBUG)\n\t\t\tif (md5->okay) {\n\t\t\t\tmd5->len += decoded;\n\t\t\t\tmd5_process_bytes (buf, decoded, &md5->ctx);\n\t\t\t}\n#endif\n\t\t\taudio_send_buf (buf, decoded);\n\t\t\tdecoded = 0;\n\t\t}\n\t\telse if (!eof && sound_params_change\n\t\t\t\t&& out_buf_get_fill(out_buf) == 0) {\n\t\t\tlogit (\"Sound parameters have changed.\");\n\t\t\t*sound_params = new_sound_params;\n\t\t\tsound_params_change = false;\n\t\t\tset_info_channels (sound_params->channels);\n\t\t\tset_info_rate (sound_params->rate / 1000);\n\t\t\tout_buf_wait (out_buf);\n\t\t\tif (!audio_open(sound_params)) {\n\t\t\t\tmd5->okay = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\telse if (eof && out_buf_get_fill(out_buf) == 0) {\n\t\t\tlogit (\"played everything\");\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tstatus_msg (\"\");\n\n\tLOCK (decoder_stream_mtx);\n\tdecoder_stream = NULL;\n\tf->close (decoder_data);\n\tUNLOCK (decoder_stream_mtx);\n\n\tbitrate_list_destroy (&bitrate_list);\n\n\tLOCK (curr_tags_mtx);\n\tif (curr_tags) {\n\t\ttags_free (curr_tags);\n\t\tcurr_tags = NULL;\n\t}\n\tUNLOCK (curr_tags_mtx);\n\n\tout_buf_wait (out_buf);\n\n\tif (precache.ok && (stopped || !options_get_bool (\"AutoNext\"))) {\n\t\tprecache_wait (&precache);\n\t\tprecache.f->close (precache.decoder_data);\n\t\tprecache_reset (&precache);\n\t}\n}\n\n#if !defined(NDEBUG) && defined(DEBUG)\nstatic void log_md5_sum (const char *file, struct sound_params sound_params,\n                         const struct decoder *f, uint8_t *md5, long md5_len)\n{\n\tunsigned int ix, bps;\n\tchar md5sum[MD5_DIGEST_SIZE * 2 + 1], format;\n\tconst char *fn, *endian;\n\n\tfor (ix = 0; ix < MD5_DIGEST_SIZE; ix += 1)\n\t\tsprintf (&md5sum[ix * 2], \"%02x\", md5[ix]);\n\tmd5sum[MD5_DIGEST_SIZE * 2] = 0x00;\n\n\tswitch (sound_params.fmt & SFMT_MASK_FORMAT) {\n\tcase SFMT_S8:\n\tcase SFMT_S16:\n\tcase SFMT_S32:\n\t\tformat = 's';\n\t\tbreak;\n\tcase SFMT_U8:\n\tcase SFMT_U16:\n\tcase SFMT_U32:\n\t\tformat = 'u';\n\t\tbreak;\n\tcase SFMT_FLOAT:\n\t\tformat = 'f';\n\t\tbreak;\n\tdefault:\n\t\tdebug (\"Unknown sound format: 0x%04lx\", sound_params.fmt);\n\t\treturn;\n\t}\n\n\tbps = sfmt_Bps (sound_params.fmt) * 8;\n\n\tendian = \"\";\n\tif (format != 'f' && bps != 8) {\n\t\tif (sound_params.fmt & SFMT_LE)\n\t\t\tendian = \"le\";\n\t\telse if (sound_params.fmt & SFMT_BE)\n\t\t\tendian = \"be\";\n\t}\n\n\tfn = strrchr (file, '/');\n\tfn = fn ? fn + 1 : file;\n\tdebug (\"MD5(%s) = %s %ld %s %c%u%s %d %d\",\n\t        fn, md5sum, md5_len, get_decoder_name (f),\n\t        format, bps, endian,\n\t        sound_params.channels, sound_params.rate);\n}\n#endif\n\n/* Play a file (disk file) using the given decoder. next_file is precached. */\nstatic void play_file (const char *file, const struct decoder *f,\n\t\tconst char *next_file, struct out_buf *out_buf)\n{\n\tvoid *decoder_data;\n\tstruct sound_params sound_params = { 0, 0, 0 };\n\tfloat already_decoded_time;\n\tstruct md5_data md5;\n\n#if !defined(NDEBUG) && defined(DEBUG)\n\tmd5.okay = true;\n\tmd5.len = 0;\n\tmd5_init_ctx (&md5.ctx);\n#endif\n\n\tout_buf_reset (out_buf);\n\n\tprecache_wait (&precache);\n\n\tif (precache.ok && strcmp(precache.file, file)) {\n\t\tlogit (\"The precached file is not the file we want.\");\n\t\tprecache.f->close (precache.decoder_data);\n\t\tprecache_reset (&precache);\n\t}\n\n\tif (precache.ok && !strcmp(precache.file, file)) {\n\t\tstruct decoder_error err;\n\n\t\tlogit (\"Using precached file\");\n\n\t\tassert (f == precache.f);\n\n\t\tsound_params = precache.sound_params;\n\t\tdecoder_data = precache.decoder_data;\n\t\tset_info_channels (sound_params.channels);\n\t\tset_info_rate (sound_params.rate / 1000);\n\n\t\tif (!audio_open(&sound_params)) {\n\t\t\tmd5.okay = false;\n\t\t\tprecache.f->close (precache.decoder_data);\n\t\t\tprecache_reset (&precache);\n\t\t\treturn;\n\t\t}\n\n#if !defined(NDEBUG) && defined(DEBUG)\n\t\tmd5.len += precache.buf_fill;\n\t\tmd5_process_bytes (precache.buf, precache.buf_fill, &md5.ctx);\n#endif\n\n\t\taudio_send_buf (precache.buf, precache.buf_fill);\n\n\t\tprecache.f->get_error (precache.decoder_data, &err);\n\t\tif (err.type != ERROR_OK) {\n\t\t\tmd5.okay = false;\n\t\t\tif (err.type != ERROR_STREAM ||\n\t\t\t    options_get_bool (\"ShowStreamErrors\"))\n\t\t\t\terror (\"%s\", err.err);\n\t\t\tdecoder_error_clear (&err);\n\t\t}\n\n\t\talready_decoded_time = precache.decoded_time;\n\n\t\tif(f->get_avg_bitrate)\n\t\t\tset_info_avg_bitrate (f->get_avg_bitrate(decoder_data));\n\t\telse\n\t\t\tset_info_avg_bitrate (0);\n\n\t\tbitrate_list_init (&bitrate_list);\n\t\tbitrate_list.head = precache.bitrate_list.head;\n\t\tbitrate_list.tail = precache.bitrate_list.tail;\n\n\t\t/* don't free list elements when resetting precache */\n\t\tprecache.bitrate_list.head = NULL;\n\t\tprecache.bitrate_list.tail = NULL;\n\t}\n\telse {\n\t\tstruct decoder_error err;\n\n\t\tstatus_msg (\"Opening...\");\n\t\tdecoder_data = f->open(file);\n\t\tf->get_error (decoder_data, &err);\n\t\tif (err.type != ERROR_OK) {\n\t\t\tf->close (decoder_data);\n\t\t\tstatus_msg (\"\");\n\t\t\terror (\"%s\", err.err);\n\t\t\tdecoder_error_clear (&err);\n\t\t\tlogit (\"Can't open file, exiting\");\n\t\t\treturn;\n\t\t}\n\n\t\talready_decoded_time = 0.0;\n\t\tif (f->get_avg_bitrate)\n\t\t\tset_info_avg_bitrate (f->get_avg_bitrate(decoder_data));\n\t\tbitrate_list_init (&bitrate_list);\n\t}\n\n\taudio_plist_set_time (file, f->get_duration(decoder_data));\n\taudio_state_started_playing ();\n\tprecache_reset (&precache);\n\n\tdecode_loop (f, decoder_data, next_file, out_buf, &sound_params,\n\t\t\t&md5, already_decoded_time);\n\n#if !defined(NDEBUG) && defined(DEBUG)\n\tif (md5.okay) {\n\t\tuint8_t buf[MD5_DIGEST_SIZE];\n\n\t\tmd5_finish_ctx (&md5.ctx, buf);\n\t\tlog_md5_sum (file, sound_params, f, buf, md5.len);\n\t}\n#endif\n}\n\n/* Play the stream (global decoder_stream) using the given decoder. */\nstatic void play_stream (const struct decoder *f, struct out_buf *out_buf)\n{\n\tvoid *decoder_data;\n\tstruct sound_params sound_params = { 0, 0, 0 };\n\tstruct decoder_error err;\n\tstruct md5_data null_md5;\n\n\tnull_md5.okay = false;\n\tout_buf_reset (out_buf);\n\n\tassert (f->open_stream != NULL);\n\n\tdecoder_data = f->open_stream (decoder_stream);\n\tf->get_error (decoder_data, &err);\n\tif (err.type != ERROR_OK) {\n\t\tLOCK (decoder_stream_mtx);\n\t\tdecoder_stream = NULL;\n\t\tUNLOCK (decoder_stream_mtx);\n\n\t\tf->close (decoder_data);\n\t\terror (\"%s\", err.err);\n\t\tstatus_msg (\"\");\n\t\tdecoder_error_clear (&err);\n\t\tlogit (\"Can't open file\");\n\t}\n\telse {\n\t\taudio_state_started_playing ();\n\t\tbitrate_list_init (&bitrate_list);\n\t\tdecode_loop (f, decoder_data, NULL, out_buf, &sound_params,\n\t\t\t\t&null_md5, 0.0);\n\t}\n}\n\n/* Callback for io buffer fill - show the prebuffering state. */\nstatic void fill_cb (struct io_stream *unused1 ATTR_UNUSED, size_t fill,\n\t\tsize_t unused2 ATTR_UNUSED, void *unused3 ATTR_UNUSED)\n{\n\tif (prebuffering) {\n\t\tchar msg[64];\n\n\t\tsprintf (msg, \"Prebuffering %zu/%d KB\", fill / 1024U,\n\t\t              options_get_int(\"Prebuffering\"));\n\t\tstatus_msg (msg);\n\t}\n}\n\n/* Open a file, decode it and put output into the buffer. At the end, start\n * precaching next_file. */\nvoid player (const char *file, const char *next_file, struct out_buf *out_buf)\n{\n\tstruct decoder *f;\n\n\tif (file_type(file) == F_URL) {\n\t\tstatus_msg (\"Connecting...\");\n\n\t\tLOCK (decoder_stream_mtx);\n\t\tdecoder_stream = io_open (file, 1);\n\t\tif (!io_ok(decoder_stream)) {\n\t\t\terror (\"Could not open URL: %s\", io_strerror(decoder_stream));\n\t\t\tio_close (decoder_stream);\n\t\t\tstatus_msg (\"\");\n\t\t\tdecoder_stream = NULL;\n\t\t\tUNLOCK (decoder_stream_mtx);\n\t\t\treturn;\n\t\t}\n\t\tUNLOCK (decoder_stream_mtx);\n\n\t\tf = get_decoder_by_content (decoder_stream);\n\t\tif (!f) {\n\t\t\tLOCK (decoder_stream_mtx);\n\t\t\tio_close (decoder_stream);\n\t\t\tstatus_msg (\"\");\n\t\t\tdecoder_stream = NULL;\n\t\t\tUNLOCK (decoder_stream_mtx);\n\t\t\treturn;\n\t\t}\n\n\t\tstatus_msg (\"Prebuffering...\");\n\t\tprebuffering = 1;\n\t\tio_set_buf_fill_callback (decoder_stream, fill_cb, NULL);\n\t\tio_prebuffer (decoder_stream,\n\t\t\t\toptions_get_int(\"Prebuffering\") * 1024);\n\t\tprebuffering = 0;\n\n\t\tstatus_msg (\"Playing...\");\n\t\tev_audio_start ();\n\t\tplay_stream (f, out_buf);\n\t\tev_audio_stop ();\n\t}\n\telse {\n\t\tf = get_decoder (file);\n\t\tLOCK (decoder_stream_mtx);\n\t\tdecoder_stream = NULL;\n\t\tUNLOCK (decoder_stream_mtx);\n\n\t\tif (!f) {\n\t\t\terror (\"Can't get decoder for %s\", file);\n\t\t\treturn;\n\t\t}\n\n\t\tev_audio_start ();\n\t\tplay_file (file, f, next_file, out_buf);\n\t\tev_audio_stop ();\n\t}\n\n\tlogit (\"exiting\");\n}\n\nvoid player_cleanup ()\n{\n\tint rc;\n\n\trc = pthread_mutex_destroy (&request_cond_mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy request mutex\", rc);\n\trc = pthread_mutex_destroy (&curr_tags_mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy tags mutex\", rc);\n\trc = pthread_mutex_destroy (&decoder_stream_mtx);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy decoder_stream mutex\", rc);\n\trc = pthread_cond_destroy (&request_cond);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy request condition\", rc);\n\n\tprecache_wait (&precache);\n\tprecache_reset (&precache);\n}\n\nvoid player_reset ()\n{\n\trequest = REQ_NOTHING;\n}\n\nvoid player_stop ()\n{\n\tlogit (\"requesting stop\");\n\trequest = REQ_STOP;\n\n\tLOCK (decoder_stream_mtx);\n\tif (decoder_stream) {\n\t\tlogit (\"decoder_stream present, aborting...\");\n\t\tio_abort (decoder_stream);\n\t}\n\tUNLOCK (decoder_stream_mtx);\n\n\tLOCK (request_cond_mtx);\n\tpthread_cond_signal (&request_cond);\n\tUNLOCK (request_cond_mtx);\n}\n\nvoid player_seek (const int sec)\n{\n\tint time;\n\n\ttime = audio_get_time ();\n\tif (time >= 0) {\n\t\trequest = REQ_SEEK;\n\t\treq_seek = sec + time;\n\t\tLOCK (request_cond_mtx);\n\t\tpthread_cond_signal (&request_cond);\n\t\tUNLOCK (request_cond_mtx);\n\t}\n}\n\nvoid player_jump_to (const int sec)\n{\n\trequest = REQ_SEEK;\n\treq_seek = sec;\n\tLOCK (request_cond_mtx);\n\tpthread_cond_signal (&request_cond);\n\tUNLOCK (request_cond_mtx);\n}\n\n/* Stop playing, clear the output buffer, but allow to unpause by starting\n * playing the same stream.  This is useful for Internet streams that can't\n * be really paused. */\nvoid player_pause ()\n{\n\trequest = REQ_PAUSE;\n\tLOCK (request_cond_mtx);\n\tpthread_cond_signal (&request_cond);\n\tUNLOCK (request_cond_mtx);\n}\n\nvoid player_unpause ()\n{\n\trequest = REQ_UNPAUSE;\n\tLOCK (request_cond_mtx);\n\tpthread_cond_signal (&request_cond);\n\tUNLOCK (request_cond_mtx);\n}\n\n/* Return tags for the currently played file or NULL if there are no tags.\n * Tags are duplicated. */\nstruct file_tags *player_get_curr_tags ()\n{\n\tstruct file_tags *tags = NULL;\n\n\tLOCK (curr_tags_mtx);\n\tif (curr_tags)\n\t\ttags = tags_dup (curr_tags);\n\tUNLOCK (curr_tags_mtx);\n\n\treturn tags;\n}\n"
  },
  {
    "path": "player.h",
    "content": "#ifndef PLAYER_H\n#define PLAYER_H\n\n#include \"out_buf.h\"\n#include \"io.h\"\n#include \"playlist.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid player_cleanup ();\nvoid player (const char *file, const char *next_file, struct out_buf *out_buf);\nvoid player_stop ();\nvoid player_seek (const int n);\nvoid player_jump_to (const int n);\nvoid player_reset ();\nvoid player_init ();\nstruct file_tags *player_get_curr_tags ();\nvoid player_pause ();\nvoid player_unpause ();\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "playlist.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004,2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * Author of title building code: Florian Kriener <me@leflo.de>\n *\n * Contributors:\n *  - Florian Kriener <me@leflo.de> - title building code\n *  - Kamil Tarkowski <kamilt@interia.pl> - plist_prev()\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <string.h>\n#include <stdint.h>\n#include <assert.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"playlist.h\"\n#include \"log.h\"\n#include \"options.h\"\n#include \"files.h\"\n#include \"rbtree.h\"\n#include \"utf8.h\"\n#include \"rcc.h\"\n\n/* Initial size of the table */\n#define\tINIT_SIZE\t64\n\nvoid tags_free (struct file_tags *tags)\n{\n\tassert (tags != NULL);\n\n\tif (tags->title)\n\t\tfree (tags->title);\n\tif (tags->artist)\n\t\tfree (tags->artist);\n\tif (tags->album)\n\t\tfree (tags->album);\n\n\tfree (tags);\n}\n\nvoid tags_clear (struct file_tags *tags)\n{\n\tassert (tags != NULL);\n\n\tif (tags->title)\n\t\tfree (tags->title);\n\tif (tags->artist)\n\t\tfree (tags->artist);\n\tif (tags->album)\n\t\tfree (tags->album);\n\n\ttags->title = NULL;\n\ttags->artist = NULL;\n\ttags->album = NULL;\n\ttags->track = -1;\n\ttags->time = -1;\n}\n\n/* Copy the tags data from src to dst freeing old fields if necessary. */\nvoid tags_copy (struct file_tags *dst, const struct file_tags *src)\n{\n\tif (dst->title)\n\t\tfree (dst->title);\n\tdst->title = xstrdup (src->title);\n\n\tif (dst->artist)\n\t\tfree (dst->artist);\n\tdst->artist = xstrdup (src->artist);\n\n\tif (dst->album)\n\t\tfree (dst->album);\n\tdst->album = xstrdup (src->album);\n\n\tdst->track = src->track;\n\tdst->time = src->time;\n\tdst->filled = src->filled;\n}\n\nstruct file_tags *tags_new ()\n{\n\tstruct file_tags *tags;\n\n\ttags = (struct file_tags *)xmalloc (sizeof(struct file_tags));\n\ttags->title = NULL;\n\ttags->artist = NULL;\n\ttags->album = NULL;\n\ttags->track = -1;\n\ttags->time = -1;\n\ttags->filled = 0;\n\n\treturn tags;\n}\n\nstruct file_tags *tags_dup (const struct file_tags *tags)\n{\n\tstruct file_tags *dtags;\n\n\tassert (tags != NULL);\n\n\tdtags = tags_new();\n\ttags_copy (dtags, tags);\n\n\treturn dtags;\n}\n\nstatic int rb_compare (const void *a, const void *b, const void *adata)\n{\n\tstruct plist *plist = (struct plist *)adata;\n\tint pos_a = (intptr_t)a;\n\tint pos_b = (intptr_t)b;\n\n\treturn strcoll (plist->items[pos_a].file, plist->items[pos_b].file);\n}\n\nstatic int rb_fname_compare (const void *key, const void *data,\n                             const void *adata)\n{\n\tstruct plist *plist = (struct plist *)adata;\n\tconst char *fname = (const char *)key;\n\tconst int pos = (intptr_t)data;\n\n\treturn strcoll (fname, plist->items[pos].file);\n}\n\n/* Return 1 if an item has 'deleted' flag. */\ninline int plist_deleted (const struct plist *plist, const int num)\n{\n\tassert (LIMIT(num, plist->num));\n\n\treturn plist->items[num].deleted;\n}\n\n/* Initialize the playlist. */\nvoid plist_init (struct plist *plist)\n{\n\tplist->num = 0;\n\tplist->allocated = INIT_SIZE;\n\tplist->not_deleted = 0;\n\tplist->items = (struct plist_item *)xmalloc (sizeof(struct plist_item)\n\t\t\t* INIT_SIZE);\n\tplist->serial = -1;\n\tplist->search_tree = rb_tree_new (rb_compare, rb_fname_compare, plist);\n\tplist->total_time = 0;\n\tplist->items_with_time = 0;\n}\n\n/* Create a new playlist item with empty fields. */\nstruct plist_item *plist_new_item ()\n{\n\tstruct plist_item *item;\n\n\titem = (struct plist_item *)xmalloc (sizeof(struct plist_item));\n\titem->file = NULL;\n\titem->type = F_OTHER;\n\titem->deleted = 0;\n\titem->title_file = NULL;\n\titem->title_tags = NULL;\n\titem->tags = NULL;\n\titem->mtime = (time_t)-1;\n\titem->queue_pos = 0;\n\n\treturn item;\n}\n\n/* Add a file to the list. Return the index of the item. */\nint plist_add (struct plist *plist, const char *file_name)\n{\n\tassert (plist != NULL);\n\tassert (plist->items != NULL);\n\n\tif (plist->allocated == plist->num) {\n\t\tplist->allocated *= 2;\n\t\tplist->items = (struct plist_item *)xrealloc (plist->items,\n\t\t\t\tsizeof(struct plist_item) * plist->allocated);\n\t}\n\n\tplist->items[plist->num].file = xstrdup (file_name);\n\tplist->items[plist->num].type = file_name ? file_type (file_name)\n\t\t: F_OTHER;\n\tplist->items[plist->num].deleted = 0;\n\tplist->items[plist->num].title_file = NULL;\n\tplist->items[plist->num].title_tags = NULL;\n\tplist->items[plist->num].tags = NULL;\n\tplist->items[plist->num].mtime = (file_name ? get_mtime(file_name)\n\t\t\t: (time_t)-1);\n\tplist->items[plist->num].queue_pos = 0;\n\n\tif (file_name) {\n\t\trb_delete (plist->search_tree, file_name);\n\t\trb_insert (plist->search_tree, (void *)(intptr_t)plist->num);\n\t}\n\n\tplist->num++;\n\tplist->not_deleted++;\n\n\treturn plist->num - 1;\n}\n\n/* Copy all fields of item src to dst. */\nvoid plist_item_copy (struct plist_item *dst, const struct plist_item *src)\n{\n\tif (dst->file)\n\t\tfree (dst->file);\n\tdst->file = xstrdup (src->file);\n\tdst->type = src->type;\n\tdst->title_file = xstrdup (src->title_file);\n\tdst->title_tags = xstrdup (src->title_tags);\n\tdst->mtime = src->mtime;\n\tdst->queue_pos = src->queue_pos;\n\n\tif (src->tags)\n\t\tdst->tags = tags_dup (src->tags);\n\telse\n\t\tdst->tags = NULL;\n\n\tdst->deleted = src->deleted;\n}\n\n/* Get the pointer to the element on the playlist.\n * If the item number is not valid, return NULL.\n * Returned memory is malloced.\n */\nchar *plist_get_file (const struct plist *plist, int i)\n{\n\tchar *file = NULL;\n\n\tassert (i >= 0);\n\tassert (plist != NULL);\n\n\tif (i < plist->num)\n\t\tfile = xstrdup (plist->items[i].file);\n\n\treturn file;\n}\n\n/* Get the number of the next item on the list (skipping deleted items).\n * If num == -1, get the first item.\n * Return -1 if there are no items left.\n */\nint plist_next (struct plist *plist, int num)\n{\n\tint i = num + 1;\n\n\tassert (plist != NULL);\n\tassert (num >= -1);\n\n\twhile (i < plist->num && plist->items[i].deleted)\n\t\ti++;\n\n\treturn i < plist->num ? i : -1;\n}\n\n/* Get the number of the previous item on the list (skipping deleted items).\n * If num == -1, get the first item.\n * Return -1 if it is the beginning of the playlist.\n */\nint plist_prev (struct plist *plist, int num)\n{\n\tint i = num - 1;\n\n\tassert (plist != NULL);\n\tassert (num >= -1);\n\n\twhile (i >= 0 && plist->items[i].deleted)\n\t\ti--;\n\n\treturn i >= 0 ? i : -1;\n}\n\nvoid plist_free_item_fields (struct plist_item *item)\n{\n\tif (item->file) {\n\t\tfree (item->file);\n\t\titem->file = NULL;\n\t}\n\tif (item->title_tags) {\n\t\tfree (item->title_tags);\n\t\titem->title_tags = NULL;\n\t}\n\tif (item->title_file) {\n\t\tfree (item->title_file);\n\t\titem->title_file = NULL;\n\t}\n\tif (item->tags) {\n\t\ttags_free (item->tags);\n\t\titem->tags = NULL;\n\t}\n}\n\n/* Clear the list. */\nvoid plist_clear (struct plist *plist)\n{\n\tint i;\n\n\tassert (plist != NULL);\n\n\tfor (i = 0; i < plist->num; i++)\n\t\tplist_free_item_fields (&plist->items[i]);\n\n\tplist->items = (struct plist_item *)xrealloc (plist->items,\n\t\t\tsizeof(struct plist_item) * INIT_SIZE);\n\tplist->allocated = INIT_SIZE;\n\tplist->num = 0;\n\tplist->not_deleted = 0;\n\trb_tree_clear (plist->search_tree);\n\tplist->total_time = 0;\n\tplist->items_with_time = 0;\n}\n\n/* Destroy the list freeing memory; the list can't be used after that. */\nvoid plist_free (struct plist *plist)\n{\n\tassert (plist != NULL);\n\n\tplist_clear (plist);\n\tfree (plist->items);\n\tplist->allocated = 0;\n\tplist->items = NULL;\n\trb_tree_free (plist->search_tree);\n}\n\n/* Sort the playlist by file names. */\nvoid plist_sort_fname (struct plist *plist)\n{\n\tstruct plist_item *sorted;\n\tstruct rb_node *x;\n\tint n;\n\n\tif (plist_count(plist) == 0)\n\t\treturn;\n\n\tsorted = (struct plist_item *)xmalloc (plist_count(plist) *\n\t\t\tsizeof(struct plist_item));\n\n\tx = rb_min (plist->search_tree);\n\tassert (!rb_is_null(x));\n\n\twhile (plist_deleted(plist, (intptr_t)rb_get_data (x)))\n\t\tx = rb_next (x);\n\n\tsorted[0] = plist->items[(intptr_t)rb_get_data (x)];\n\trb_set_data (x, NULL);\n\n\tn = 1;\n\twhile (!rb_is_null(x = rb_next(x))) {\n\t\tif (!plist_deleted(plist, (intptr_t)rb_get_data (x))) {\n\t\t\tsorted[n] = plist->items[(intptr_t)rb_get_data (x)];\n\t\t\trb_set_data (x, (void *)(intptr_t)n++);\n\t\t}\n\t}\n\n\tplist->num = n;\n\tplist->not_deleted = n;\n\n\tmemcpy (plist->items, sorted, sizeof(struct plist_item) * n);\n\tfree (sorted);\n}\n\n/* Find an item in the list.  Return the index or -1 if not found. */\nint plist_find_fname (struct plist *plist, const char *file)\n{\n\tstruct rb_node *x;\n\n\tassert (plist != NULL);\n\tassert (file != NULL);\n\n\tx = rb_search (plist->search_tree, file);\n\n\tif (rb_is_null(x))\n\t\treturn -1;\n\n\treturn !plist_deleted(plist, (intptr_t)rb_get_data (x)) ?\n                                 (intptr_t)rb_get_data (x) : -1;\n}\n\n/* Find an item in the list; also find deleted items.  If there is more than\n * one item for this file, return the non-deleted one or, if all are deleted,\n * return the last of them.  Return the index or -1 if not found. */\nint plist_find_del_fname (const struct plist *plist, const char *file)\n{\n\tint i;\n\tint item = -1;\n\n\tassert (plist != NULL);\n\n\tfor (i = 0; i < plist->num; i++) {\n\t\tif (plist->items[i].file\n\t\t\t\t&& !strcmp(plist->items[i].file, file)) {\n\t\t\tif (item == -1 || plist_deleted(plist, item))\n\t\t\t\titem = i;\n\t\t}\n\t}\n\n\treturn item;\n}\n\n/* Returns the next filename that is a dead entry, or NULL if there are none\n * left.\n *\n * It will set the index on success.\n */\nconst char *plist_get_next_dead_entry (const struct plist *plist,\n                                       int *last_index)\n{\n\tint i;\n\n\tassert (last_index != NULL);\n\tassert (plist != NULL);\n\n\tfor (i = *last_index; i < plist->num; i++) {\n\t\tif (plist->items[i].file\n\t\t\t  && ! plist_deleted(plist, i)\n\t\t\t  && ! can_read_file(plist->items[i].file)) {\n\t\t\t*last_index = i + 1;\n\t\t\treturn plist->items[i].file;\n\t\t}\n\t}\n\n\treturn NULL;\n}\n\n#define if_not_empty(str)\t(tags && (str) && (*str) ? (str) : NULL)\n\nstatic char *title_expn_subs(char fmt, const struct file_tags *tags)\n{\n\tstatic char track[16];\n\n\tswitch (fmt) {\n\t\tcase 'n':\n\t\t\tif (!tags || tags->track == -1)\n\t\t\t\tbreak;\n\t\t\tsnprintf (track, sizeof(track), \"%d\", tags->track);\n\t\t\treturn track;\n\t\tcase 'a':\n\t\t\treturn if_not_empty (tags->artist);\n\t\tcase 'A':\n\t\t\treturn if_not_empty (tags->album);\n\t\tcase 't':\n\t\t\treturn if_not_empty (tags->title);\n\t\tdefault:\n\t\t\tfatal (\"Error parsing format string!\");\n\t}\n\n\treturn NULL;\n}\n\nstatic inline void check_zero (const char *x)\n{\n\tif (*x == '\\0')\n\t\tfatal (\"Unexpected end of title expression!\");\n}\n\n/* Generate a title from fmt. */\nstatic void do_title_expn (char *dest, int size, const char *fmt,\n\t\tconst struct file_tags *tags)\n{\n\tconst char *h;\n\tint free = --size;\n\tshort escape = 0;\n\n\tdest[0] = 0;\n\n\twhile (free > 0 && *fmt) {\n\t\tif (*fmt == '%' && !escape) {\n\t\t\tcheck_zero(++fmt);\n\n\t\t\t/* do ternary expansion\n\t\t\t * format: %(x:true:false)\n\t\t\t */\n\t\t\tif (*fmt == '(') {\n\t\t\t\tchar separator, expr[256];\n\t\t\t\tint expr_pos = 0;\n\n\t\t\t\tcheck_zero(++fmt);\n\t\t\t\th = title_expn_subs(*fmt, tags);\n\n\t\t\t\tcheck_zero(++fmt);\n\t\t\t\tseparator = *fmt;\n\n\t\t\t\tcheck_zero(++fmt);\n\n\t\t\t\tif(h) { /* true */\n\n\t\t\t\t\t/* copy the expression */\n\t\t\t\t\twhile (escape || *fmt != separator) {\n\t\t\t\t\t\tif (expr_pos == sizeof(expr)-2)\n\t\t\t\t\t\t\tfatal (\"Nested ternary expression too long!\");\n\t\t\t\t\t\texpr[expr_pos++] = *fmt;\n\t\t\t\t\t\tif (*fmt == '\\\\')\n\t\t\t\t\t\t\tescape = 1;\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tescape = 0;\n\t\t\t\t\t\tcheck_zero(++fmt);\n\t\t\t\t\t}\n\t\t\t\t\texpr[expr_pos] = '\\0';\n\n\t\t\t\t\t/* eat the rest */\n\t\t\t\t\twhile (escape || *fmt != ')') {\n\t\t\t\t\t\tif (escape)\n\t\t\t\t\t\t\tescape = 0;\n\t\t\t\t\t\telse if (*fmt == '\\\\')\n\t\t\t\t\t\t\tescape = 1;\n\t\t\t\t\t\tcheck_zero(++fmt);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse { /* false */\n\n\t\t\t\t\t/* eat the truth :-) */\n\t\t\t\t\twhile (escape || *fmt != separator) {\n\t\t\t\t\t\tif (escape)\n\t\t\t\t\t\t\tescape = 0;\n\t\t\t\t\t\telse if (*fmt == '\\\\')\n\t\t\t\t\t\t\tescape = 1;\n\t\t\t\t\t\tcheck_zero(++fmt);\n\t\t\t\t\t}\n\n\t\t\t\t\tcheck_zero(++fmt);\n\n\t\t\t\t\t/* copy the expression */\n\t\t\t\t\twhile (escape || *fmt != ')') {\n\t\t\t\t\t\tif (expr_pos == sizeof(expr)-2)\n\t\t\t\t\t\t\tfatal (\"Ternary expression too long!\");\n\t\t\t\t\t\texpr[expr_pos++] = *fmt;\n\t\t\t\t\t\tif (*fmt == '\\\\')\n\t\t\t\t\t\t\tescape = 1;\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tescape = 0;\n\t\t\t\t\t\tcheck_zero(++fmt);\n\t\t\t\t\t}\n\t\t\t\t\texpr[expr_pos] = '\\0';\n\t\t\t\t}\n\n\t\t\t\tdo_title_expn((dest + size - free),\n\t\t\t\t\t      free, expr, tags);\n\t\t\t\tfree -= strlen(dest + size - free);\n\t\t\t}\n\t\t\telse {\n\t\t\t\th = title_expn_subs(*fmt, tags);\n\n\t\t\t\tif (h) {\n\t\t\t\t\tstrncat(dest, h, free-1);\n\t\t\t\t\tfree -= strlen (h);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (*fmt == '\\\\' && !escape)\n\t\t\tescape = 1;\n\t\telse {\n\t\t\tdest[size - free] = *fmt;\n\t\t\tdest[size - free + 1] = 0;\n\t\t\t--free;\n\t\t\tescape = 0;\n\t\t}\n\t\tfmt++;\n\t}\n\n\tfree = MAX(free, 0); /* Possible integer overflow? */\n\tdest[size - free] = '\\0';\n}\n\n/* Build file title from struct file_tags. Returned memory is malloc()ed. */\nchar *build_title_with_format (const struct file_tags *tags, const char *fmt)\n{\n\tchar title[512];\n\n\tdo_title_expn (title, sizeof(title), fmt, tags);\n\treturn xstrdup (title);\n}\n\n/* Build file title from struct file_tags. Returned memory is malloc()ed. */\nchar *build_title (const struct file_tags *tags)\n{\n\treturn build_title_with_format (tags, options_get_str (\"FormatString\"));\n}\n\n/* Copy the item to the playlist. Return the index of the added item. */\nint plist_add_from_item (struct plist *plist, const struct plist_item *item)\n{\n\tint pos = plist_add (plist, item->file);\n\n\tplist_item_copy (&plist->items[pos], item);\n\n\tif (item->tags && item->tags->time != -1) {\n\t\tplist->total_time += item->tags->time;\n\t\tplist->items_with_time++;\n\t}\n\n\treturn pos;\n}\n\nvoid plist_delete (struct plist *plist, const int num)\n{\n\tassert (plist != NULL);\n\tassert (!plist->items[num].deleted);\n\tassert (plist->not_deleted > 0);\n\n\tif (num < plist->num) {\n\n\t\t/* Free every field except the file, it is needed in deleted\n\t\t * items. */\n\t\tchar *file = plist->items[num].file;\n\n\t\tplist->items[num].file = NULL;\n\n\t\tif (plist->items[num].tags\n\t\t\t\t&& plist->items[num].tags->time != -1) {\n\t\t\tplist->total_time -= plist->items[num].tags->time;\n\t\t\tplist->items_with_time--;\n\t\t}\n\n\t\tplist_free_item_fields (&plist->items[num]);\n\t\tplist->items[num].file = file;\n\n\t\tplist->items[num].deleted = 1;\n\n\t\tplist->not_deleted--;\n\t}\n}\n\n/* Count non-deleted items. */\nint plist_count (const struct plist *plist)\n{\n\tassert (plist != NULL);\n\n\treturn plist->not_deleted;\n}\n\n/* Set tags title of an item. */\nvoid plist_set_title_tags (struct plist *plist, const int num,\n\t\tconst char *title)\n{\n\tassert (LIMIT(num, plist->num));\n\n\tif (plist->items[num].title_tags)\n\t\tfree (plist->items[num].title_tags);\n\tplist->items[num].title_tags = xstrdup (title);\n}\n\n/* Set file title of an item. */\nvoid plist_set_title_file (struct plist *plist, const int num,\n\t\tconst char *title)\n{\n\tassert (LIMIT(num, plist->num));\n\n\tif (plist->items[num].title_file)\n\t\tfree (plist->items[num].title_file);\n\n#ifdef  HAVE_RCC\n\tif (options_get_bool (\"UseRCCForFilesystem\")) {\n\t\tchar *t_str = xstrdup (title);\n\t\tplist->items[num].title_file = rcc_reencode (t_str);\n\t\treturn;\n\t}\n#endif\n\n\tplist->items[num].title_file = xstrdup (title);\n}\n\n/* Set file for an item. */\nvoid plist_set_file (struct plist *plist, const int num, const char *file)\n{\n\tassert (LIMIT(num, plist->num));\n\tassert (file != NULL);\n\n\tif (plist->items[num].file) {\n\t\trb_delete (plist->search_tree, file);\n\t\tfree (plist->items[num].file);\n\t}\n\n\tplist->items[num].file = xstrdup (file);\n\tplist->items[num].type = file_type (file);\n\tplist->items[num].mtime = get_mtime (file);\n\trb_insert (plist->search_tree, (void *)(intptr_t)num);\n}\n\n/* Add the content of playlist b to a by copying items. */\nvoid plist_cat (struct plist *a, struct plist *b)\n{\n\tint i;\n\n\tassert (a != NULL);\n\tassert (b != NULL);\n\n\tfor (i = 0; i < b->num; i++) {\n\t\tif (plist_deleted (b, i))\n\t\t\tcontinue;\n\n\t\tassert (b->items[i].file != NULL);\n\n\t\tif (plist_find_fname (a, b->items[i].file) == -1)\n\t\t\tplist_add_from_item (a, &b->items[i]);\n\t}\n}\n\n/* Set the time tags field for the item. */\nvoid plist_set_item_time (struct plist *plist, const int num, const int time)\n{\n\tint old_time;\n\n\tassert (plist != NULL);\n\tassert (LIMIT(num, plist->num));\n\n\tif (!plist->items[num].tags) {\n\t\tplist->items[num].tags = tags_new ();\n\t\told_time = -1;\n\t}\n\telse if (plist->items[num].tags->time != -1)\n\t\told_time = plist->items[num].tags->time;\n\telse\n\t\told_time = -1;\n\n\tif (old_time != -1) {\n\t\tplist->total_time -= old_time;\n\t\tplist->items_with_time--;\n\t}\n\n\tif (time != -1) {\n\t\tplist->total_time += time;\n\t\tplist->items_with_time++;\n\t}\n\n\tplist->items[num].tags->time = time;\n\tplist->items[num].tags->filled |= TAGS_TIME;\n}\n\nint get_item_time (const struct plist *plist, const int i)\n{\n\tassert (plist != NULL);\n\n\tif (plist->items[i].tags)\n\t\treturn plist->items[i].tags->time;\n\n\treturn -1;\n}\n\n/* Return the total time of all files on the playlist having the time tag.\n * If the time information is missing for any file, all_files is set to 0,\n * otherwise 1.\n * Returned value is that counted by plist_count_time(), so may be not\n * up-to-date. */\nint plist_total_time (const struct plist *plist, int *all_files)\n{\n\t*all_files = plist->not_deleted == plist->items_with_time;\n\n\treturn plist->total_time;\n}\n\n/* Swap two items on the playlist. */\nstatic void plist_swap (struct plist *plist, const int a, const int b)\n{\n\tassert (plist != NULL);\n\tassert (LIMIT(a, plist->num));\n\tassert (LIMIT(b, plist->num));\n\n\tif (a != b) {\n\t\tstruct plist_item t;\n\n\t\tt = plist->items[a];\n\t\tplist->items[a] = plist->items[b];\n\t\tplist->items[b] = t;\n\t}\n}\n\n/* Shuffle the playlist. */\nvoid plist_shuffle (struct plist *plist)\n{\n\tint i;\n\n\tfor (i = 0; i < plist->num; i += 1)\n\t\tplist_swap (plist, i, (rand () / (float)RAND_MAX) * (plist->num - 1));\n\n\trb_tree_clear (plist->search_tree);\n\n\tfor (i = 0; i < plist->num; i++)\n\t\trb_insert (plist->search_tree, (void *)(intptr_t)i);\n}\n\n/* Swap the first item on the playlist with the item with file fname. */\nvoid plist_swap_first_fname (struct plist *plist, const char *fname)\n{\n\tint i;\n\n\tassert (plist != NULL);\n\tassert (fname != NULL);\n\n\ti = plist_find_fname (plist, fname);\n\n\tif (i != -1 && i != 0) {\n\t\trb_delete (plist->search_tree, fname);\n\t\trb_delete (plist->search_tree, plist->items[0].file);\n\t\tplist_swap (plist, 0, i);\n\t\trb_insert (plist->search_tree, NULL);\n\t\trb_insert (plist->search_tree, (void *)(intptr_t)i);\n\t}\n}\n\nvoid plist_set_serial (struct plist *plist, const int serial)\n{\n\tplist->serial = serial;\n}\n\nint plist_get_serial (const struct plist *plist)\n{\n\treturn plist->serial;\n}\n\n/* Return the index of the last non-deleted item from the playlist.\n * Return -1 if there are no items. */\nint plist_last (const struct plist *plist)\n{\n\tint i;\n\n\ti = plist->num - 1;\n\n\twhile (i > 0 && plist_deleted(plist, i))\n\t\ti--;\n\n\treturn i;\n}\n\nenum file_type plist_file_type (const struct plist *plist, const int num)\n{\n\tassert (plist != NULL);\n\tassert (num < plist->num);\n\n\treturn plist->items[num].type;\n}\n\n/* Remove items from playlist 'a' that are also present on playlist 'b'. */\nvoid plist_remove_common_items (struct plist *a, struct plist *b)\n{\n\tint i;\n\n\tassert (a != NULL);\n\tassert (b != NULL);\n\n\tfor (i = 0; i < a->num; i += 1) {\n\t\tif (plist_deleted (a, i))\n\t\t\tcontinue;\n\n\t\tassert (a->items[i].file != NULL);\n\n\t\tif (plist_find_fname (b, a->items[i].file) != -1)\n\t\t\tplist_delete (a, i);\n\t}\n}\n\nvoid plist_discard_tags (struct plist *plist)\n{\n\tint i;\n\n\tassert (plist != NULL);\n\n\tfor (i = 0; i < plist->num; i++)\n\t\tif (!plist_deleted(plist, i) && plist->items[i].tags) {\n\t\t\ttags_free (plist->items[i].tags);\n\t\t\tplist->items[i].tags = NULL;\n\t\t}\n\n\tplist->items_with_time = 0;\n\tplist->total_time = 0;\n}\n\nvoid plist_set_tags (struct plist *plist, const int num,\n\t\tconst struct file_tags *tags)\n{\n\tint old_time;\n\n\tassert (plist != NULL);\n\tassert (LIMIT(num, plist->num));\n\tassert (tags != NULL);\n\n\tif (plist->items[num].tags && plist->items[num].tags->time != -1)\n\t\told_time = plist->items[num].tags->time;\n\telse\n\t\told_time = -1;\n\n\tif (plist->items[num].tags)\n\t\ttags_free (plist->items[num].tags);\n\tplist->items[num].tags = tags_dup (tags);\n\n\tif (old_time != -1) {\n\t\tplist->total_time -= old_time;\n\t\tplist->items_with_time--;\n\t}\n\n\tif (tags->time != -1) {\n\t\tplist->total_time += tags->time;\n\t\tplist->items_with_time++;\n\t}\n}\n\nstruct file_tags *plist_get_tags (const struct plist *plist, const int num)\n{\n\tassert (plist != NULL);\n\tassert (LIMIT(num, plist->num));\n\n\tif (plist->items[num].tags)\n\t\treturn tags_dup (plist->items[num].tags);\n\n\treturn NULL;\n}\n\n/* Swap two files on the playlist. */\nvoid plist_swap_files (struct plist *plist, const char *file1,\n\t\tconst char *file2)\n{\n\tstruct rb_node *x1, *x2;\n\n\tassert (plist != NULL);\n\tassert (file1 != NULL);\n\tassert (file2 != NULL);\n\n\tx1 = rb_search (plist->search_tree, file1);\n\tx2 = rb_search (plist->search_tree, file2);\n\n\tif (!rb_is_null(x1) && !rb_is_null(x2)) {\n\t\tconst void *t;\n\n\t\tplist_swap (plist, (intptr_t)rb_get_data (x1),\n\t\t                   (intptr_t)rb_get_data (x2));\n\n\t\tt = rb_get_data (x1);\n\t\trb_set_data (x1, rb_get_data (x2));\n\t\trb_set_data (x2, t);\n\t}\n}\n\n/* Return the position of a file in the list, starting with 1. */\nint plist_get_position (const struct plist *plist, int num)\n{\n\tint i, pos = 1;\n\n\tassert (LIMIT(num, plist->num));\n\n\tfor (i = 0; i < num; i++) {\n\t\tif(!plist->items[i].deleted)\n\t\t\tpos++;\n\t}\n\n\treturn pos;\n}\n"
  },
  {
    "path": "playlist.h",
    "content": "#ifndef PLAYLIST_H\n#define PLAYLIST_H\n\n#include <sys/types.h>\n\n#include \"rbtree.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* Flags for the info decoder function. */\nenum tags_select\n{\n\tTAGS_COMMENTS\t= 0x01, /* artist, title, etc. */\n\tTAGS_TIME\t= 0x02 /* time of the file. */\n};\n\nstruct file_tags\n{\n\tchar *title;\n\tchar *artist;\n\tchar *album;\n\tint track;\n\tint time;\n\tint filled; /* Which tags are filled: TAGS_COMMENTS, TAGS_TIME. */\n};\n\nenum file_type\n{\n\tF_DIR,\n\tF_SOUND,\n\tF_URL,\n\tF_PLAYLIST,\n\tF_THEME,\n\tF_OTHER\n};\n\nstruct plist_item\n{\n\tchar *file;\n\tenum file_type type;\t/* type of the file (F_OTHER if not read yet) */\n\tchar *title_file;\t/* title based on the file name */\n\tchar *title_tags;\t/* title based on the tags */\n\tstruct file_tags *tags;\n\tshort deleted;\n\ttime_t mtime;\t\t/* modification time */\n\tint queue_pos;\t\t/* position in the queue */\n};\n\nstruct plist\n{\n\tint num;\t\t\t/* Number of elements on the list */\n\tint allocated;\t\t/* Number of allocated elements */\n\tint not_deleted;\t/* Number of non-deleted items */\n\tstruct plist_item *items;\n\tint serial;\t\t/* Optional serial number of this playlist */\n\tint total_time;\t\t/* Total time for files on the playlist */\n\tint items_with_time;\t/* Number of items for which the time is set. */\n\n\tstruct rb_tree *search_tree;\n};\n\nvoid plist_init (struct plist *plist);\nint plist_add (struct plist *plist, const char *file_name);\nint plist_add_from_item (struct plist *plist, const struct plist_item *item);\nchar *plist_get_file (const struct plist *plist, int i);\nint plist_next (struct plist *plist, int num);\nint plist_prev (struct plist *plist, int num);\nvoid plist_clear (struct plist *plist);\nvoid plist_delete (struct plist *plist, const int num);\nvoid plist_free (struct plist *plist);\nvoid plist_sort_fname (struct plist *plist);\nint plist_find_fname (struct plist *plist, const char *file);\nstruct file_tags *tags_new ();\nvoid tags_clear (struct file_tags *tags);\nvoid tags_copy (struct file_tags *dst, const struct file_tags *src);\nstruct file_tags *tags_dup (const struct file_tags *tags);\nvoid tags_free (struct file_tags *tags);\nchar *build_title_with_format (const struct file_tags *tags, const char *fmt);\nchar *build_title (const struct file_tags *tags);\nint plist_count (const struct plist *plist);\nvoid plist_set_title_tags (struct plist *plist, const int num,\n\t\tconst char *title);\nvoid plist_set_title_file (struct plist *plist, const int num,\n\t\tconst char *title);\nvoid plist_set_file (struct plist *plist, const int num, const char *file);\nint plist_deleted (const struct plist *plist, const int num);\nvoid plist_cat (struct plist *a, struct plist *b);\nvoid update_file (struct plist_item *item);\nvoid plist_set_item_time (struct plist *plist, const int num, const int time);\nint get_item_time (const struct plist *plist, const int i);\nint plist_total_time (const struct plist *plisti, int *all_files);\nvoid plist_shuffle (struct plist *plist);\nvoid plist_swap_first_fname (struct plist *plist, const char *fname);\nstruct plist_item *plist_new_item ();\nvoid plist_free_item_fields (struct plist_item *item);\nvoid plist_set_serial (struct plist *plist, const int serial);\nint plist_get_serial (const struct plist *plist);\nint plist_last (const struct plist *plist);\nint plist_find_del_fname (const struct plist *plist, const char *file);\nconst char *plist_get_next_dead_entry (const struct plist *plist,\n                                       int *last_index);\nvoid plist_item_copy (struct plist_item *dst, const struct plist_item *src);\nenum file_type plist_file_type (const struct plist *plist, const int num);\nvoid plist_remove_common_items (struct plist *a, struct plist *b);\nvoid plist_discard_tags (struct plist *plist);\nvoid plist_set_tags (struct plist *plist, const int num,\n\t\tconst struct file_tags *tags);\nstruct file_tags *plist_get_tags (const struct plist *plist, const int num);\nvoid plist_swap_files (struct plist *plist, const char *file1,\n\t\tconst char *file2);\nint plist_get_position (const struct plist *plist, int num);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "playlist_file.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 - 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <unistd.h>\n#include <fcntl.h>\n#include <sys/file.h>\n#include <stdio.h>\n#include <string.h>\n#include <strings.h>\n#include <ctype.h>\n#include <errno.h>\n#include <assert.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"playlist.h\"\n#include \"playlist_file.h\"\n#include \"log.h\"\n#include \"files.h\"\n#include \"options.h\"\n#include \"interface.h\"\n#include \"decoder.h\"\n\nint is_plist_file (const char *name)\n{\n\tconst char *ext = ext_pos (name);\n\n\tif (ext && (!strcasecmp(ext, \"m3u\") || !strcasecmp(ext, \"pls\")))\n\t\treturn 1;\n\n\treturn 0;\n}\n\nstatic void make_path (char *buf, size_t buf_size, const char *cwd, char *path)\n{\n\tif (file_type(path) == F_URL) {\n\t\tstrncpy (buf, path, buf_size);\n\t\tbuf[buf_size-1] = 0;\n\t\treturn;\n\t}\n\n\tif (path[0] != '/')\n\t\tstrcpy (buf, cwd);\n\telse\n\t\tstrcpy (buf, \"/\");\n\n\tresolve_path (buf, buf_size, path);\n}\n\n/* Strip white chars from the end of a string. */\nstatic void strip_string (char *str)\n{\n\tchar *c = str;\n\tchar *last_non_white = str;\n\n\twhile (*c) {\n\t\tif (!isblank(*c))\n\t\t\tlast_non_white = c;\n\t\tc++;\n\t}\n\n\tif (c > last_non_white)\n\t\t*(last_non_white + 1) = 0;\n}\n\n/* Load M3U file into plist.  Return the number of items read. */\nstatic int plist_load_m3u (struct plist *plist, const char *fname,\n\t\tconst char *cwd, const int load_serial)\n{\n\tFILE *file;\n\tchar *line = NULL;\n\tint last_added = -1;\n\tint after_extinf = 0;\n\tint added = 0;\n\tstruct flock read_lock = {.l_type = F_RDLCK, .l_whence = SEEK_SET};\n\n\tfile = fopen (fname, \"r\");\n\tif (!file) {\n\t\terror_errno (\"Can't open playlist file\", errno);\n\t\treturn 0;\n\t}\n\n\t/* Lock gets released by fclose(). */\n\tif (fcntl (fileno (file), F_SETLKW, &read_lock) == -1)\n\t\tlog_errno (\"Can't lock the playlist file\", errno);\n\n\twhile ((line = read_line (file))) {\n\t\tif (!strncmp (line, \"#EXTINF:\", sizeof(\"#EXTINF:\") - 1)) {\n\t\t\tchar *comma, *num_err;\n\t\t\tchar time_text[10] = \"\";\n\t\t\tint time_sec;\n\n\t\t\tif (after_extinf) {\n\t\t\t\terror (\"Broken M3U file: double #EXTINF!\");\n\t\t\t\tplist_delete (plist, last_added);\n\t\t\t\tgoto err;\n\t\t\t}\n\n\t\t\t/* Find the comma */\n\t\t\tcomma = strchr (line + (sizeof(\"#EXTINF:\") - 1), ',');\n\t\t\tif (!comma) {\n\t\t\t\terror (\"Broken M3U file: no comma in #EXTINF!\");\n\t\t\t\tgoto err;\n\t\t\t}\n\n\t\t\t/* Get the time string */\n\t\t\ttime_text[sizeof(time_text) - 1] = 0;\n\t\t\tstrncpy (time_text, line + sizeof(\"#EXTINF:\") - 1,\n\t\t\t         MIN(comma - line - (sizeof(\"#EXTINF:\") - 1),\n\t\t\t         sizeof(time_text)));\n\t\t\tif (time_text[sizeof(time_text) - 1]) {\n\t\t\t\terror (\"Broken M3U file: wrong time!\");\n\t\t\t\tgoto err;\n\t\t\t}\n\n\t\t\t/* Extract the time. */\n\t\t\ttime_sec = strtol (time_text, &num_err, 10);\n\t\t\tif (*num_err) {\n\t\t\t\terror (\"Broken M3U file: time is not a number!\");\n\t\t\t\tgoto err;\n\t\t\t}\n\n\t\t\tafter_extinf = 1;\n\t\t\tlast_added = plist_add (plist, NULL);\n\t\t\tplist_set_title_tags (plist, last_added, comma + 1);\n\n\t\t\tif (*time_text)\n\t\t\t\tplist_set_item_time (plist, last_added, time_sec);\n\t\t}\n\t\telse if (line[0] != '#') {\n\t\t\tchar path[2 * PATH_MAX];\n\n\t\t\tstrip_string (line);\n\t\t\tif (strlen (line) <= PATH_MAX) {\n\t\t\t\tmake_path (path, sizeof(path), cwd, line);\n\n\t\t\t\tif (plist_find_fname (plist, path) == -1) {\n\t\t\t\t\tif (after_extinf)\n\t\t\t\t\t\tplist_set_file (plist, last_added, path);\n\t\t\t\t\telse\n\t\t\t\t\t\tplist_add (plist, path);\n\t\t\t\t\tadded += 1;\n\t\t\t\t}\n\t\t\t\telse if (after_extinf)\n\t\t\t\t\tplist_delete (plist, last_added);\n\t\t\t}\n\t\t\telse if (after_extinf)\n\t\t\t\tplist_delete (plist, last_added);\n\n\t\t\tafter_extinf = 0;\n\t\t}\n\t\telse if (load_serial &&\n\t\t         !strncmp (line, \"#MOCSERIAL: \", sizeof(\"#MOCSERIAL: \") - 1)) {\n\t\t\tchar *serial_str = line + sizeof(\"#MOCSERIAL: \") - 1;\n\n\t\t\tif (serial_str[0]) {\n\t\t\t\tchar *err;\n\t\t\t\tlong serial;\n\n\t\t\t\tserial = strtol (serial_str, &err, 0);\n\t\t\t\tif (!*err) {\n\t\t\t\t\tplist_set_serial (plist, serial);\n\t\t\t\t\tlogit (\"Got MOCSERIAL tag with serial %ld\", serial);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfree (line);\n\t}\n\nerr:\n\tfree (line);\n\tfclose (file);\n\treturn added;\n}\n\n/* Return 1 if the line contains only blank characters, 0 otherwise. */\nstatic int is_blank_line (const char *l)\n{\n\twhile (*l && isblank(*l))\n\t\tl++;\n\n\tif (*l)\n\t\treturn 0;\n\treturn 1;\n}\n\n/* Read a value from the given section from .INI file.  File should be opened\n * and seeking will be performed on it.  Return the malloc()ed value or NULL\n * if not present or error occurred. */\nstatic char *read_ini_value (FILE *file, const char *section, const char *key)\n{\n\tchar *line = NULL;\n\tint in_section = 0;\n\tchar *value = NULL;\n\tint key_len;\n\n\tif (fseek(file, 0, SEEK_SET)) {\n\t\terror_errno (\"File fseek() error\", errno);\n\t\treturn NULL;\n\t}\n\n\tkey_len = strlen (key);\n\n\twhile ((line = read_line(file))) {\n\t\tif (line[0] == '[') {\n\t\t\tif (in_section) {\n\n\t\t\t\t/* we are outside of the interesting section */\n\t\t\t\tfree (line);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tchar *close = strchr (line, ']');\n\n\t\t\t\tif (!close) {\n\t\t\t\t\terror (\"Parse error in the INI file\");\n\t\t\t\t\tfree (line);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tif (!strncasecmp(line + 1, section,\n\t\t\t\t\t\t\tclose - line - 1))\n\t\t\t\t\tin_section = 1;\n\t\t\t}\n\t\t}\n\t\telse if (in_section && line[0] != '#' && !is_blank_line(line)) {\n\t\t\tchar *t, *t2;\n\n\t\t\tt2 = t = strchr (line, '=');\n\n\t\t\tif (!t) {\n\t\t\t\terror (\"Parse error in the INI file\");\n\t\t\t\tfree (line);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t/* go back to the last char in the name */\n\t\t\twhile (t2 >= t && (isblank(*t2) || *t2 == '='))\n\t\t\t\tt2--;\n\n\t\t\tif (t2 == t) {\n\t\t\t\terror (\"Parse error in the INI file\");\n\t\t\t\tfree (line);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (!strncasecmp(line, key,\n\t\t\t\t\t\tMAX(t2 - line + 1, key_len))) {\n\t\t\t\tvalue = t + 1;\n\n\t\t\t\twhile (isblank(value[0]))\n\t\t\t\t\tvalue++;\n\n\t\t\t\tif (value[0] == '\"') {\n\t\t\t\t\tchar *q = strchr (value + 1, '\"');\n\n\t\t\t\t\tif (!q) {\n\t\t\t\t\t\terror (\"Parse error in the INI file\");\n\t\t\t\t\t\tfree (line);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t*q = 0;\n\t\t\t\t}\n\n\t\t\t\tvalue = xstrdup (value);\n\t\t\t\tfree (line);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tfree (line);\n\t}\n\n\treturn value;\n}\n\n/* Load PLS file into plist. Return the number of items read. */\nstatic int plist_load_pls (struct plist *plist, const char *fname,\n\t\tconst char *cwd)\n{\n\tFILE *file;\n\tchar *e, *line = NULL;\n\tlong i, nitems, added = 0;\n\n\tfile = fopen (fname, \"r\");\n\tif (!file) {\n\t\terror_errno (\"Can't open playlist file\", errno);\n\t\treturn 0;\n\t}\n\n\tline = read_ini_value (file, \"playlist\", \"NumberOfEntries\");\n\tif (!line) {\n\n\t\t/* Assume that it is a pls file version 1 - plist_load_m3u()\n\t\t * should handle it like an m3u file without the m3u extensions. */\n\t\tfclose (file);\n\t\treturn plist_load_m3u (plist, fname, cwd, 0);\n\t}\n\n\tnitems = strtol (line, &e, 10);\n\tif (*e) {\n\t\terror (\"Broken PLS file\");\n\t\tgoto err;\n\t}\n\n\tfor (i = 1; i <= nitems; i++) {\n\t\tint time, last_added;\n\t\tchar *pls_file, *pls_title, *pls_length;\n\t\tchar key[32], path[2 * PATH_MAX];\n\n\t\tsprintf (key, \"File%ld\", i);\n\t\tpls_file = read_ini_value (file, \"playlist\", key);\n\t\tif (!pls_file) {\n\t\t\terror (\"Broken PLS file\");\n\t\t\tgoto err;\n\t\t}\n\n\t\tsprintf (key, \"Title%ld\", i);\n\t\tpls_title = read_ini_value (file, \"playlist\", key);\n\n\t\tsprintf (key, \"Length%ld\", i);\n\t\tpls_length = read_ini_value (file, \"playlist\", key);\n\n\t\tif (pls_length) {\n\t\t\ttime = strtol (pls_length, &e, 10);\n\t\t\tif (*e)\n\t\t\t\ttime = -1;\n\t\t}\n\t\telse\n\t\t\ttime = -1;\n\n\t\tif (strlen (pls_file) <= PATH_MAX) {\n\t\t\tmake_path (path, sizeof(path), cwd, pls_file);\n\t\t\tif (plist_find_fname (plist, path) == -1) {\n\t\t\t\tlast_added = plist_add (plist, path);\n\n\t\t\t\tif (pls_title && pls_title[0])\n\t\t\t\t\tplist_set_title_tags (plist, last_added, pls_title);\n\n\t\t\t\tif (time > 0) {\n\t\t\t\t\tplist->items[last_added].tags = tags_new ();\n\t\t\t\t\tplist->items[last_added].tags->time = time;\n\t\t\t\t\tplist->items[last_added].tags->filled |= TAGS_TIME;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfree (pls_file);\n\t\tif (pls_title)\n\t\t\tfree (pls_title);\n\t\tif (pls_length)\n\t\t\tfree (pls_length);\n\t\tadded += 1;\n\t}\n\nerr:\n\tfree (line);\n\tfclose (file);\n\treturn added;\n}\n\n/* Load a playlist into plist. Return the number of items on the list. */\n/* The playlist may have deleted items. */\nint plist_load (struct plist *plist, const char *fname, const char *cwd,\n\t\tconst int load_serial)\n{\n\tint num, read_tags;\n\tconst char *ext;\n\n\tread_tags = options_get_bool (\"ReadTags\");\n\text = ext_pos (fname);\n\n\tif (ext && !strcasecmp(ext, \"pls\"))\n\t\tnum = plist_load_pls (plist, fname, cwd);\n\telse\n\t\tnum = plist_load_m3u (plist, fname, cwd, load_serial);\n\n\tif (read_tags)\n\t\tswitch_titles_tags (plist);\n\telse\n\t\tswitch_titles_file (plist);\n\n\treturn num;\n}\n\n/* Save the playlist into the file in m3u format.  If save_serial is not 0,\n * the playlist serial is saved in a comment. */\nint plist_save (struct plist *plist, const char *fname, const int save_serial)\n{\n\tFILE *file = NULL;\n\tint i, ret, result = 0;\n\tstruct flock write_lock = {.l_type = F_WRLCK, .l_whence = SEEK_SET};\n\n\tdebug (\"Saving playlist to '%s'\", fname);\n\n\tfile = fopen (fname, \"w\");\n\tif (!file) {\n\t\terror_errno (\"Can't save playlist\", errno);\n\t\treturn 0;\n\t}\n\n\t/* Lock gets released by fclose(). */\n\tif (fcntl (fileno (file), F_SETLKW, &write_lock) == -1)\n\t\tlog_errno (\"Can't lock the playlist file\", errno);\n\n\tif (fprintf (file, \"#EXTM3U\\r\\n\") < 0) {\n\t\terror_errno (\"Error writing playlist\", errno);\n\t\tgoto err;\n\t}\n\n\tif (save_serial && fprintf (file, \"#MOCSERIAL: %d\\r\\n\",\n\t                                  plist_get_serial (plist)) < 0) {\n\t\terror_errno (\"Error writing playlist\", errno);\n\t\tgoto err;\n\t}\n\n\tfor (i = 0; i < plist->num; i++) {\n\t\tif (!plist_deleted (plist, i)) {\n\n\t\t\t/* EXTM3U */\n\t\t\tif (plist->items[i].tags)\n\t\t\t\tret = fprintf (file, \"#EXTINF:%d,%s\\r\\n\",\n\t\t\t\t\t\tplist->items[i].tags->time,\n\t\t\t\t\t\tplist->items[i].title_tags ?\n\t\t\t\t\t\tplist->items[i].title_tags\n\t\t\t\t\t\t: plist->items[i].title_file);\n\t\t\telse\n\t\t\t\tret = fprintf (file, \"#EXTINF:%d,%s\\r\\n\", 0,\n\t\t\t\t\t\tplist->items[i].title_file);\n\n\t\t\t/* file */\n\t\t\tif (ret >= 0)\n\t\t\t\tret = fprintf (file, \"%s\\r\\n\", plist->items[i].file);\n\n\t\t\tif (ret < 0) {\n\t\t\t\terror_errno (\"Error writing playlist\", errno);\n\t\t\t\tgoto err;\n\t\t\t}\n\t\t}\n\t}\n\n\tret = fclose (file);\n\tfile = NULL;\n\tif (ret)\n\t\terror_errno (\"Error writing playlist\", errno);\n\telse\n\t\tresult = 1;\n\nerr:\n\tif (file)\n\t\tfclose (file);\n\treturn result;\n}\n"
  },
  {
    "path": "playlist_file.h",
    "content": "#ifndef PLAYLIST_FILE_H\n#define PLAYLIST_FILE_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nint plist_load (struct plist *plist, const char *fname, const char *cwd,\n\t\tconst int load_serial);\nint plist_save (struct plist *plist, const char *file, const int save_serial);\nint is_plist_file (const char *name);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "protocol.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2003 - 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <string.h>\n#include <stdlib.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <sys/socket.h>\n#include <time.h>\n#include <assert.h>\n#include <unistd.h>\n#include <fcntl.h>\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"protocol.h\"\n#include \"playlist.h\"\n#include \"files.h\"\n\n/* Maximal socket name. */\n#define UNIX_PATH_MAX\t108\n#define SOCKET_NAME\t\"socket2\"\n\n#define nonblocking(fn, result, sock, buf, len) \\\n\tdo { \\\n\t\tlong flags = fcntl (sock, F_GETFL); \\\n\t\tif (flags == -1) \\\n\t\t\tfatal (\"Getting flags for socket failed: %s\", \\\n\t\t\t        xstrerror (errno)); \\\n\t\tflags |= O_NONBLOCK; \\\n\t\tif (fcntl (sock, F_SETFL, O_NONBLOCK) == -1) \\\n\t\t\tfatal (\"Setting O_NONBLOCK for the socket failed: %s\", \\\n\t\t\t        xstrerror (errno)); \\\n\t\tresult = fn (sock, buf, len, 0); \\\n\t\tflags &= ~O_NONBLOCK; \\\n\t\tif (fcntl (sock, F_SETFL, flags) == -1) \\\n\t\t\tfatal (\"Restoring flags for socket failed: %s\", \\\n\t\t\t        xstrerror (errno)); \\\n\t} while (0)\n\n/* Buffer used to send data in one bigger chunk instead of sending sigle\n * integer, string etc. values. */\nstruct packet_buf\n{\n\tchar *buf;\n\tsize_t allocated;\n\tsize_t len;\n};\n\n/* Create a socket name, return NULL if the name could not be created. */\nchar *socket_name ()\n{\n\tchar *socket_name = create_file_name (SOCKET_NAME);\n\n\tif (strlen(socket_name) > UNIX_PATH_MAX)\n\t\tfatal (\"Can't create socket name!\");\n\n\treturn socket_name;\n}\n\n/* Get an integer value from the socket, return == 0 on error. */\nint get_int (int sock, int *i)\n{\n\tssize_t res;\n\n\tres = recv (sock, i, sizeof(int), 0);\n\tif (res == -1)\n\t\tlog_errno (\"recv() failed when getting int\", errno);\n\n\treturn res == ssizeof(int) ? 1 : 0;\n}\n\n/* Get an integer value from the socket without blocking. */\nenum noblock_io_status get_int_noblock (int sock, int *i)\n{\n\tssize_t res;\n\tchar *err;\n\n\tnonblocking (recv, res, sock, i, sizeof (int));\n\n\tif (res == ssizeof (int))\n\t\treturn NB_IO_OK;\n\tif (res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))\n\t\treturn NB_IO_BLOCK;\n\n\terr = xstrerror (errno);\n\tlogit (\"recv() failed when getting int (res %zd): %s\", res, err);\n\tfree (err);\n\n\treturn NB_IO_ERR;\n}\n\n/* Send an integer value to the socket, return == 0 on error */\nint send_int (int sock, int i)\n{\n\tssize_t res;\n\n\tres = send (sock, &i, sizeof(int), 0);\n\tif (res == -1)\n\t\tlog_errno (\"send() failed\", errno);\n\n\treturn res == ssizeof(int) ? 1 : 0;\n}\n\n#if 0\n/* Get a long value from the socket, return == 0 on error. */\nstatic int get_long (int sock, long *i)\n{\n\tssize_t res;\n\n\tres = recv (sock, i, sizeof(long), 0);\n\tif (res == -1)\n\t\tlog_errno (\"recv() failed when getting int\", errno);\n\n\treturn res == ssizeof(long) ? 1 : 0;\n}\n#endif\n\n#if 0\n/* Send a long value to the socket, return == 0 on error */\nstatic int send_long (int sock, long i)\n{\n\tssize_t res;\n\n\tres = send (sock, &i, sizeof(long), 0);\n\tif (res == -1)\n\t\tlog_errno (\"send() failed\", errno);\n\n\treturn res == ssizeof(long) ? 1 : 0;\n}\n#endif\n\n/* Get the string from socket, return NULL on error. The memory is malloced. */\nchar *get_str (int sock)\n{\n\tint len, nread = 0;\n\tchar *str;\n\n\tif (!get_int(sock, &len))\n\t\treturn NULL;\n\n\tif (!RANGE(0, len, MAX_SEND_STRING)) {\n\t\tlogit (\"Bad string length.\");\n\t\treturn NULL;\n\t}\n\n\tstr = (char *)xmalloc (sizeof(char) * (len + 1));\n\twhile (nread < len) {\n\t\tssize_t res;\n\n\t\tres = recv (sock, str + nread, len - nread, 0);\n\t\tif (res == -1) {\n\t\t\tlog_errno (\"recv() failed when getting string\", errno);\n\t\t\tfree (str);\n\t\t\treturn NULL;\n\t\t}\n\t\tif (res == 0) {\n\t\t\tlogit (\"Unexpected EOF when getting string\");\n\t\t\tfree (str);\n\t\t\treturn NULL;\n\t\t}\n\t\tnread += res;\n\t}\n\tstr[len] = 0;\n\n\treturn str;\n}\n\nint send_str (int sock, const char *str)\n{\n\tint len;\n\n\tlen = strlen (str);\n\tif (!send_int (sock, len))\n\t\treturn 0;\n\n\tif (send (sock, str, len, 0) != len)\n\t\treturn 0;\n\n\treturn 1;\n}\n\n/* Get a time_t value from the socket, return == 0 on error. */\nint get_time (int sock, time_t *i)\n{\n\tssize_t res;\n\n\tres = recv (sock, i, sizeof(time_t), 0);\n\tif (res == -1)\n\t\tlog_errno (\"recv() failed when getting time_t\", errno);\n\n\treturn res == ssizeof(time_t) ? 1 : 0;\n}\n\n/* Send a time_t value to the socket, return == 0 on error */\nint send_time (int sock, time_t i)\n{\n\tssize_t res;\n\n\tres = send (sock, &i, sizeof(time_t), 0);\n\tif (res == -1)\n\t\tlog_errno (\"send() failed\", errno);\n\n\treturn res == ssizeof(time_t) ? 1 : 0;\n}\n\nstatic struct packet_buf *packet_buf_new ()\n{\n\tstruct packet_buf *b;\n\n\tb = (struct packet_buf *)xmalloc (sizeof(struct packet_buf));\n\tb->buf = (char *)xmalloc (1024);\n\tb->allocated = 1024;\n\tb->len = 0;\n\n\treturn b;\n}\n\nstatic void packet_buf_free (struct packet_buf *b)\n{\n\tassert (b != NULL);\n\n\tfree (b->buf);\n\tfree (b);\n}\n\n/* Make sure that there is at least len bytes free. */\nstatic void packet_buf_add_space (struct packet_buf *b, const size_t len)\n{\n\tassert (b != NULL);\n\n\tif (b->allocated < b->len + len) {\n\t\tb->allocated += len + 256; /* put some more space */\n\t\tb->buf = (char *)xrealloc (b->buf, b->allocated);\n\t}\n}\n\n/* Add an integer value to the buffer */\nstatic void packet_buf_add_int (struct packet_buf *b, const int n)\n{\n\tassert (b != NULL);\n\n\tpacket_buf_add_space (b, sizeof(n));\n\tmemcpy (b->buf + b->len, &n, sizeof(n));\n\tb->len += sizeof(n);\n}\n\n/* Add a string value to the buffer. */\nstatic void packet_buf_add_str (struct packet_buf *b, const char *str)\n{\n\tint str_len;\n\n\tassert (b != NULL);\n\tassert (str != NULL);\n\n\tstr_len = strlen (str);\n\n\tpacket_buf_add_int (b, str_len);\n\tpacket_buf_add_space (b, str_len * sizeof(char));\n\tmemcpy (b->buf + b->len, str, str_len * sizeof(char));\n\tb->len += str_len * sizeof(char);\n}\n\n/* Add a time_t value to the buffer. */\nstatic void packet_buf_add_time (struct packet_buf *b, const time_t n)\n{\n\tassert (b != NULL);\n\n\tpacket_buf_add_space (b, sizeof(n));\n\tmemcpy (b->buf + b->len, &n, sizeof(n));\n\tb->len += sizeof(n);\n}\n\n/* Add tags to the buffer. If tags == NULL, add empty tags. */\nvoid packet_buf_add_tags (struct packet_buf *b, const struct file_tags *tags)\n{\n\tassert (b != NULL);\n\n\tif (tags) {\n\t\tpacket_buf_add_str (b, tags->title ? tags->title : \"\");\n\t\tpacket_buf_add_str (b, tags->artist ? tags->artist : \"\");\n\t\tpacket_buf_add_str (b, tags->album ? tags->album : \"\");\n\t\tpacket_buf_add_int (b, tags->track);\n\t\tpacket_buf_add_int (b, tags->filled & TAGS_TIME ? tags->time : -1);\n\t\tpacket_buf_add_int (b, tags->filled);\n\t}\n\telse {\n\n\t\t/* empty tags: */\n\t\tpacket_buf_add_str (b, \"\"); /* title */\n\t\tpacket_buf_add_str (b, \"\"); /* artist */\n\t\tpacket_buf_add_str (b, \"\"); /* album */\n\t\tpacket_buf_add_int (b, -1); /* track */\n\t\tpacket_buf_add_int (b, -1); /* time */\n\t\tpacket_buf_add_int (b, 0); /* filled */\n\t}\n}\n\n/* Add an item to the buffer. */\nvoid packet_buf_add_item (struct packet_buf *b, const struct plist_item *item)\n{\n\tpacket_buf_add_str (b, item->file);\n\tpacket_buf_add_str (b, item->title_tags ? item->title_tags : \"\");\n\tpacket_buf_add_tags (b, item->tags);\n\tpacket_buf_add_time (b, item->mtime);\n}\n\n/* Send data to the socket. Return 0 on error. */\nstatic int send_all (int sock, const char *buf, const size_t size)\n{\n\tssize_t sent;\n\tsize_t send_pos = 0;\n\n\twhile (send_pos < size) {\n\t\tsent = send (sock, buf + send_pos, size - send_pos, 0);\n\t\tif (sent < 0) {\n\t\t\tlog_errno (\"Error while sending data\", errno);\n\t\t\treturn 0;\n\t\t}\n\t\tsend_pos += sent;\n\t}\n\n\treturn 1;\n}\n\n/* Send a playlist item to the socket. If item == NULL, send empty item mark\n * (end of playlist). Return 0 on error. */\nint send_item (int sock, const struct plist_item *item)\n{\n\tint res = 1;\n\tstruct packet_buf *b;\n\n\tif (!item) {\n\t\tif (!send_str(sock, \"\")) {\n\t\t\tlogit (\"Error while sending empty item\");\n\t\t\treturn 0;\n\t\t}\n\t\treturn 1;\n\t}\n\n\tb = packet_buf_new ();\n\tpacket_buf_add_item (b, item);\n\tif (!send_all(sock, b->buf, b->len)) {\n\t\tlogit (\"Error when sending item\");\n\t\tres = 0;\n\t}\n\n\tpacket_buf_free (b);\n\treturn res;\n}\n\nstruct file_tags *recv_tags (int sock)\n{\n\tstruct file_tags *tags = tags_new ();\n\n\tif (!(tags->title = get_str(sock))) {\n\t\tlogit (\"Error while receiving title\");\n\t\ttags_free (tags);\n\t\treturn NULL;\n\t}\n\n\tif (!(tags->artist = get_str(sock))) {\n\t\tlogit (\"Error while receiving artist\");\n\t\ttags_free (tags);\n\t\treturn NULL;\n\t}\n\n\tif (!(tags->album = get_str(sock))) {\n\t\tlogit (\"Error while receiving album\");\n\t\ttags_free (tags);\n\t\treturn NULL;\n\t}\n\n\tif (!get_int(sock, &tags->track)) {\n\t\tlogit (\"Error while receiving track\");\n\t\ttags_free (tags);\n\t\treturn NULL;\n\t}\n\n\tif (!get_int(sock, &tags->time)) {\n\t\tlogit (\"Error while receiving time\");\n\t\ttags_free (tags);\n\t\treturn NULL;\n\t}\n\n\tif (!get_int(sock, &tags->filled)) {\n\t\tlogit (\"Error while receiving 'filled'\");\n\t\ttags_free (tags);\n\t\treturn NULL;\n\t}\n\n\t/* Set NULL instead of empty tags. */\n\tif (!tags->title[0]) {\n\t\tfree (tags->title);\n\t\ttags->title = NULL;\n\t}\n\tif (!tags->artist[0]) {\n\t\tfree (tags->artist);\n\t\ttags->artist = NULL;\n\t}\n\tif (!tags->album[0]) {\n\t\tfree (tags->album);\n\t\ttags->album = NULL;\n\t}\n\n\treturn tags;\n}\n\n/* Send tags. If tags == NULL, send empty tags. Return 0 on error. */\nint send_tags (int sock, const struct file_tags *tags)\n{\n\tint res = 1;\n\tstruct packet_buf *b;\n\n\tb = packet_buf_new ();\n\tpacket_buf_add_tags (b, tags);\n\n\tif (!send_all(sock, b->buf, b->len))\n\t\tres = 0;\n\n\tpacket_buf_free (b);\n\treturn res;\n}\n\n/* Get a playlist item from the server.\n * The end of the playlist is indicated by item->file being an empty string.\n * The memory is malloc()ed.  Returns NULL on error. */\nstruct plist_item *recv_item (int sock)\n{\n\tstruct plist_item *item = plist_new_item ();\n\n\t/* get the file name */\n\tif (!(item->file = get_str(sock))) {\n\t\tlogit (\"Error while receiving file name\");\n\t\tfree (item);\n\t\treturn NULL;\n\t}\n\n\tif (item->file[0]) {\n\t\tif (!(item->title_tags = get_str(sock))) {\n\t\t\tlogit (\"Error while receiving tags title\");\n\t\t\tfree (item->file);\n\t\t\tfree (item);\n\t\t\treturn NULL;\n\t\t}\n\n\t\titem->type = file_type (item->file);\n\n\t\tif (!item->title_tags[0]) {\n\t\t\tfree (item->title_tags);\n\t\t\titem->title_tags = NULL;\n\t\t}\n\n\t\tif (!(item->tags = recv_tags(sock))) {\n\t\t\tlogit (\"Error while receiving tags\");\n\t\t\tfree (item->file);\n\t\t\tif (item->title_tags)\n\t\t\t\tfree (item->title_tags);\n\t\t\tfree (item);\n\t\t\treturn NULL;\n\t\t}\n\n\t\tif (!get_time(sock, &item->mtime)) {\n\t\t\tlogit (\"Error while receiving mtime\");\n\t\t\tif (item->title_tags)\n\t\t\t\tfree (item->title_tags);\n\t\t\tfree (item->file);\n\t\t\ttags_free (item->tags);\n\t\t\tfree (item);\n\t\t\treturn NULL;\n\t\t}\n\t}\n\n\treturn item;\n}\n\nstruct move_ev_data *recv_move_ev_data (int sock)\n{\n\tstruct move_ev_data *d;\n\n\td = (struct move_ev_data *)xmalloc (sizeof(struct move_ev_data));\n\n\tif (!(d->from = get_str(sock))) {\n\t\tlogit (\"Error while receiving 'from' data\");\n\t\tfree (d);\n\t\treturn NULL;\n\t}\n\n\tif (!(d->to = get_str(sock))) {\n\t\tlogit (\"Error while receiving 'to' data\");\n\t\tfree (d->from);\n\t\tfree (d);\n\t\treturn NULL;\n\t}\n\n\treturn d;\n}\n\n/* Push an event on the queue if it's not already there. */\nvoid event_push (struct event_queue *q, const int event, void *data)\n{\n\tassert (q != NULL);\n\n\tif (!q->head) {\n\t\tq->head = (struct event *)xmalloc (sizeof(struct event));\n\t\tq->head->next = NULL;\n\t\tq->head->type = event;\n\t\tq->head->data = data;\n\t\tq->tail = q->head;\n\t}\n\telse {\n\t\tassert (q->head != NULL);\n\t\tassert (q->tail != NULL);\n\t\tassert (q->tail->next == NULL);\n\n\t\tq->tail->next = (struct event *)xmalloc (\n\t\t\t\tsizeof(struct event));\n\t\tq->tail = q->tail->next;\n\t\tq->tail->next = NULL;\n\t\tq->tail->type = event;\n\t\tq->tail->data = data;\n\t}\n}\n\n/* Remove the first event from the queue (don't free the data field). */\nvoid event_pop (struct event_queue *q)\n{\n\tstruct event *e;\n\n\tassert (q != NULL);\n\tassert (q->head != NULL);\n\tassert (q->tail != NULL);\n\n\te = q->head;\n\tq->head = e->next;\n\tfree (e);\n\n\tif (q->tail == e)\n\t\tq->tail = NULL; /* the queue is empty */\n}\n\n/* Get the pointer to the first item in the queue or NULL if the queue is\n * empty. */\nstruct event *event_get_first (struct event_queue *q)\n{\n\tassert (q != NULL);\n\n\treturn q->head;\n}\n\nvoid free_tag_ev_data (struct tag_ev_response *d)\n{\n\tassert (d != NULL);\n\n\tfree (d->file);\n\ttags_free (d->tags);\n\tfree (d);\n}\n\nvoid free_move_ev_data (struct move_ev_data *m)\n{\n\tassert (m != NULL);\n\tassert (m->from != NULL);\n\tassert (m->to != NULL);\n\n\tfree (m->to);\n\tfree (m->from);\n\tfree (m);\n}\n\nstruct move_ev_data *move_ev_data_dup (const struct move_ev_data *m)\n{\n\tstruct move_ev_data *new;\n\n\tassert (m != NULL);\n\tassert (m->from != NULL);\n\tassert (m->to != NULL);\n\n\tnew = (struct move_ev_data *)xmalloc (sizeof(struct move_ev_data));\n\tnew->from = xstrdup (m->from);\n\tnew->to = xstrdup (m->to);\n\n\treturn new;\n}\n\n/* Free data associated with the event if any. */\nvoid free_event_data (const int type, void *data)\n{\n\tif (type == EV_PLIST_ADD || type == EV_QUEUE_ADD) {\n\t\tplist_free_item_fields ((struct plist_item *)data);\n\t\tfree (data);\n\t}\n\telse if (type == EV_FILE_TAGS)\n\t\tfree_tag_ev_data ((struct tag_ev_response *)data);\n\telse if (type == EV_PLIST_DEL || type == EV_STATUS_MSG\n\t\t\t|| type == EV_SRV_ERROR || type == EV_QUEUE_DEL)\n\t\tfree (data);\n\telse if (type == EV_PLIST_MOVE || type == EV_QUEUE_MOVE)\n\t\tfree_move_ev_data ((struct move_ev_data *)data);\n\telse if (data)\n\t\tabort (); /* BUG */\n}\n\n/* Free event queue content without the queue structure. */\nvoid event_queue_free (struct event_queue *q)\n{\n\tstruct event *e;\n\n\tassert (q != NULL);\n\n\twhile ((e = event_get_first(q))) {\n\t\tfree_event_data (e->type, e->data);\n\t\tevent_pop (q);\n\t}\n}\n\nvoid event_queue_init (struct event_queue *q)\n{\n\tassert (q != NULL);\n\n\tq->head = NULL;\n\tq->tail = NULL;\n}\n\n#if 0\n/* Search for an event of this type and return pointer to it or NULL if there\n * was no such event. */\nstruct event *event_search (struct event_queue *q, const int event)\n{\n\tstruct event *e;\n\n\tassert (q != NULL);\n\n\twhile ((e = q->head)) {\n\t\tif (e->type == event)\n\t\t\treturn e;\n\t\te = e->next;\n\t}\n\n\treturn NULL;\n}\n#endif\n\n/* Return != 0 if the queue is empty. */\nint event_queue_empty (const struct event_queue *q)\n{\n\tassert (q != NULL);\n\n\treturn q->head == NULL ? 1 : 0;\n}\n\n/* Make a packet buffer filled with the event (with data). */\nstatic struct packet_buf *make_event_packet (const struct event *e)\n{\n\tstruct packet_buf *b;\n\n\tassert (e != NULL);\n\n\tb = packet_buf_new ();\n\n\tpacket_buf_add_int (b, e->type);\n\n\tif (e->type == EV_PLIST_DEL\n\t\t\t|| e->type == EV_QUEUE_DEL\n\t\t\t|| e->type == EV_SRV_ERROR\n\t\t\t|| e->type == EV_STATUS_MSG) {\n\t\tassert (e->data != NULL);\n\t\tpacket_buf_add_str (b, e->data);\n\t}\n\telse if (e->type == EV_PLIST_ADD || e->type == EV_QUEUE_ADD) {\n\t\tassert (e->data != NULL);\n\t\tpacket_buf_add_item (b, e->data);\n\t}\n\telse if (e->type == EV_FILE_TAGS) {\n\t\tstruct tag_ev_response *r;\n\n\t\tassert (e->data != NULL);\n\t\tr = e->data;\n\n\t\tpacket_buf_add_str (b, r->file);\n\t\tpacket_buf_add_tags (b, r->tags);\n\t}\n\telse if (e->type == EV_PLIST_MOVE || e->type == EV_QUEUE_MOVE) {\n\t\tstruct move_ev_data *m;\n\n\t\tassert (e->data != NULL);\n\n\t\tm = (struct move_ev_data *)e->data;\n\t\tpacket_buf_add_str (b, m->from);\n\t\tpacket_buf_add_str (b, m->to);\n\t}\n\telse if (e->data)\n\t\tabort (); /* BUG */\n\n\treturn b;\n}\n\n/* Send the first event from the queue and remove it on success.  If the\n * operation would block return NB_IO_BLOCK.  Return NB_IO_ERR on error\n * or NB_IO_OK on success. */\nenum noblock_io_status event_send_noblock (int sock, struct event_queue *q)\n{\n\tssize_t res;\n\tchar *err;\n\tstruct packet_buf *b;\n\tenum noblock_io_status result;\n\n\tassert (q != NULL);\n\tassert (!event_queue_empty(q));\n\n\tb = make_event_packet (event_get_first(q));\n\n\t/* We must do it in one send() call to be able to handle blocking. */\n\tnonblocking (send, res, sock, b->buf, b->len);\n\n\tif (res == (ssize_t)b->len) {\n\t\tstruct event *e;\n\n\t\te = event_get_first (q);\n\t\tfree_event_data (e->type, e->data);\n\t\tevent_pop (q);\n\n\t\tresult = NB_IO_OK;\n\t\tgoto exit;\n\t}\n\n\tif (res < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {\n\t\tlogit (\"Sending event would block\");\n\t\tresult = NB_IO_BLOCK;\n\t\tgoto exit;\n\t}\n\n\terr = xstrerror (errno);\n\tlogit (\"send()ing event failed (%zd): %s\", res, err);\n\tfree (err);\n\tresult = NB_IO_ERR;\n\nexit:\n\tpacket_buf_free (b);\n\treturn result;\n}\n"
  },
  {
    "path": "protocol.h",
    "content": "#ifndef PROTOCOL_H\n#define PROTOCOL_H\n\n#include \"playlist.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct event\n{\n\tint type;\t/* type of the event (one of EV_*) */\n\tvoid *data;\t/* optional data associated with the event */\n\tstruct event *next;\n};\n\nstruct event_queue\n{\n\tstruct event *head;\n\tstruct event *tail;\n};\n\n/* Used as data field in the event queue for EV_FILE_TAGS. */\nstruct tag_ev_response\n{\n\tchar *file;\n\tstruct file_tags *tags;\n};\n\n/* Used as data field in the event queue for EV_PLIST_MOVE. */\nstruct move_ev_data\n{\n\t/* Two files that are to be exchanged. */\n\tchar *from;\n\tchar *to;\n};\n\n/* Status of nonblock sending/receiving function. */\nenum noblock_io_status\n{\n\tNB_IO_OK,\n\tNB_IO_BLOCK,\n\tNB_IO_ERR\n};\n\n/* Definition of events sent by server to the client. */\n#define EV_STATE\t0x01 /* server has changed the state */\n#define EV_CTIME\t0x02 /* current time of the song has changed */\n#define EV_SRV_ERROR\t0x04 /* an error occurred */\n#define EV_BUSY\t\t0x05 /* another client is connected to the server */\n#define EV_DATA\t\t0x06 /* data in response to a request will arrive */\n#define EV_BITRATE\t0x07 /* the bitrate has changed */\n#define EV_RATE\t\t0x08 /* the rate has changed */\n#define EV_CHANNELS\t0x09 /* the number of channels has changed */\n#define EV_EXIT\t\t0x0a /* the server is about to exit */\n#define EV_PONG\t\t0x0b /* response for CMD_PING */\n#define EV_OPTIONS\t0x0c /* the options has changed */\n#define EV_SEND_PLIST\t0x0d /* request for sending the playlist */\n#define EV_TAGS\t\t0x0e /* tags for the current file have changed */\n#define EV_STATUS_MSG\t0x0f /* followed by a status message */\n#define EV_MIXER_CHANGE\t0x10 /* the mixer channel was changed */\n#define EV_FILE_TAGS\t0x11 /* tags in a response for tags request */\n#define EV_AVG_BITRATE  0x12 /* average bitrate has changed (new song) */\n#define EV_AUDIO_START\t0x13 /* playing of audio has started */\n#define EV_AUDIO_STOP\t0x14 /* playing of audio has stopped */\n\n/* Events caused by a client that wants to modify the playlist (see\n * CMD_CLI_PLIST* commands). */\n#define EV_PLIST_ADD\t0x50 /* add an item, followed by the file name */\n#define EV_PLIST_DEL\t0x51 /* delete an item, followed by the file name */\n#define EV_PLIST_MOVE\t0x52 /* move an item, followed by 2 file names */\n#define EV_PLIST_CLEAR\t0x53 /* clear the playlist */\n\n/* These events, though similar to the four previous are caused by server\n * which takes care of clients' queue synchronization. */\n#define EV_QUEUE_ADD\t0x54\n#define EV_QUEUE_DEL\t0x55\n#define EV_QUEUE_MOVE\t0x56\n#define EV_QUEUE_CLEAR\t0x57\n\n/* State of the server. */\n#define STATE_PLAY\t0x01\n#define STATE_STOP\t0x02\n#define STATE_PAUSE\t0x03\n\n/* Definition of server commands. */\n#define CMD_PLAY\t0x00 /* play the first element on the list */\n#define CMD_LIST_CLEAR\t0x01 /* clear the list */\n#define CMD_LIST_ADD\t0x02 /* add an item to the list */\n#define CMD_STOP\t0x04 /* stop playing */\n#define CMD_PAUSE\t0x05 /* pause */\n#define CMD_UNPAUSE\t0x06 /* unpause */\n#define CMD_SET_OPTION\t0x07 /* set an option */\n#define CMD_GET_OPTION\t0x08 /* get an option */\n#define CMD_GET_CTIME\t0x0d /* get the current song time */\n#define CMD_GET_SNAME\t0x0f /* get the stream file name */\n#define CMD_NEXT\t0x10 /* start playing next song if available */\n#define CMD_QUIT\t0x11 /* shutdown the server */\n#define CMD_SEEK\t0x12 /* seek in the current stream */\n#define CMD_GET_STATE\t0x13 /* get the state */\n#define CMD_DISCONNECT\t0x15 /* disconnect from the server */\n#define CMD_GET_BITRATE\t0x16 /* get the bitrate */\n#define CMD_GET_RATE\t0x17 /* get the rate */\n#define CMD_GET_CHANNELS\t0x18 /* get the number of channels */\n#define CMD_PING\t0x19 /* request for EV_PONG */\n#define CMD_GET_MIXER\t0x1a /* get the volume level */\n#define CMD_SET_MIXER\t0x1b /* set the volume level */\n#define CMD_DELETE\t0x1c /* delete an item from the playlist */\n#define CMD_SEND_PLIST_EVENTS 0x1d /* request for playlist events */\n#define CMD_PREV\t0x20 /* start playing previous song if available */\n#define CMD_SEND_PLIST\t0x21 /* send the playlist to the requesting client */\n#define CMD_GET_PLIST\t0x22 /* get the playlist from one of the clients */\n#define CMD_CAN_SEND_PLIST\t0x23 /* mark the client as able to send\n\t\t\t\t\tplaylist */\n#define CMD_CLI_PLIST_ADD\t0x24 /* add an item to the client's playlist */\n#define CMD_CLI_PLIST_DEL\t0x25 /* delete an item from the client's\n\t\t\t\t\tplaylist */\n#define CMD_CLI_PLIST_CLEAR\t0x26 /* clear the client's playlist */\n#define CMD_GET_SERIAL\t0x27 /* get an unique serial number */\n#define CMD_PLIST_SET_SERIAL\t0x28 /* assign a serial number to the server's\n\t\t\t\t\tplaylist */\n#define CMD_LOCK\t0x29 /* acquire a lock */\n#define CMD_UNLOCK\t0x2a /* release the lock */\n#define CMD_PLIST_GET_SERIAL\t0x2b /* get the serial number of the server's\n\t\t\t\t\tplaylist */\n#define CMD_GET_TAGS\t0x2c /* get tags for the currently played file */\n#define CMD_TOGGLE_MIXER_CHANNEL\t0x2d /* toggle the mixer channel */\n#define CMD_GET_MIXER_CHANNEL_NAME\t0x2e /* get the mixer channel's name */\n#define CMD_GET_FILE_TAGS\t0x2f\t/* get tags for the specified file */\n#define CMD_ABORT_TAGS_REQUESTS\t0x30\t/* abort previous CMD_GET_FILE_TAGS\n\t\t\t\t\t   requests up to some file */\n#define CMD_CLI_PLIST_MOVE\t0x31\t/* move an item */\n#define CMD_LIST_MOVE\t\t0x32\t/* move an item */\n#define CMD_GET_AVG_BITRATE\t0x33\t/* get the average bitrate */\n\n#define CMD_TOGGLE_SOFTMIXER    0x34    /* toggle use of softmixer */\n#define CMD_TOGGLE_EQUALIZER    0x35    /* toggle use of equalizer */\n#define CMD_EQUALIZER_REFRESH   0x36    /* refresh EQ-presets */\n#define CMD_EQUALIZER_PREV      0x37    /* select previous eq-preset */\n#define CMD_EQUALIZER_NEXT      0x38    /* select next eq-preset */\n\n#define CMD_TOGGLE_MAKE_MONO    0x39    /* toggle mono mixing */\n#define CMD_JUMP_TO\t0x3a /* jumps to a some position in the current stream */\n#define CMD_QUEUE_ADD\t0x3b /* add an item to the queue */\n#define CMD_QUEUE_DEL\t0x3c /* delete an item from the queue */\n#define CMD_QUEUE_MOVE\t0x3d /* move an item in the queue */\n#define CMD_QUEUE_CLEAR\t0x3e /* clear the queue */\n#define CMD_GET_QUEUE\t0x3f /* request the queue from the server */\n\nchar *socket_name ();\nint get_int (int sock, int *i);\nenum noblock_io_status get_int_noblock (int sock, int *i);\nint send_int (int sock, int i);\nchar *get_str (int sock);\nint send_str (int sock, const char *str);\nint get_time (int sock, time_t *i);\nint send_time (int sock, time_t i);\nint send_item (int sock, const struct plist_item *item);\nstruct plist_item *recv_item (int sock);\nstruct file_tags *recv_tags (int sock);\nint send_tags (int sock, const struct file_tags *tags);\n\nvoid event_queue_init (struct event_queue *q);\nvoid event_queue_free (struct event_queue *q);\nvoid free_event_data (const int type, void *data);\nstruct event *event_get_first (struct event_queue *q);\nvoid event_pop (struct event_queue *q);\nvoid event_push (struct event_queue *q, const int event, void *data);\nint event_queue_empty (const struct event_queue *q);\nenum noblock_io_status event_send_noblock (int sock, struct event_queue *q);\nvoid free_tag_ev_data (struct tag_ev_response *d);\nvoid free_move_ev_data (struct move_ev_data *m);\nstruct move_ev_data *move_ev_data_dup (const struct move_ev_data *m);\nstruct move_ev_data *recv_move_ev_data (int sock);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "rbtree.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n * Functions based on pseudocode from \"Introduction to Algorithms\"\n * The only modification is that we avoid to modify fields of the nil value.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdlib.h>\n#include <assert.h>\n\n#include \"common.h\"\n#include \"rbtree.h\"\n\nenum rb_color { RB_RED, RB_BLACK };\n\nstruct rb_node\n{\n\tstruct rb_node *left;\n\tstruct rb_node *right;\n\tstruct rb_node *parent;\n\tenum rb_color color;\n\tconst void *data;\n};\n\nstruct rb_tree\n{\n\tstruct rb_node *root;\n\n\t/* compare function for two data elements */\n\trb_t_compare *cmp_fn;\n\n\t/* compare function for data element and a key value */\n\trb_t_compare_key *cmp_key_fn;\n\n\t/* pointer to additional data passed to compare functions */\n\tconst void *adata;\n};\n\n/* item used as a null value */\nstatic struct rb_node rb_null = { NULL, NULL, NULL, RB_BLACK, NULL };\n\nstatic void rb_left_rotate (struct rb_node **root, struct rb_node *x)\n{\n\tstruct rb_node *y = x->right;\n\n\tassert (y != &rb_null);\n\n\tx->right = y->left;\n\tif (y->left != &rb_null)\n\t\ty->left->parent = x;\n\ty->parent = x->parent;\n\n\tif (x->parent == &rb_null)\n\t\t*root = y;\n\telse {\n\t\tif (x == x->parent->left)\n\t\t\tx->parent->left = y;\n\t\telse\n\t\t\tx->parent->right = y;\n\t}\n\n\ty->left = x;\n\tx->parent = y;\n}\n\nstatic void rb_right_rotate (struct rb_node **root, struct rb_node *x)\n{\n\tstruct rb_node *y = x->left;\n\n\tassert (y != &rb_null);\n\n\tx->left = y->right;\n\tif (y->right != &rb_null)\n\t\ty->right->parent = x;\n\ty->parent = x->parent;\n\n\tif (x->parent == &rb_null)\n\t\t*root = y;\n\telse {\n\t\tif (x == x->parent->right)\n\t\t\tx->parent->right = y;\n\t\telse\n\t\t\tx->parent->left = y;\n\t}\n\n\ty->right = x;\n\tx->parent = y;\n}\n\nstatic void rb_insert_fixup (struct rb_node **root, struct rb_node *z)\n{\n\twhile (z->parent->color == RB_RED)\n\t\tif (z->parent == z->parent->parent->left) {\n\t\t\tstruct rb_node *y = z->parent->parent->right;\n\n\t\t\tif (y->color == RB_RED) {\n\t\t\t\tz->parent->color = RB_BLACK;\n\t\t\t\ty->color = RB_BLACK;\n\t\t\t\tz->parent->parent->color = RB_RED;\n\t\t\t\tz = z->parent->parent;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (z == z->parent->right) {\n\t\t\t\t\tz = z->parent;\n\t\t\t\t\trb_left_rotate (root, z);\n\t\t\t\t}\n\n\t\t\t\tz->parent->color = RB_BLACK;\n\t\t\t\tz->parent->parent->color = RB_RED;\n\t\t\t\trb_right_rotate (root, z->parent->parent);\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tstruct rb_node *y = z->parent->parent->left;\n\n\t\t\tif (y->color == RB_RED) {\n\t\t\t\tz->parent->color = RB_BLACK;\n\t\t\t\ty->color = RB_BLACK;\n\t\t\t\tz->parent->parent->color = RB_RED;\n\t\t\t\tz = z->parent->parent;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (z == z->parent->left) {\n\t\t\t\t\tz = z->parent;\n\t\t\t\t\trb_right_rotate (root, z);\n\t\t\t\t}\n\n\t\t\t\tz->parent->color = RB_BLACK;\n\t\t\t\tz->parent->parent->color = RB_RED;\n\t\t\t\trb_left_rotate (root, z->parent->parent);\n\t\t\t}\n\t\t}\n\n\t(*root)->color = RB_BLACK;\n}\n\nstatic void rb_delete_fixup (struct rb_node **root, struct rb_node *x,\n\t\tstruct rb_node *parent)\n{\n\tstruct rb_node *w;\n\n\twhile (x != *root && x->color == RB_BLACK) {\n\t\tif (x == parent->left) {\n\t\t\tw = parent->right;\n\n\t\t\tif (w->color == RB_RED) {\n\t\t\t\tw->color = RB_BLACK;\n\t\t\t\tparent->color = RB_RED;\n\t\t\t\trb_left_rotate (root, parent);\n\t\t\t\tw = parent->right;\n\t\t\t}\n\n\t\t\tif (w->left->color == RB_BLACK\n\t\t\t\t\t&& w->right->color == RB_BLACK) {\n\t\t\t\tw->color = RB_RED;\n\t\t\t\tx = parent;\n\t\t\t\tparent = x->parent;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (w->right->color == RB_BLACK) {\n\t\t\t\t\tw->left->color = RB_BLACK;\n\t\t\t\t\tw->color = RB_RED;\n\t\t\t\t\trb_right_rotate (root, w);\n\t\t\t\t\tw = parent->right;\n\t\t\t\t}\n\n\t\t\t\tw->color = parent->color;\n\t\t\t\tparent->color = RB_BLACK;\n\t\t\t\tw->right->color = RB_BLACK;\n\t\t\t\trb_left_rotate (root, parent);\n\t\t\t\tx = *root;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tw = parent->left;\n\n\t\t\tif (w->color == RB_RED) {\n\t\t\t\tw->color = RB_BLACK;\n\t\t\t\tparent->color = RB_RED;\n\t\t\t\trb_right_rotate (root, parent);\n\t\t\t\tw = parent->left;\n\t\t\t}\n\n\t\t\tif (w->right->color == RB_BLACK\n\t\t\t\t\t&& w->left->color == RB_BLACK) {\n\t\t\t\tw->color = RB_RED;\n\t\t\t\tx = parent;\n\t\t\t\tparent = x->parent;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (w->left->color == RB_BLACK) {\n\t\t\t\t\tw->right->color = RB_BLACK;\n\t\t\t\t\tw->color = RB_RED;\n\t\t\t\t\trb_left_rotate (root, w);\n\t\t\t\t\tw = parent->left;\n\t\t\t\t}\n\n\t\t\t\tw->color = parent->color;\n\t\t\t\tparent->color = RB_BLACK;\n\t\t\t\tw->left->color = RB_BLACK;\n\t\t\t\trb_right_rotate (root, parent);\n\t\t\t\tx = *root;\n\t\t\t}\n\t\t}\n\n\t}\n\n\tx->color = RB_BLACK;\n}\n\nvoid rb_insert (struct rb_tree *t, void *data)\n{\n\tstruct rb_node *x, *y, *z;\n\n\tassert (t != NULL);\n\tassert (t->root != NULL);\n\n\tx = t->root;\n\ty = &rb_null;\n\tz = (struct rb_node *)xmalloc (sizeof(struct rb_node));\n\n\tz->data = data;\n\n\twhile (x != &rb_null) {\n\t\tint cmp = t->cmp_fn (z->data, x->data, t->adata);\n\n\t\ty = x;\n\t\tif (cmp < 0)\n\t\t\tx = x->left;\n\t\telse if (cmp > 0)\n\t\t\tx = x->right;\n\t\telse\n\t\t\tabort ();\n\t}\n\n\tz->parent = y;\n\tif (y == &rb_null)\n\t\tt->root = z;\n\telse {\n\t\tif (t->cmp_fn (z->data, y->data, t->adata) < 0)\n\t\t\ty->left = z;\n\t\telse\n\t\t\ty->right = z;\n\t}\n\n\tz->left = &rb_null;\n\tz->right = &rb_null;\n\tz->color = RB_RED;\n\n\trb_insert_fixup (&t->root, z);\n}\n\nstruct rb_node *rb_search (struct rb_tree *t, const void *key)\n{\n\tstruct rb_node *x;\n\n\tassert (t != NULL);\n\tassert (t->root != NULL);\n\tassert (key != NULL);\n\n\tx = t->root;\n\n\twhile (x != &rb_null) {\n\t\tint cmp = t->cmp_key_fn (key, x->data, t->adata);\n\n\t\tif (cmp < 0)\n\t\t\tx = x->left;\n\t\telse if (cmp > 0)\n\t\t\tx = x->right;\n\t\telse\n\t\t\tbreak;\n\t}\n\n\treturn x;\n}\n\nint rb_is_null (const struct rb_node *n)\n{\n\treturn n == &rb_null;\n}\n\nconst void *rb_get_data (const struct rb_node *n)\n{\n\treturn n->data;\n}\n\nvoid rb_set_data (struct rb_node *n, const void *data)\n{\n\tn->data = data;\n}\n\nstatic struct rb_node *rb_min_internal (struct rb_node *n)\n{\n\tif (n == &rb_null)\n\t\treturn &rb_null;\n\n\twhile (n->left != &rb_null)\n\t\tn = n->left;\n\n\treturn n;\n}\n\nstruct rb_node *rb_min (struct rb_tree *t)\n{\n\tassert (t != NULL);\n\tassert (t->root != NULL);\n\n\treturn rb_min_internal (t->root);\n}\n\nstruct rb_node *rb_next (struct rb_node *x)\n{\n\tstruct rb_node *y;\n\n\tif (x->right != &rb_null)\n\t\treturn rb_min_internal (x->right);\n\n\ty = x->parent;\n\twhile (y != &rb_null && x == y->right) {\n\t\tx = y;\n\t\ty = y->parent;\n\t}\n\n\treturn y;\n}\n\nvoid rb_delete (struct rb_tree *t, const void *key)\n{\n\tstruct rb_node *z;\n\n\tassert (t != NULL);\n\tassert (t->root != NULL);\n\tassert (key != NULL);\n\n\tz = rb_search (t, key);\n\n\tif (z != &rb_null) {\n\t\tstruct rb_node *x, *y, *parent;\n\n\t\tif (z->left == &rb_null || z->right == &rb_null)\n\t\t\ty = z;\n\t\telse\n\t\t\ty = rb_next (z);\n\n\t\tif (y->left != &rb_null)\n\t\t\tx = y->left;\n\t\telse\n\t\t\tx = y->right;\n\n\t\tparent = y->parent;\n\t\tif (x != &rb_null)\n\t\t\tx->parent = parent;\n\n\t\tif (y->parent == &rb_null)\n\t\t\tt->root = x;\n\t\telse {\n\t\t\tif (y == y->parent->left)\n\t\t\t\ty->parent->left = x;\n\t\t\telse\n\t\t\t\ty->parent->right = x;\n\t\t}\n\n\t\tif (y != z)\n\t\t\tz->data = y->data;\n\n\t\tif (y->color == RB_BLACK)\n\t\t\trb_delete_fixup (&t->root, x, parent);\n\n\t\tfree (y);\n\t}\n}\n\nstruct rb_tree *rb_tree_new (rb_t_compare *cmp_fn,\n                             rb_t_compare_key *cmp_key_fn,\n                             const void *adata)\n{\n\tstruct rb_tree *t;\n\n\tassert (cmp_fn != NULL);\n\tassert (cmp_key_fn != NULL);\n\n\tt = xmalloc (sizeof (*t));\n\n\tt->root = &rb_null;\n\tt->cmp_fn = cmp_fn;\n\tt->cmp_key_fn = cmp_key_fn;\n\tt->adata = adata;\n\n\treturn t;\n}\n\nstatic void rb_destroy (struct rb_node *n)\n{\n\tif (n != &rb_null) {\n\t\trb_destroy (n->right);\n\t\trb_destroy (n->left);\n\t\tfree (n);\n\t}\n}\n\nvoid rb_tree_clear (struct rb_tree *t)\n{\n\tassert (t != NULL);\n\tassert (t->root != NULL);\n\n\tif (t->root != &rb_null) {\n\t\trb_destroy (t->root->left);\n\t\trb_destroy (t->root->right);\n\t\tfree (t->root);\n\t\tt->root = &rb_null;\n\t}\n}\n\nvoid rb_tree_free (struct rb_tree *t)\n{\n\tassert (t != NULL);\n\tassert (t->root != NULL);\n\n\trb_tree_clear (t);\n\tfree (t);\n}\n"
  },
  {
    "path": "rbtree.h",
    "content": "#ifndef RBTREE_H\n#define RBTREE_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\ntypedef int rb_t_compare (const void *, const void *, const void *);\ntypedef int rb_t_compare_key (const void *, const void *, const void *);\n\nstruct rb_tree;\nstruct rb_node;\n\n/* Whole-of-tree functions. */\nstruct rb_tree *rb_tree_new (rb_t_compare *cmp_fn,\n                             rb_t_compare_key *cmp_key_fn,\n                             const void *adata);\nvoid rb_tree_clear (struct rb_tree *t);\nvoid rb_tree_free (struct rb_tree *t);\n\n/* Individual node functions. */\nvoid rb_delete (struct rb_tree *t, const void *key);\nstruct rb_node *rb_next (struct rb_node *x);\nstruct rb_node *rb_min (struct rb_tree *t);\nint rb_is_null (const struct rb_node *n);\nconst void *rb_get_data (const struct rb_node *n);\nvoid rb_set_data (struct rb_node *n, const void *data);\nstruct rb_node *rb_search (struct rb_tree *t, const void *key);\nvoid rb_insert (struct rb_tree *t, void *data);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "rcc.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005,2011 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdlib.h>\n\n#ifdef HAVE_RCC\n# include <librcc.h>\n#endif\n\n#include <assert.h>\n\n#include \"rcc.h\"\n\nchar *rcc_reencode (char *str)\n{\n\tchar *result = str;\n\n\tassert (str != NULL);\n\n#ifdef HAVE_RCC\n\trcc_string rccstring;\n\n\trccstring = rccFrom (NULL, 0, str);\n\tif (rccstring) {\n\t\tif (*rccstring) {\n\t\t\tchar *reencoded;\n\n\t\t\treencoded = rccToCharset (NULL, \"UTF-8\", rccstring);\n\t\t\tif (reencoded) {\n\t\t    \tfree (result);\n\t\t    \tresult = reencoded;\n\t\t\t}\n\t\t}\n\n\t\tfree (rccstring);\n\t}\n#endif /* HAVE_RCC */\n\n\treturn result;\n}\n\nvoid rcc_init ()\n{\n#ifdef HAVE_RCC\n\trcc_class classes[] = {\n\t\t{\"input\", RCC_CLASS_STANDARD, NULL, NULL, \"Input Encoding\", 0},\n\t\t{\"output\", RCC_CLASS_KNOWN, NULL, NULL, \"Output Encoding\", 0},\n\t\t{NULL, 0, NULL, NULL, NULL, 0}\n\t};\n\n\trccInit ();\n\trccInitDefaultContext (NULL, 0, 0, classes, 0);\n\trccLoad (NULL, \"moc\");\n\trccSetOption (NULL, RCC_OPTION_TRANSLATE,\n\t                    RCC_OPTION_TRANSLATE_SKIP_PARRENT);\n\trccSetOption (NULL, RCC_OPTION_AUTODETECT_LANGUAGE, 1);\n#endif /* HAVE_RCC */\n}\n\nvoid rcc_cleanup ()\n{\n#ifdef HAVE_RCC\n\trccFree ();\n#endif /* HAVE_RCC */\n}\n"
  },
  {
    "path": "rcc.h",
    "content": "#ifndef RCC_H\n#define RCC_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nchar *rcc_reencode (char *);\nvoid rcc_init ();\nvoid rcc_cleanup ();\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "server.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2003 - 2005 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <sys/socket.h>\n#include <sys/select.h>\n#ifdef HAVE_GETRLIMIT\n# include <sys/resource.h>\n#endif\n#include <sys/un.h>\n#include <time.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <string.h>\n#include <strings.h>\n#include <signal.h>\n#include <errno.h>\n#include <stdarg.h>\n#include <pthread.h>\n#include <assert.h>\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"protocol.h\"\n#include \"audio.h\"\n#include \"oss.h\"\n#include \"options.h\"\n#include \"server.h\"\n#include \"playlist.h\"\n#include \"tags_cache.h\"\n#include \"files.h\"\n#include \"softmixer.h\"\n#include \"equalizer.h\"\n\n#define SERVER_LOG\t\"mocp_server_log\"\n#define PID_FILE\t\"pid\"\n\nstruct client\n{\n\tint socket; \t\t/* -1 if inactive */\n\tint wants_plist_events;\t/* requested playlist events? */\n\tstruct event_queue events;\n\tpthread_mutex_t events_mtx;\n\tint requests_plist;\t/* is the client waiting for the playlist? */\n\tint can_send_plist;\t/* can this client send a playlist? */\n\tint lock;\t\t/* is this client locking us? */\n\tint serial;\t\t/* used for generating unique serial numbers */\n};\n\nstatic struct client clients[CLIENTS_MAX];\n\n/* Thread ID of the server thread. */\nstatic pthread_t server_tid;\n\n/* Pipe used to wake up the server from select() from another thread. */\nstatic int wake_up_pipe[2];\n\n/* Socket used to accept incoming client connections. */\nstatic int server_sock = -1;\n\n/* Set to 1 when a signal arrived causing the program to exit. */\nstatic volatile int server_quit = 0;\n\n/* Information about currently played file */\nstatic struct {\n\tint avg_bitrate;\n\tint bitrate;\n\tint rate;\n\tint channels;\n} sound_info = {\n\t-1,\n\t-1,\n\t-1,\n\t-1\n};\n\nstatic struct tags_cache *tags_cache;\n\nextern char **environ;\n\nstatic void write_pid_file ()\n{\n\tchar *fname = create_file_name (PID_FILE);\n\tFILE *file;\n\n\tif ((file = fopen(fname, \"w\")) == NULL)\n\t\tfatal (\"Can't open pid file for writing: %s\", xstrerror (errno));\n\tfprintf (file, \"%d\\n\", getpid());\n\tfclose (file);\n}\n\n/* Check if there is a pid file and if it is valid, return the pid, else 0 */\nstatic pid_t check_pid_file ()\n{\n\tFILE *file;\n\tpid_t pid;\n\tchar *fname = create_file_name (PID_FILE);\n\n\t/* Read the pid file */\n\tif ((file = fopen(fname, \"r\")) == NULL)\n\t\treturn 0;\n\tif (fscanf(file, \"%d\", &pid) != 1) {\n\t\tfclose (file);\n\t\treturn 0;\n\t}\n\tfclose (file);\n\n\treturn pid;\n}\n\nstatic void sig_chld (int sig LOGIT_ONLY)\n{\n\tint saved_errno;\n\tpid_t rc;\n\n\tlog_signal (sig);\n\n\tsaved_errno = errno;\n\tdo {\n\t\trc = waitpid (-1, NULL, WNOHANG);\n\t} while (rc > 0);\n\terrno = saved_errno;\n}\n\nstatic void sig_exit (int sig)\n{\n\tlog_signal (sig);\n\tserver_quit = 1;\n\n\t// FIXME (JCF): pthread_*() are not async-signal-safe and\n\t//              should not be used within signal handlers.\n\tif (!pthread_equal (server_tid, pthread_self()))\n\t\tpthread_kill (server_tid, sig);\n}\n\nstatic void clients_init ()\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++) {\n\t\tclients[i].socket = -1;\n\t\tpthread_mutex_init (&clients[i].events_mtx, NULL);\n\t}\n}\n\nstatic void clients_cleanup ()\n{\n\tint i, rc;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++) {\n\t\tclients[i].socket = -1;\n\t\trc = pthread_mutex_destroy (&clients[i].events_mtx);\n\t\tif (rc != 0)\n\t\t\tlog_errno (\"Can't destroy events mutex\", rc);\n\t}\n}\n\n/* Add a client to the list, return 1 if ok, 0 on error (max clients exceeded) */\nstatic int add_client (int sock)\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].socket == -1) {\n\t\t\tclients[i].wants_plist_events = 0;\n\t\t\tLOCK (clients[i].events_mtx);\n\t\t\tevent_queue_free (&clients[i].events);\n\t\t\tevent_queue_init (&clients[i].events);\n\t\t\tUNLOCK (clients[i].events_mtx);\n\t\t\tclients[i].socket = sock;\n\t\t\tclients[i].requests_plist = 0;\n\t\t\tclients[i].can_send_plist = 0;\n\t\t\tclients[i].lock = 0;\n\t\t\ttags_cache_clear_queue (tags_cache, i);\n\t\t\treturn 1;\n\t\t}\n\n\treturn 0;\n}\n\n/* Return index of a client that has a lock acquired. Return -1 if there is no\n * lock. */\nstatic int locking_client ()\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].socket != -1 && clients[i].lock)\n\t\t\treturn i;\n\treturn -1;\n}\n\n/* Acquire a lock for this client. Return 0 on error. */\nstatic int client_lock (struct client *cli)\n{\n\tif (cli->lock) {\n\t\tlogit (\"Client wants deadlock\");\n\t\treturn 0;\n\t}\n\n\tassert (locking_client() == -1);\n\n\tcli->lock = 1;\n\tlogit (\"Lock acquired for client with fd %d\", cli->socket);\n\treturn 1;\n}\n\n/* Return != 0 if this client holds a lock. */\nstatic int is_locking (const struct client *cli)\n{\n\treturn cli->lock;\n}\n\n/* Release the lock hold by the client. Return 0 on error. */\nstatic int client_unlock (struct client *cli)\n{\n\tif (!cli->lock) {\n\t\tlogit (\"Client wants to unlock when there is no lock\");\n\t\treturn 0;\n\t}\n\n\tcli->lock = 0;\n\tlogit (\"Lock released by client with fd %d\", cli->socket);\n\treturn 1;\n}\n\n/* Return the client index from the clients table. */\nstatic int client_index (const struct client *cli)\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].socket == cli->socket)\n\t\t\treturn i;\n\treturn -1;\n}\n\nstatic void del_client (struct client *cli)\n{\n\tcli->socket = -1;\n\tLOCK (cli->events_mtx);\n\tevent_queue_free (&cli->events);\n\ttags_cache_clear_queue (tags_cache, client_index(cli));\n\tUNLOCK (cli->events_mtx);\n}\n\n/* Check if the process with given PID exists. Return != 0 if so. */\nstatic int valid_pid (const pid_t pid)\n{\n\treturn kill(pid, 0) == 0 ? 1 : 0;\n}\n\nstatic void wake_up_server ()\n{\n\tint w = 1;\n\n\tdebug (\"Waking up the server\");\n\n\tif (write(wake_up_pipe[1], &w, sizeof(w)) < 0)\n\t\tlog_errno (\"Can't wake up the server: (write() failed)\", errno);\n}\n\nstatic void redirect_output (FILE *stream)\n{\n\tFILE *rc;\n\n\tif (stream == stdin)\n\t\trc = freopen (\"/dev/null\", \"r\", stream);\n\telse\n\t\trc = freopen (\"/dev/null\", \"w\", stream);\n\n\tif (!rc)\n\t\tfatal (\"Can't open /dev/null: %s\", xstrerror (errno));\n}\n\nstatic void log_process_stack_size ()\n{\n#if !defined(NDEBUG) && defined(HAVE_GETRLIMIT)\n\tint rc;\n\tstruct rlimit limits;\n\n\trc = getrlimit (RLIMIT_STACK, &limits);\n\tif (rc == 0)\n\t\tlogit (\"Process's stack size: %u\", (unsigned int)limits.rlim_cur);\n#endif\n}\n\nstatic void log_pthread_stack_size ()\n{\n#if !defined(NDEBUG) && defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE)\n\tint rc;\n\tsize_t stack_size;\n\tpthread_attr_t attr;\n\n\trc = pthread_attr_init (&attr);\n\tif (rc)\n\t\treturn;\n\n\trc = pthread_attr_getstacksize (&attr, &stack_size);\n\tif (rc == 0)\n\t\tlogit (\"PThread's stack size: %u\", (unsigned int)stack_size);\n\n\tpthread_attr_destroy (&attr);\n#endif\n}\n\n/* Handle running external command on requested event. */\nstatic void run_extern_cmd (const char *event)\n{\n\tchar *command;\n\n\tcommand = xstrdup (options_get_str (event));\n\n\tif (command) {\n\t\tchar *args[2], *err;\n\n\t\targs[0] = xstrdup (command);\n\t\targs[1] = NULL;\n\n\t\tswitch (fork ()) {\n\t\tcase 0:\n\t\t\texecve (command, args, environ);\n\t\t\tfatal (\"Error when running %s command '%s': %s\",\n\t\t\t        event, command, xstrerror (errno));\n\t\tcase -1:\n\t\t\terr = xstrerror (errno);\n\t\t\tlogit (\"Error when running %s command '%s': %s\",\n\t\t\t        event, command, err);\n\t\t\tfree (err);\n\t\t\tbreak;\n\t\t}\n\n\t\tfree (command);\n\t\tfree (args[0]);\n\t}\n}\n\n/* Initialize the server - return fd of the listening socket or -1 on error */\nvoid server_init (int debugging, int foreground)\n{\n\tstruct sockaddr_un sock_name;\n\tpid_t pid;\n\n\tlogit (\"Starting MOC Server\");\n\n\tassert (server_sock == -1);\n\n\tpid = check_pid_file ();\n\tif (pid && valid_pid(pid)) {\n\t\tfprintf (stderr, \"\\nIt seems that the server is already running\"\n\t\t\t\t\" with pid %d.\\n\", pid);\n\t\tfprintf (stderr, \"If it is not true, remove the pid file (%s)\"\n\t\t\t\t\" and try again.\\n\",\n\t\t\t\tcreate_file_name(PID_FILE));\n\t\tfatal (\"Exiting!\");\n\t}\n\n\tif (foreground)\n\t\tlog_init_stream (stdout, \"stdout\");\n\telse {\n\t\tFILE *logfp;\n\n\t\tlogfp = NULL;\n\t\tif (debugging) {\n\t\t\tlogfp = fopen (SERVER_LOG, \"a\");\n\t\t\tif (!logfp)\n\t\t\t\tfatal (\"Can't open server log file: %s\", xstrerror (errno));\n\t\t}\n\t\tlog_init_stream (logfp, SERVER_LOG);\n\t}\n\n\tif (pipe(wake_up_pipe) < 0)\n\t\tfatal (\"pipe() failed: %s\", xstrerror (errno));\n\n\tunlink (socket_name());\n\n\t/* Create a socket.\n\t * For reasons why AF_UNIX is the correct constant to use in both\n\t * cases, see the commentary the SVN log for commit r9999. */\n\tserver_sock = socket (AF_UNIX, SOCK_STREAM, 0);\n\tif (server_sock == -1)\n\t\tfatal (\"Can't create socket: %s\", xstrerror (errno));\n\tsock_name.sun_family = AF_UNIX;\n\tstrcpy (sock_name.sun_path, socket_name());\n\n\t/* Bind to socket */\n\tif (bind(server_sock, (struct sockaddr *)&sock_name, SUN_LEN(&sock_name)) == -1)\n\t\tfatal (\"Can't bind() to the socket: %s\", xstrerror (errno));\n\n\tif (listen(server_sock, 1) == -1)\n\t\tfatal (\"listen() failed: %s\", xstrerror (errno));\n\n\t/* Log stack sizes so stack overflows can be debugged. */\n\tlog_process_stack_size ();\n\tlog_pthread_stack_size ();\n\n\tclients_init ();\n\taudio_initialize ();\n\ttags_cache = tags_cache_new (options_get_int(\"TagsCacheSize\"));\n\ttags_cache_load (tags_cache, create_file_name(\"cache\"));\n\n\tserver_tid = pthread_self ();\n\txsignal (SIGTERM, sig_exit);\n\txsignal (SIGINT, foreground ? sig_exit : SIG_IGN);\n\txsignal (SIGHUP, SIG_IGN);\n\txsignal (SIGQUIT, sig_exit);\n\txsignal (SIGPIPE, SIG_IGN);\n\txsignal (SIGCHLD, sig_chld);\n\n\twrite_pid_file ();\n\n\tif (!foreground) {\n\t\tsetsid ();\n\t\tredirect_output (stdin);\n\t\tredirect_output (stdout);\n\t\tredirect_output (stderr);\n\t}\n\n\tlogit (\"Running OnServerStart\");\n\trun_extern_cmd (\"OnServerStart\");\n\n\treturn;\n}\n\n/* Send EV_DATA and the integer value. Return 0 on error. */\nstatic int send_data_int (const struct client *cli, const int data)\n{\n\tassert (cli->socket != -1);\n\n\tif (!send_int(cli->socket, EV_DATA) || !send_int(cli->socket, data))\n\t\treturn 0;\n\n\treturn 1;\n}\n\n/* Send EV_DATA and the boolean value. Return 0 on error. */\nstatic int send_data_bool (const struct client *cli, const bool data)\n{\n\tassert (cli->socket != -1);\n\n\tif (!send_int(cli->socket, EV_DATA) ||\n\t    !send_int(cli->socket, data ? 1 : 0))\n\t\treturn 0;\n\n\treturn 1;\n}\n\n/* Send EV_DATA and the string value. Return 0 on error. */\nstatic int send_data_str (const struct client *cli, const char *str) {\n\tif (!send_int(cli->socket, EV_DATA) || !send_str(cli->socket, str))\n\t\treturn 0;\n\treturn 1;\n}\n\n/* Add event to the client's queue */\nstatic void add_event (struct client *cli, const int event, void *data)\n{\n\tLOCK (cli->events_mtx);\n\tevent_push (&cli->events, event, data);\n\tUNLOCK (cli->events_mtx);\n}\n\nstatic void on_song_change ()\n{\n\tstatic char *last_file = NULL;\n\tstatic lists_t_strs *on_song_change = NULL;\n\n\tint ix;\n\tbool same_file, unpaused;\n\tchar *curr_file, **args;\n\tstruct file_tags *curr_tags;\n\tlists_t_strs *arg_list;\n\n\t/* We only need to do OnSongChange tokenisation once. */\n\tif (on_song_change == NULL) {\n\t\tchar *command;\n\n\t\ton_song_change = lists_strs_new (4);\n\t\tcommand = options_get_str (\"OnSongChange\");\n\n\t\tif (command)\n\t\t\tlists_strs_tokenise (on_song_change, command);\n\t}\n\n\tif (lists_strs_empty (on_song_change))\n\t\treturn;\n\n\tcurr_file = audio_get_sname ();\n\n\tif (curr_file == NULL)\n\t\treturn;\n\n\tsame_file = (last_file && !strcmp (last_file, curr_file));\n\tunpaused = (audio_get_prev_state () == STATE_PAUSE);\n\tif (same_file && (unpaused || !options_get_bool (\"RepeatSongChange\"))) {\n\t\tfree (curr_file);\n\t\treturn;\n\t}\n\n\tcurr_tags = tags_cache_get_immediate (tags_cache, curr_file,\n\t                                      TAGS_COMMENTS | TAGS_TIME);\n\targ_list = lists_strs_new (lists_strs_size (on_song_change));\n\tfor (ix = 0; ix < lists_strs_size (on_song_change); ix += 1) {\n\t\tchar *arg, *str;\n\n\t\targ = lists_strs_at (on_song_change, ix);\n\t\tif (arg[0] != '%')\n\t\t\tlists_strs_append (arg_list, arg);\n\t\telse if (!curr_tags)\n\t\t\tlists_strs_append (arg_list, \"\");\n\t\telse {\n\t\t\tswitch (arg[1]) {\n\t\t\tcase 'a':\n\t\t\t\tstr = curr_tags->artist ? curr_tags->artist : \"\";\n\t\t\t\tlists_strs_append (arg_list, str);\n\t\t\t\tbreak;\n\t\t\tcase 'r':\n\t\t\t\tstr = curr_tags->album ? curr_tags->album : \"\";\n\t\t\t\tlists_strs_append (arg_list, str);\n\t\t\t\tbreak;\n\t\t\tcase 't':\n\t\t\t\tstr = curr_tags->title ? curr_tags->title : \"\";\n\t\t\t\tlists_strs_append (arg_list, str);\n\t\t\t\tbreak;\n\t\t\tcase 'n':\n\t\t\t\tif (curr_tags->track >= 0) {\n\t\t\t\t\tstr = (char *) xmalloc (sizeof (char) * 16);\n\t\t\t\t\tsnprintf (str, 16, \"%d\", curr_tags->track);\n\t\t\t\t\tlists_strs_push (arg_list, str);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tlists_strs_append (arg_list, \"\");\n\t\t\t\tbreak;\n\t\t\tcase 'f':\n\t\t\t\tlists_strs_append (arg_list, curr_file);\n\t\t\t\tbreak;\n\t\t\tcase 'D':\n\t\t\t\tif (curr_tags->time >= 0) {\n\t\t\t\t\tstr = (char *) xmalloc (sizeof (char) * 16);\n\t\t\t\t\tsnprintf (str, 16, \"%d\", curr_tags->time);\n\t\t\t\t\tlists_strs_push (arg_list, str);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tlists_strs_append (arg_list, \"\");\n\t\t\t\tbreak;\n\t\t\tcase 'd':\n\t\t\t\tif (curr_tags->time >= 0) {\n\t\t\t\t\tstr = (char *) xmalloc (sizeof (char) * 32);\n\t\t\t\t\tsec_to_min (str, curr_tags->time);\n\t\t\t\t\tlists_strs_push (arg_list, str);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\tlists_strs_append (arg_list, \"\");\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tlists_strs_append (arg_list, arg);\n\t\t\t}\n\t\t}\n\t}\n\ttags_free (curr_tags);\n\n#ifndef NDEBUG\n\t{\n\t\tchar *cmd;\n\n\t\tcmd = lists_strs_fmt (arg_list, \" %s\");\n\t\tdebug (\"Running command: %s\", cmd);\n\t\tfree (cmd);\n\t}\n#endif\n\n\tswitch (fork ()) {\n\tcase 0:\n\t\targs = lists_strs_save (arg_list);\n\t\texecve (args[0], args, environ);\n\t\tfatal (\"Error when running OnSongChange command '%s': %s\",\n\t\t        args[0], xstrerror (errno));\n\tcase -1:\n\t\tlog_errno (\"Failed to fork()\", errno);\n\t}\n\n\tlists_strs_free (arg_list);\n\tfree (last_file);\n\tlast_file = curr_file;\n}\n\n/* Return true iff 'event' is a playlist event. */\nstatic inline bool is_plist_event (const int event)\n{\n\tbool result = false;\n\n\tswitch (event) {\n\tcase EV_PLIST_ADD:\n\tcase EV_PLIST_DEL:\n\tcase EV_PLIST_MOVE:\n\tcase EV_PLIST_CLEAR:\n\t\tresult = true;\n\t}\n\n\treturn result;\n}\n\nstatic void add_event_all (const int event, const void *data)\n{\n\tint i;\n\tint added = 0;\n\n\tif (event == EV_STATE) {\n\t\tswitch (audio_get_state()) {\n\t\t\tcase STATE_PLAY:\n\t\t\t\ton_song_change ();\n\t\t\t\tbreak;\n\t\t\tcase STATE_STOP:\n\t\t\t\trun_extern_cmd (\"OnStop\");\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tfor (i = 0; i < CLIENTS_MAX; i++) {\n\t\tvoid *data_copy = NULL;\n\n\t\tif (clients[i].socket == -1)\n\t\t\tcontinue;\n\n\t\tif (!clients[i].wants_plist_events && is_plist_event (event))\n\t\t\tcontinue;\n\n\t\tif (data) {\n\t\t\tif (event == EV_PLIST_ADD\n\t\t\t\t\t|| event == EV_QUEUE_ADD) {\n\t\t\t\tdata_copy = plist_new_item ();\n\t\t\t\tplist_item_copy (data_copy, data);\n\t\t\t}\n\t\t\telse if (event == EV_PLIST_DEL\n\t\t\t\t\t|| event == EV_QUEUE_DEL\n\t\t\t\t\t|| event == EV_STATUS_MSG\n\t\t\t\t\t|| event == EV_SRV_ERROR) {\n\t\t\t\tdata_copy = xstrdup (data);\n\t\t\t}\n\t\t\telse if (event == EV_PLIST_MOVE\n\t\t\t\t\t|| event == EV_QUEUE_MOVE)\n\t\t\t\tdata_copy = move_ev_data_dup (\n\t\t\t\t\t\t(struct move_ev_data *)\n\t\t\t\t\t\tdata);\n\t\t\telse\n\t\t\t\tlogit (\"Unhandled data!\");\n\t\t}\n\n\t\tadd_event (&clients[i], event, data_copy);\n\t\tadded++;\n\t}\n\n\tif (added)\n\t\twake_up_server ();\n\telse\n\t\tdebug (\"No events have been added because there are no clients\");\n}\n\n/* Send events from the queue. Return 0 on error. */\nstatic int flush_events (struct client *cli)\n{\n\tenum noblock_io_status st = NB_IO_OK;\n\n\tLOCK (cli->events_mtx);\n\twhile (!event_queue_empty(&cli->events)\n\t\t\t&& (st = event_send_noblock(cli->socket, &cli->events))\n\t\t\t== NB_IO_OK)\n\t\t;\n\tUNLOCK (cli->events_mtx);\n\n\treturn st != NB_IO_ERR ? 1 : 0;\n}\n\n/* Send events to clients whose sockets are ready to write. */\nstatic void send_events (fd_set *fds)\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].socket != -1\n\t\t\t\t&& FD_ISSET(clients[i].socket, fds)) {\n\t\t\tdebug (\"Flushing events for client %d\", i);\n\t\t\tif (!flush_events (&clients[i])) {\n\t\t\t\tclose (clients[i].socket);\n\t\t\t\tdel_client (&clients[i]);\n\t\t\t}\n\t\t}\n}\n\n/* End playing and cleanup. */\nstatic void server_shutdown ()\n{\n\tlogit (\"Server exiting...\");\n\taudio_exit ();\n\ttags_cache_free (tags_cache);\n\ttags_cache = NULL;\n\tlogit (\"Running OnServerStop\");\n\trun_extern_cmd (\"OnServerStop\");\n\tunlink (socket_name());\n\tunlink (create_file_name(PID_FILE));\n\tclose (wake_up_pipe[0]);\n\tclose (wake_up_pipe[1]);\n\tlogit (\"Server exited\");\n\tlog_close ();\n}\n\n/* Send EV_BUSY message and close the connection. */\nstatic void busy (int sock)\n{\n\tlogit (\"Closing connection due to maximum number of clients reached\");\n\tsend_int (sock, EV_BUSY);\n\tclose (sock);\n}\n\n/* Handle CMD_LIST_ADD, return 1 if ok or 0 on error. */\nstatic int req_list_add (struct client *cli)\n{\n\tchar *file;\n\n\tfile = get_str (cli->socket);\n\tif (!file)\n\t\treturn 0;\n\n\tlogit (\"Adding '%s' to the list\", file);\n\n\taudio_plist_add (file);\n\tfree (file);\n\n\treturn 1;\n}\n\n/* Handle CMD_QUEUE_ADD, return 1 if ok or 0 on error. */\nstatic int req_queue_add (const struct client *cli)\n{\n\tchar *file;\n\tstruct plist_item *item;\n\n\tfile = get_str (cli->socket);\n\tif (!file)\n\t\treturn 0;\n\n\tlogit (\"Adding '%s' to the queue\", file);\n\n\taudio_queue_add (file);\n\n\t/* Wrap the filename in struct plist_item.\n\t * We don't need tags, because the player gets them\n\t * when playing the file. This may change if there is\n\t * support for viewing/reordering the queue and here\n\t * is the place to read the tags and fill them into\n\t * the item. */\n\n\titem = plist_new_item ();\n\titem->file = xstrdup (file);\n\titem->type = file_type (file);\n\titem->mtime = get_mtime (file);\n\n\tadd_event_all (EV_QUEUE_ADD, item);\n\n\tplist_free_item_fields (item);\n\tfree (item);\n\tfree (file);\n\n\treturn 1;\n}\n\n/* Handle CMD_PLAY, return 1 if ok or 0 on error. */\nstatic int req_play (struct client *cli)\n{\n\tchar *file;\n\n\tif (!(file = get_str(cli->socket)))\n\t\treturn 0;\n\n\tlogit (\"Playing %s\", *file ? file : \"first element on the list\");\n\taudio_play (file);\n\tfree (file);\n\n\treturn 1;\n}\n\n/* Handle CMD_SEEK, return 1 if ok or 0 on error */\nstatic int req_seek (struct client *cli)\n{\n\tint sec;\n\n\tif (!get_int(cli->socket, &sec))\n\t\treturn 0;\n\n\tlogit (\"Seeking %ds\", sec);\n\taudio_seek (sec);\n\n\treturn 1;\n}\n\n/* Handle CMD_JUMP_TO, return 1 if ok or 0 on error */\nstatic int req_jump_to (struct client *cli)\n{\n\tint sec;\n\n\tif (!get_int(cli->socket, &sec))\n\t\treturn 0;\n\tlogit (\"Jumping to %ds\", sec);\n\taudio_jump_to (sec);\n\n\treturn 1;\n}\n\n/* Report an error logging it and sending a message to the client. */\nvoid server_error (const char *file, int line, const char *function,\n                   const char *msg)\n{\n\tinternal_logit (file, line, function, \"ERROR: %s\", msg);\n\tadd_event_all (EV_SRV_ERROR, msg);\n}\n\n/* Send the song name to the client. Return 0 on error. */\nstatic int send_sname (struct client *cli)\n{\n\tint status = 1;\n\tchar *sname = audio_get_sname ();\n\n\tif (!send_data_str(cli, sname ? sname : \"\"))\n\t\tstatus = 0;\n\tfree (sname);\n\n\treturn status;\n}\n\n/* Return 0 if an option is valid when getting/setting with the client. */\nstatic int valid_sync_option (const char *name)\n{\n\treturn !strcasecmp(name, \"ShowStreamErrors\")\n\t\t|| !strcasecmp(name, \"Repeat\")\n\t\t|| !strcasecmp(name, \"Shuffle\")\n\t\t|| !strcasecmp(name, \"AutoNext\");\n}\n\n/* Send requested option value to the client. Return 1 if OK. */\nstatic int send_option (struct client *cli)\n{\n\tchar *name;\n\n\tif (!(name = get_str(cli->socket)))\n\t\treturn 0;\n\n\t/* We can send only a few options, others make no sense here. */\n\tif (!valid_sync_option(name)) {\n\t\tlogit (\"Client wanted to get invalid option '%s'\", name);\n\t\tfree (name);\n\t\treturn 0;\n\t}\n\n\t/* All supported options are boolean type. */\n\tif (!send_data_bool(cli, options_get_bool(name))) {\n\t\tfree (name);\n\t\treturn 0;\n\t}\n\n\tfree (name);\n\treturn 1;\n}\n\n/* Get and set an option from the client. Return 1 on error. */\nstatic int get_set_option (struct client *cli)\n{\n\tchar *name;\n\tint val;\n\n\tif (!(name = get_str (cli->socket)))\n\t\treturn 0;\n\tif (!valid_sync_option (name)) {\n\t\tlogit (\"Client requested setting invalid option '%s'\", name);\n\t\treturn 0;\n\t}\n\tif (!get_int (cli->socket, &val)) {\n\t\tfree (name);\n\t\treturn 0;\n\t}\n\n\toptions_set_bool (name, val ? true : false);\n\tfree (name);\n\n\tadd_event_all (EV_OPTIONS, NULL);\n\n\treturn 1;\n}\n\n/* Set the mixer to the value provided by the client. Return 0 on error. */\nstatic int set_mixer (struct client *cli)\n{\n\tint val;\n\n\tif (!get_int(cli->socket, &val))\n\t\treturn 0;\n\n\taudio_set_mixer (val);\n\treturn 1;\n}\n\n/* Delete an item from the playlist. Return 0 on error. */\nstatic int delete_item (struct client *cli)\n{\n\tchar *file;\n\n\tif (!(file = get_str(cli->socket)))\n\t\treturn 0;\n\n\tdebug (\"Request for deleting %s\", file);\n\n\taudio_plist_delete (file);\n\tfree (file);\n\treturn 1;\n}\n\nstatic int req_queue_del (const struct client *cli)\n{\n\tchar *file;\n\n\tif (!(file = get_str(cli->socket)))\n\t\treturn 0;\n\n\tdebug (\"Deleting '%s' from queue\", file);\n\n\taudio_queue_delete (file);\n\tadd_event_all (EV_QUEUE_DEL, file);\n\tfree (file);\n\n\treturn 1;\n}\n\n/* Return the index of the first client able to send the playlist or -1 if\n * there isn't any. */\nstatic int find_sending_plist ()\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].socket != -1 && clients[i].can_send_plist)\n\t\t\treturn i;\n\treturn -1;\n}\n\n/* Handle CMD_GET_PLIST. Return 0 on error. */\nstatic int get_client_plist (struct client *cli)\n{\n\tint first;\n\n\tdebug (\"Client with fd %d requests the playlist\", cli->socket);\n\n\t/* Find the first connected client, and ask it to send the playlist.\n\t * Here, send 1 if there is a client with the playlist, or 0 if there\n\t * isn't. */\n\n\tcli->requests_plist = 1;\n\n\tfirst = find_sending_plist ();\n\tif (first == -1) {\n\t\tdebug (\"No clients with the playlist\");\n\t\tcli->requests_plist = 0;\n\t\tif (!send_data_int(cli, 0))\n\t\t\treturn 0;\n\t\treturn 1;\n\t}\n\n\tif (!send_data_int(cli, 1))\n\t\treturn 0;\n\n\tif (!send_int(clients[first].socket, EV_SEND_PLIST))\n\t\treturn 0;\n\n\treturn 1;\n}\n\n/* Find the client requesting the playlist. */\nstatic int find_cli_requesting_plist ()\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].requests_plist)\n\t\t\treturn i;\n\treturn -1;\n}\n\n/* Handle CMD_SEND_PLIST. Some client requested to get the playlist, so we asked\n * another client to send it (EV_SEND_PLIST). */\nstatic int req_send_plist (struct client *cli)\n{\n\tint requesting = find_cli_requesting_plist ();\n\tint send_fd;\n\tstruct plist_item *item;\n\tint serial;\n\n\tdebug (\"Client with fd %d wants to send its playlists\", cli->socket);\n\n\tif (requesting == -1) {\n\t\tlogit (\"No clients are requesting the playlist\");\n\t\tsend_fd = -1;\n\t}\n\telse {\n\t\tsend_fd = clients[requesting].socket;\n\t\tif (!send_int(send_fd, EV_DATA)) {\n\t\t\tlogit (\"Error while sending response; disconnecting the client\");\n\t\t\tclose (send_fd);\n\t\t\tdel_client (&clients[requesting]);\n\t\t\tsend_fd = -1;\n\t\t}\n\t}\n\n\tif (!get_int(cli->socket, &serial)) {\n\t\tlogit (\"Error while getting serial\");\n\t\treturn 0;\n\t}\n\n\tif (send_fd != -1 && !send_int(send_fd, serial)) {\n\t\terror (\"Error while sending serial; disconnecting the client\");\n\t\tclose (send_fd);\n\t\tdel_client (&clients[requesting]);\n\t\tsend_fd = -1;\n\t}\n\n\t/* Even if no clients are requesting the playlist, we must read it,\n\t * because there is no way to say that we don't need it. */\n\twhile ((item = recv_item(cli->socket)) && item->file[0]) {\n\t\tif (send_fd != -1 && !send_item(send_fd, item)) {\n\t\t\tlogit (\"Error while sending item; disconnecting the client\");\n\t\t\tclose (send_fd);\n\t\t\tdel_client (&clients[requesting]);\n\t\t\tsend_fd = -1;\n\t\t}\n\t\tplist_free_item_fields (item);\n\t\tfree (item);\n\t}\n\n\tif (item) {\n\t\tplist_free_item_fields (item);\n\t\tfree (item);\n\t\tlogit (\"Playlist sent\");\n\t}\n\telse\n\t\tlogit (\"Error while receiving item\");\n\n\tif (send_fd != -1 && !send_item (send_fd, NULL)) {\n\t\tlogit (\"Error while sending end of playlist mark; \"\n\t\t       \"disconnecting the client\");\n\t\tclose (send_fd);\n\t\tdel_client (&clients[requesting]);\n\t\treturn 0;\n\t}\n\n\tif (requesting != -1)\n\t\tclients[requesting].requests_plist = 0;\n\n\treturn item ? 1 : 0;\n}\n\n/* Client requested we send the queue so we get it from audio.c and\n * send it to the client. */\nstatic int req_send_queue (struct client *cli)\n{\n\tint i;\n\tstruct plist *queue;\n\n\tlogit (\"Client with fd %d wants queue... sending it\", cli->socket);\n\n\tif (!send_int(cli->socket, EV_DATA)) {\n\t\tlogit (\"Error while sending response; disconnecting the client\");\n\t\tclose (cli->socket);\n\t\tdel_client (cli);\n\t\treturn 0;\n\t}\n\n\tqueue = audio_queue_get_contents ();\n\n\tfor (i = 0; i < queue->num; i++)\n\t\tif (!plist_deleted(queue, i)) {\n\t\t\tif(!send_item(cli->socket, &queue->items[i])){\n\t\t\t\tlogit (\"Error sending queue; disconnecting the client\");\n\t\t\t\tclose (cli->socket);\n\t\t\t\tdel_client (cli);\n\t\t\t\tfree (queue);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\tplist_free (queue);\n\tfree (queue);\n\n\tif (!send_item (cli->socket, NULL)) {\n\t\tlogit (\"Error while sending end of playlist mark; \"\n\t\t       \"disconnecting the client\");\n\t\tclose (cli->socket);\n\t\tdel_client (cli);\n\t\treturn 0;\n\t}\n\n\tlogit (\"Queue sent\");\n\treturn 1;\n}\n\n/* Handle command that synchronises the playlists between interfaces\n * (except forwarding the whole list). Return 0 on error. */\nstatic int plist_sync_cmd (struct client *cli, const int cmd)\n{\n\tif (cmd == CMD_CLI_PLIST_ADD) {\n\t\tstruct plist_item *item;\n\n\t\tdebug (\"Sending EV_PLIST_ADD\");\n\n\t\tif (!(item = recv_item(cli->socket))) {\n\t\t\tlogit (\"Error while receiving item\");\n\t\t\treturn 0;\n\t\t}\n\n\t\tadd_event_all (EV_PLIST_ADD, item);\n\t\tplist_free_item_fields (item);\n\t\tfree (item);\n\t}\n\telse if (cmd == CMD_CLI_PLIST_DEL) {\n\t\tchar *file;\n\n\t\tdebug (\"Sending EV_PLIST_DEL\");\n\n\t\tif (!(file = get_str(cli->socket))) {\n\t\t\tlogit (\"Error while receiving file\");\n\t\t\treturn 0;\n\t\t}\n\n\t\tadd_event_all (EV_PLIST_DEL, file);\n\t\tfree (file);\n\t}\n\telse if (cmd == CMD_CLI_PLIST_MOVE) {\n\t\tstruct move_ev_data m;\n\n\t\tif (!(m.from = get_str(cli->socket))\n\t\t\t\t|| !(m.to = get_str(cli->socket))) {\n\t\t\tlogit (\"Error while receiving file\");\n\t\t\treturn 0;\n\t\t}\n\n\t\tadd_event_all (EV_PLIST_MOVE, &m);\n\n\t\tfree (m.from);\n\t\tfree (m.to);\n\t}\n\telse { /* it can be only CMD_CLI_PLIST_CLEAR */\n\t\tdebug (\"Sending EV_PLIST_CLEAR\");\n\t\tadd_event_all (EV_PLIST_CLEAR, NULL);\n\t}\n\n\treturn 1;\n}\n\n/* Handle CMD_PLIST_GET_SERIAL. Return 0 on error. */\nstatic int req_plist_get_serial (struct client *cli)\n{\n\tif (!send_data_int(cli, audio_plist_get_serial()))\n\t\treturn 0;\n\treturn 1;\n}\n\n/* Handle CMD_PLIST_SET_SERIAL. Return 0 on error. */\nstatic int req_plist_set_serial (struct client *cli)\n{\n\tint serial;\n\n\tif (!get_int(cli->socket, &serial))\n\t\treturn 0;\n\n\tif (serial < 0) {\n\t\tlogit (\"Client wants to set bad serial number\");\n\t\treturn 0;\n\t}\n\n\tdebug (\"Setting the playlist serial number to %d\", serial);\n\taudio_plist_set_serial (serial);\n\n\treturn 1;\n}\n\n/* Generate a unique playlist serial number. */\nstatic int gen_serial (const struct client *cli)\n{\n\tstatic int seed = 0;\n\tint serial;\n\n\t/* Each client must always get a different serial number, so we use\n\t * also the client index to generate it. It must also not be used by\n\t * our playlist to not confuse clients.\n\t * There can be 256 different serial number per client, but it's\n\t * enough since clients use only two playlists. */\n\n\tdo {\n\t\tserial = (seed << 8) | client_index(cli);\n\t\tseed = (seed + 1) & 0xFF;\n\t} while (serial == audio_plist_get_serial());\n\n\tdebug (\"Generated serial %d for client with fd %d\", serial, cli->socket);\n\n\treturn serial;\n}\n\n/* Send the unique number to the client. Return 0 on error. */\nstatic int send_serial (struct client *cli)\n{\n\tif (!send_data_int(cli, gen_serial(cli))) {\n\t\tlogit (\"Error when sending serial\");\n\t\treturn 0;\n\t}\n\treturn 1;\n}\n\n/* Send tags to the client. Return 0 on error. */\nstatic int req_get_tags (struct client *cli)\n{\n\tstruct file_tags *tags;\n\tint res = 1;\n\n\tdebug (\"Sending tags to client with fd %d...\", cli->socket);\n\n\tif (!send_int(cli->socket, EV_DATA)) {\n\t\tlogit (\"Error when sending EV_DATA\");\n\t\treturn 0;\n\t}\n\n\ttags = audio_get_curr_tags ();\n\tif (!send_tags(cli->socket, tags)) {\n\t\tlogit (\"Error when sending tags\");\n\t\tres = 0;\n\t}\n\n\tif (tags)\n\t\ttags_free (tags);\n\n\treturn res;\n}\n\n/* Handle CMD_GET_MIXER_CHANNEL_NAME. Return 0 on error. */\nint req_get_mixer_channel_name (struct client *cli)\n{\n\tint status = 1;\n\tchar *name = audio_get_mixer_channel_name ();\n\n\tif (!send_data_str(cli, name ? name : \"\"))\n\t\tstatus = 0;\n\tfree (name);\n\n\treturn status;\n}\n\n/* Handle CMD_TOGGLE_MIXER_CHANNEL. */\nvoid req_toggle_mixer_channel ()\n{\n\taudio_toggle_mixer_channel ();\n\tadd_event_all (EV_MIXER_CHANGE, NULL);\n}\n\n/* Handle CMD_TOGGLE_SOFTMIXER. */\nvoid req_toggle_softmixer ()\n{\n\tsoftmixer_set_active(!softmixer_is_active());\n\tadd_event_all (EV_MIXER_CHANGE, NULL);\n}\n\nvoid update_eq_name()\n{\n\tchar buffer[27];\n\n\tchar *n = equalizer_current_eqname();\n\n\tint l = strlen(n);\n\n\t/* Status message can only take strings up to 25 chars\n\t * (Without terminating zero).\n\t * The message header has 11 chars (EQ set to...).\n\t */\n\tif (l > 14)\n\t{\n\t\tn[14] = 0;\n\t\tn[13] = '.';\n\t\tn[12] = '.';\n\t\tn[11] = '.';\n\t}\n\n\tsprintf(buffer, \"EQ set to: %s\", n);\n\n\tlogit(\"%s\", buffer);\n\n\tfree(n);\n\n\tstatus_msg(buffer);\n}\n\nvoid req_toggle_equalizer ()\n{\n\tequalizer_set_active(!equalizer_is_active());\n\n\tupdate_eq_name();\n}\n\nvoid req_equalizer_refresh()\n{\n\tequalizer_refresh();\n\n\tstatus_msg(\"Equalizer refreshed\");\n\n\tlogit(\"Equalizer refreshed\");\n}\n\nvoid req_equalizer_prev()\n{\n\tequalizer_prev();\n\n\tupdate_eq_name();\n}\n\nvoid req_equalizer_next()\n{\n\tequalizer_next();\n\n\tupdate_eq_name();\n}\n\nvoid req_toggle_make_mono()\n{\n\tchar buffer[128];\n\n\tsoftmixer_set_mono(!softmixer_is_mono());\n\n\tsprintf(buffer, \"Mono-Mixing set to: %s\", softmixer_is_mono()?\"on\":\"off\");\n\n\tstatus_msg(buffer);\n}\n\n/* Handle CMD_GET_FILE_TAGS. Return 0 on error. */\nstatic int get_file_tags (const int cli_id)\n{\n\tchar *file;\n\tint tags_sel;\n\n\tif (!(file = get_str(clients[cli_id].socket)))\n\t\treturn 0;\n\tif (!get_int(clients[cli_id].socket, &tags_sel)) {\n\t\tfree (file);\n\t\treturn 0;\n\t}\n\n\ttags_cache_add_request (tags_cache, file, tags_sel, cli_id);\n\tfree (file);\n\n\treturn 1;\n}\n\nstatic int abort_tags_requests (const int cli_id)\n{\n\tchar *file;\n\n\tif (!(file = get_str(clients[cli_id].socket)))\n\t\treturn 0;\n\n\ttags_cache_clear_up_to (tags_cache, file, cli_id);\n\tfree (file);\n\n\treturn 1;\n}\n\n/* Handle CMD_LIST_MOVE. Return 0 on error. */\nstatic int req_list_move (struct client *cli)\n{\n\tchar *from;\n\tchar *to;\n\n\tif (!(from = get_str(cli->socket)))\n\t\treturn 0;\n\tif (!(to = get_str(cli->socket))) {\n\t\tfree (from);\n\t\treturn 0;\n\t}\n\n\taudio_plist_move (from, to);\n\n\tfree (from);\n\tfree (to);\n\n\treturn 1;\n}\n\n/* Handle CMD_QUEUE_MOVE. Return 0 on error. */\nstatic int req_queue_move (const struct client *cli)\n{\n\tstruct move_ev_data m;\n\n\tif (!(m.from = get_str(cli->socket)))\n\t\treturn 0;\n\tif (!(m.to = get_str(cli->socket))) {\n\t\tfree (m.from);\n\t\treturn 0;\n\t}\n\n\taudio_queue_move (m.from, m.to);\n\n\tlogit (\"Swapping %s with %s in the queue\", m.from, m.to);\n\n\t/* Broadcast the event to clients */\n\tadd_event_all (EV_QUEUE_MOVE, &m);\n\n\tfree (m.from);\n\tfree (m.to);\n\n\treturn 1;\n}\n\n/* Receive a command from the client and execute it. */\nstatic void handle_command (const int client_id)\n{\n\tint cmd;\n\tint err = 0;\n\tstruct client *cli = &clients[client_id];\n\n\tif (!get_int(cli->socket, &cmd)) {\n\t\tlogit (\"Failed to get command from the client\");\n\t\tclose (cli->socket);\n\t\tdel_client (cli);\n\t\treturn;\n\t}\n\n\tswitch (cmd) {\n\t\tcase CMD_QUIT:\n\t\t\tlogit (\"Exit request from the client\");\n\t\t\tclose (cli->socket);\n\t\t\tdel_client (cli);\n\t\t\tserver_quit = 1;\n\t\t\tbreak;\n\t\tcase CMD_LIST_CLEAR:\n\t\t\tlogit (\"Clearing the list\");\n\t\t\taudio_plist_clear ();\n\t\t\tbreak;\n\t\tcase CMD_LIST_ADD:\n\t\t\tif (!req_list_add(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_PLAY:\n\t\t\tif (!req_play(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_DISCONNECT:\n\t\t\tlogit (\"Client disconnected\");\n\t\t\tclose (cli->socket);\n\t\t\tdel_client (cli);\n\t\t\tbreak;\n\t\tcase CMD_PAUSE:\n\t\t\taudio_pause ();\n\t\t\tbreak;\n\t\tcase CMD_UNPAUSE:\n\t\t\taudio_unpause ();\n\t\t\tbreak;\n\t\tcase CMD_STOP:\n\t\t\taudio_stop ();\n\t\t\tbreak;\n\t\tcase CMD_GET_CTIME:\n\t\t\tif (!send_data_int(cli, MAX(0, audio_get_time())))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_SEEK:\n\t\t\tif (!req_seek(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_JUMP_TO:\n\t\t\tif (!req_jump_to(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_SNAME:\n\t\t\tif (!send_sname(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_STATE:\n\t\t\tif (!send_data_int(cli, audio_get_state()))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_BITRATE:\n\t\t\tif (!send_data_int(cli, sound_info.bitrate))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_AVG_BITRATE:\n\t\t\tif (!send_data_int(cli, sound_info.avg_bitrate))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_RATE:\n\t\t\tif (!send_data_int(cli, sound_info.rate))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_CHANNELS:\n\t\t\tif (!send_data_int(cli, sound_info.channels))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_NEXT:\n\t\t\taudio_next ();\n\t\t\tbreak;\n\t\tcase CMD_PREV:\n\t\t\taudio_prev ();\n\t\t\tbreak;\n\t\tcase CMD_PING:\n\t\t\tif (!send_int(cli->socket, EV_PONG))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_OPTION:\n\t\t\tif (!send_option(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_SET_OPTION:\n\t\t\tif (!get_set_option(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_MIXER:\n\t\t\tif (!send_data_int(cli, audio_get_mixer()))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_SET_MIXER:\n\t\t\tif (!set_mixer(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_DELETE:\n\t\t\tif (!delete_item(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_SEND_PLIST_EVENTS:\n\t\t\tcli->wants_plist_events = 1;\n\t\t\tlogit (\"Request for events\");\n\t\t\tbreak;\n\t\tcase CMD_GET_PLIST:\n\t\t\tif (!get_client_plist(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_SEND_PLIST:\n\t\t\tif (!req_send_plist(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_CAN_SEND_PLIST:\n\t\t\tcli->can_send_plist = 1;\n\t\t\tbreak;\n\t\tcase CMD_CLI_PLIST_ADD:\n\t\tcase CMD_CLI_PLIST_DEL:\n\t\tcase CMD_CLI_PLIST_CLEAR:\n\t\tcase CMD_CLI_PLIST_MOVE:\n\t\t\tif (!plist_sync_cmd(cli, cmd))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_LOCK:\n\t\t\tif (!client_lock(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_UNLOCK:\n\t\t\tif (!client_unlock(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_SERIAL:\n\t\t\tif (!send_serial(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_PLIST_GET_SERIAL:\n\t\t\tif (!req_plist_get_serial(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_PLIST_SET_SERIAL:\n\t\t\tif (!req_plist_set_serial(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_TAGS:\n\t\t\tif (!req_get_tags(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_TOGGLE_MIXER_CHANNEL:\n\t\t\treq_toggle_mixer_channel ();\n\t\t\tbreak;\n\t\tcase CMD_TOGGLE_SOFTMIXER:\n\t\t\treq_toggle_softmixer ();\n\t\t\tbreak;\n\t\tcase CMD_GET_MIXER_CHANNEL_NAME:\n\t\t\tif (!req_get_mixer_channel_name(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_FILE_TAGS:\n\t\t\tif (!get_file_tags(client_id))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_ABORT_TAGS_REQUESTS:\n\t\t\tif (!abort_tags_requests(client_id))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_LIST_MOVE:\n\t\t\tif (!req_list_move(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_TOGGLE_EQUALIZER:\n\t\t\treq_toggle_equalizer();\n\t\t\tbreak;\n\t\tcase CMD_EQUALIZER_REFRESH:\n\t\t\treq_equalizer_refresh();\n\t\t\tbreak;\n\t\tcase CMD_EQUALIZER_PREV:\n\t\t\treq_equalizer_prev();\n\t\t\tbreak;\n\t\tcase CMD_EQUALIZER_NEXT:\n\t\t\treq_equalizer_next();\n\t\t\tbreak;\n\t\tcase CMD_TOGGLE_MAKE_MONO:\n\t\t\treq_toggle_make_mono();\n\t\t\tbreak;\n\t\tcase CMD_QUEUE_ADD:\n\t\t\tif (!req_queue_add(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_QUEUE_DEL:\n\t\t\tif (!req_queue_del(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_QUEUE_CLEAR:\n\t\t\tlogit (\"Clearing the queue\");\n\t\t\taudio_queue_clear ();\n\t\t\tadd_event_all (EV_QUEUE_CLEAR, NULL);\n\t\t\tbreak;\n\t\tcase CMD_QUEUE_MOVE:\n\t\t\tif (!req_queue_move(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tcase CMD_GET_QUEUE:\n\t\t\tif (!req_send_queue(cli))\n\t\t\t\terr = 1;\n\t\t\tbreak;\n\t\tdefault:\n\t\t\tlogit (\"Bad command (0x%x) from the client\", cmd);\n\t\t\terr = 1;\n\t}\n\n\tif (err) {\n\t\tlogit (\"Closing client connection due to error\");\n\t\tclose (cli->socket);\n\t\tdel_client (cli);\n\t}\n}\n\n/* Add clients file descriptors to fds. */\nstatic void add_clients_fds (fd_set *read, fd_set *write)\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].socket != -1) {\n\t\t\tif (locking_client() == -1 || is_locking(&clients[i]))\n\t\t\t\tFD_SET (clients[i].socket, read);\n\n\t\t\tLOCK (clients[i].events_mtx);\n\t\t\tif (!event_queue_empty(&clients[i].events))\n\t\t\t\tFD_SET (clients[i].socket, write);\n\t\t\tUNLOCK (clients[i].events_mtx);\n\t\t}\n}\n\n/* Return the maximum fd from clients and the argument. */\nstatic int max_fd (int max)\n{\n\tint i;\n\n\tif (wake_up_pipe[0] > max)\n\t\tmax = wake_up_pipe[0];\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].socket > max)\n\t\t\tmax = clients[i].socket;\n\treturn max;\n}\n\n/* Handle clients whose fds are ready to read. */\nstatic void handle_clients (fd_set *fds)\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].socket != -1\n\t\t\t\t&& FD_ISSET(clients[i].socket, fds)) {\n\t\t\tif (locking_client() == -1\n\t\t\t\t\t|| is_locking(&clients[i]))\n\t\t\t\thandle_command (i);\n\t\t\telse\n\t\t\t\tdebug (\"Not getting a command from client with\"\n\t\t\t\t\t\t\" fd %d because of lock\",\n\t\t\t\t\t\tclients[i].socket);\n\t\t}\n}\n\n/* Close all client connections sending EV_EXIT. */\nstatic void close_clients ()\n{\n\tint i;\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\tif (clients[i].socket != -1) {\n\t\t\tsend_int (clients[i].socket, EV_EXIT);\n\t\t\tclose (clients[i].socket);\n\t\t\tdel_client (&clients[i]);\n\t\t}\n}\n\n/* Handle incoming connections */\nvoid server_loop ()\n{\n\tstruct sockaddr_un client_name;\n\tsocklen_t name_len = sizeof (client_name);\n\n\tlogit (\"MOC server started, pid: %d\", getpid());\n\n\tassert (server_sock != -1);\n\n\tlog_circular_start ();\n\n\tdo {\n\t\tint res;\n\t\tfd_set fds_write, fds_read;\n\n\t\tFD_ZERO (&fds_read);\n\t\tFD_ZERO (&fds_write);\n\t\tFD_SET (server_sock, &fds_read);\n\t\tFD_SET (wake_up_pipe[0], &fds_read);\n\t\tadd_clients_fds (&fds_read, &fds_write);\n\n\t\tres = 0;\n\t\tif (!server_quit)\n\t\t\tres = select (max_fd(server_sock)+1, &fds_read,\n\t\t\t\t\t&fds_write, NULL, NULL);\n\n\t\tif (res == -1 && errno != EINTR && !server_quit)\n\t\t\tfatal (\"select() failed: %s\", xstrerror (errno));\n\n\t\tif (!server_quit && res >= 0) {\n\t\t\tif (FD_ISSET(server_sock, &fds_read)) {\n\t\t\t\tint client_sock;\n\n\t\t\t\tdebug (\"accept()ing connection...\");\n\t\t\t\tclient_sock = accept (server_sock,\n\t\t\t\t\t(struct sockaddr *)&client_name,\n\t\t\t\t\t&name_len);\n\n\t\t\t\tif (client_sock == -1)\n\t\t\t\t\tfatal (\"accept() failed: %s\", xstrerror (errno));\n\t\t\t\tlogit (\"Incoming connection\");\n\t\t\t\tif (!add_client(client_sock))\n\t\t\t\t\tbusy (client_sock);\n\t\t\t}\n\n\t\t\tif (FD_ISSET(wake_up_pipe[0], &fds_read)) {\n\t\t\t\tint w;\n\n\t\t\t\tlogit (\"Got 'wake up'\");\n\n\t\t\t\tif (read(wake_up_pipe[0], &w, sizeof(w)) < 0)\n\t\t\t\t\tfatal (\"Can't read wake up signal: %s\", xstrerror (errno));\n\t\t\t}\n\n\t\t\tsend_events (&fds_write);\n\t\t\thandle_clients (&fds_read);\n\t\t}\n\n\t\tif (server_quit)\n\t\t\tlogit (\"Exiting...\");\n\n\t} while (!server_quit);\n\n\tlog_circular_log ();\n\tlog_circular_stop ();\n\n\tclose_clients ();\n\tclients_cleanup ();\n\tclose (server_sock);\n\tserver_sock = -1;\n\tserver_shutdown ();\n}\n\nvoid set_info_bitrate (const int bitrate)\n{\n\tsound_info.bitrate = bitrate;\n\tadd_event_all (EV_BITRATE, NULL);\n}\n\nvoid set_info_channels (const int channels)\n{\n\tsound_info.channels = channels;\n\tadd_event_all (EV_CHANNELS, NULL);\n}\n\nvoid set_info_rate (const int rate)\n{\n\tsound_info.rate = rate;\n\tadd_event_all (EV_RATE, NULL);\n}\n\nvoid set_info_avg_bitrate (const int avg_bitrate)\n{\n\tsound_info.avg_bitrate = avg_bitrate;\n\tadd_event_all (EV_AVG_BITRATE, NULL);\n}\n\n/* Notify the client about change of the player state. */\nvoid state_change ()\n{\n\tadd_event_all (EV_STATE, NULL);\n}\n\nvoid ctime_change ()\n{\n\tadd_event_all (EV_CTIME, NULL);\n}\n\nvoid tags_change ()\n{\n\tadd_event_all (EV_TAGS, NULL);\n}\n\nvoid status_msg (const char *msg)\n{\n\tadd_event_all (EV_STATUS_MSG, msg);\n}\n\nvoid tags_response (const int client_id, const char *file,\n\t\tconst struct file_tags *tags)\n{\n\tassert (file != NULL);\n\tassert (tags != NULL);\n\tassert (LIMIT(client_id, CLIENTS_MAX));\n\n\tif (clients[client_id].socket != -1) {\n\t\tstruct tag_ev_response *data\n\t\t\t= (struct tag_ev_response *)xmalloc (\n\t\t\t\t\tsizeof(struct tag_ev_response));\n\n\t\tdata->file = xstrdup (file);\n\t\tdata->tags = tags_dup (tags);\n\n\t\tadd_event (&clients[client_id], EV_FILE_TAGS, data);\n\t\twake_up_server ();\n\t}\n}\n\nvoid ev_audio_start ()\n{\n\tadd_event_all (EV_AUDIO_START, NULL);\n}\n\nvoid ev_audio_stop ()\n{\n\tadd_event_all (EV_AUDIO_STOP, NULL);\n}\n\n/* Announce to clients that first file from the queue is being played\n * and therefore needs to be removed from it */\n/* XXX: this function is called from player thread and add_event_all\n *      imho doesn't properly lock all shared variables -- possible\n *      race condition??? */\nvoid server_queue_pop (const char *filename)\n{\n\tdebug (\"Queue pop -- broadcasting EV_QUEUE_DEL\");\n\tadd_event_all (EV_QUEUE_DEL, filename);\n}\n"
  },
  {
    "path": "server.h",
    "content": "#ifndef SERVER_H\n#define SERVER_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"playlist.h\"\n\n#define CLIENTS_MAX\t10\n\nvoid server_init (int debug, int foreground);\nvoid server_loop ();\nvoid server_error (const char *file, int line, const char *function,\n                   const char *msg);\nvoid state_change ();\nvoid set_info_rate (const int rate);\nvoid set_info_channels (const int channels);\nvoid set_info_bitrate (const int bitrate);\nvoid set_info_avg_bitrate (const int avg_bitrate);\nvoid tags_change ();\nvoid ctime_change ();\nvoid status_msg (const char *msg);\nvoid tags_response (const int client_id, const char *file,\n\t\tconst struct file_tags *tags);\nvoid ev_audio_start ();\nvoid ev_audio_stop ();\nvoid server_queue_pop (const char *filename);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "sndio_out.c",
    "content": "/*\n * MOC - music on console\n *\n * SNDIO sound driver for MOC by Alexander Polakov.\n * Copyright (C) 2011 Alexander Polakov <polachok@gmail.com>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#ifdef HAVE_SNDIO_H\n# include <sndio.h>\n#endif\n\n#include <assert.h>\n\n#include \"common.h\"\n#include \"audio.h\"\n#include \"log.h\"\n\n#define PCT_TO_SIO(pct)\t((127 * (pct) + 50) / 100)\n#define SIO_TO_PCT(vol)\t((100 * (vol) + 64) / 127)\n\nstatic struct sio_hdl *hdl = NULL;\nstatic int curvol = 100;\nstatic struct sound_params params = { 0, 0, 0 };\n\nstatic void sndio_close ();\n\nstatic void volume_cb (void *unused ATTR_UNUSED, unsigned int vol)\n{\n\tcurvol = SIO_TO_PCT(vol);\n}\n\nstatic int sndio_init (struct output_driver_caps *caps)\n{\n\tassert (caps != NULL);\n\n\tcaps->formats = SFMT_S8 | SFMT_U8 | SFMT_U16 | SFMT_S16 | SFMT_NE;\n\tcaps->min_channels = 1;\n\tcaps->max_channels = 2;\n\n\treturn 1;\n}\n\nstatic void sndio_shutdown ()\n{\n\tif (hdl)\n\t\tsndio_close ();\n}\n\n/* Return 0 on failure. */\nstatic int sndio_open (struct sound_params *sound_params)\n{\n\tstruct sio_par par;\n\n\tassert (hdl == NULL);\n\n\tif ((hdl = sio_open (NULL, SIO_PLAY, 0)) == NULL)\n\t\treturn 0;\n\n\tparams = *sound_params;\n\tsio_initpar (&par);\n\t/* Add volume change callback. */\n\tsio_onvol (hdl, volume_cb, NULL);\n\tpar.rate = sound_params->rate;\n\tpar.pchan = sound_params->channels;\n\tpar.bits = (((sound_params->fmt & SFMT_S8) ||\n\t             (sound_params->fmt & SFMT_U8)) ? 8 : 16);\n\tpar.le = SIO_LE_NATIVE;\n\tpar.sig = (((sound_params->fmt & SFMT_S16) ||\n\t            (sound_params->fmt & SFMT_S8)) ? 1 : 0);\n\tpar.round = par.rate / 8;\n\tpar.appbufsz = par.round * 2;\n\tlogit (\"rate %d pchan %d bits %d sign %d\",\n\t        par.rate, par.pchan, par.bits, par.sig);\n\n\tif (!sio_setpar (hdl, &par) || !sio_getpar (hdl, &par)\n\t                            || !sio_start (hdl)) {\n\t\tlogit (\"Failed to set sndio parameters.\");\n\t\tsio_close (hdl);\n\t\thdl = NULL;\n\t\treturn 0;\n\t}\n\tsio_setvol (hdl, PCT_TO_SIO(curvol));\n\n\treturn 1;\n}\n\n/* Return the number of bytes played, or -1 on error. */\nstatic int sndio_play (const char *buff, const size_t size)\n{\n\tint count;\n\n\tassert (hdl != NULL);\n\n\tcount = (int) sio_write (hdl, buff, size);\n\tif (!count && sio_eof (hdl))\n\t\treturn -1;\n\n\treturn count;\n}\n\nstatic void sndio_close ()\n{\n\tassert (hdl != NULL);\n\n\tsio_stop (hdl);\n\tsio_close (hdl);\n\thdl = NULL;\n}\n\nstatic int sndio_read_mixer ()\n{\n\treturn curvol;\n}\n\nstatic void sndio_set_mixer (int vol)\n{\n\tif (hdl != NULL)\n\t\tsio_setvol (hdl, PCT_TO_SIO (vol));\n}\n\nstatic int sndio_get_buff_fill ()\n{\n\tassert (hdl != NULL);\n\n\t/* Since we cannot stop SNDIO playing the samples already in\n\t * its buffer, there will never be anything left unheard. */\n\n\treturn 0;\n}\n\nstatic int sndio_reset ()\n{\n\tassert (hdl != NULL);\n\n\t/* SNDIO will continue to play the samples already in its buffer\n\t * regardless of what we do, so there's nothing we can do. */\n\n\treturn 1;\n}\n\nstatic int sndio_get_rate ()\n{\n\tassert (hdl != NULL);\n\n\treturn params.rate;\n}\n\nstatic void sndio_toggle_mixer_channel ()\n{\n\tassert (hdl != NULL);\n}\n\nstatic char *sndio_get_mixer_channel_name ()\n{\n\treturn xstrdup (\"moc\");\n}\n\nvoid sndio_funcs (struct hw_funcs *funcs)\n{\n\tfuncs->init = sndio_init;\n\tfuncs->shutdown = sndio_shutdown;\n\tfuncs->open = sndio_open;\n\tfuncs->close = sndio_close;\n\tfuncs->play = sndio_play;\n\tfuncs->read_mixer = sndio_read_mixer;\n\tfuncs->set_mixer = sndio_set_mixer;\n\tfuncs->get_buff_fill = sndio_get_buff_fill;\n\tfuncs->reset = sndio_reset;\n\tfuncs->get_rate = sndio_get_rate;\n\tfuncs->toggle_mixer_channel = sndio_toggle_mixer_channel;\n\tfuncs->get_mixer_channel_name = sndio_get_mixer_channel_name;\n}\n"
  },
  {
    "path": "sndio_out.h",
    "content": "#ifndef SNDIO_OUT_H\n#define SNDIO_OUT_H\n\n#include \"audio.h\"\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid sndio_funcs (struct hw_funcs *funcs);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "softmixer.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004-2008 Damian Pietras <daper@daper.net>\n *\n * Softmixer-extension Copyright (C) 2007-2008 Hendrik Iben <hiben@tzi.de>\n * Provides a software-mixer to regulate volume independent from\n * hardware.\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n  #include \"config.h\"\n#endif\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n#include <strings.h>\n#include <assert.h>\n#include <stdint.h>\n\n/* #define DEBUG */\n\n#include \"common.h\"\n#include \"audio.h\"\n#include \"audio_conversion.h\"\n#include \"softmixer.h\"\n#include \"options.h\"\n#include \"files.h\"\n#include \"log.h\"\n\nstatic int active;\nstatic int mix_mono;\nstatic int mixer_val, mixer_amp, mixer_real;\nstatic float mixer_realf;\n\nstatic void softmixer_read_config();\nstatic void softmixer_write_config();\n\n/* public code */\n\nchar *softmixer_name()\n{\n  return xstrdup((active)?SOFTMIXER_NAME:SOFTMIXER_NAME_OFF);\n}\n\nvoid softmixer_init()\n{\n  active = 0;\n  mix_mono = 0;\n  mixer_amp = 100;\n  softmixer_set_value(100);\n  softmixer_read_config();\n  logit (\"Softmixer initialized\");\n}\n\nvoid softmixer_shutdown()\n{\n  if(options_get_bool(SOFTMIXER_SAVE_OPTION))\n    softmixer_write_config();\n  logit (\"Softmixer stopped\");\n}\n\nvoid softmixer_set_value(const int val)\n{\n  mixer_val = CLAMP(0, val, 100);\n  mixer_real = (mixer_val * mixer_amp) / 100;\n  mixer_real = CLAMP(SOFTMIXER_MIN, mixer_real, SOFTMIXER_MAX);\n  mixer_realf = ((float)mixer_real)/100.0f;\n}\n\nint softmixer_get_value()\n{\n  return mixer_val;\n}\n\nvoid softmixer_set_active(int act)\n{\n  if(act)\n    active = 1;\n  else\n    active = 0;\n}\n\nint softmixer_is_active()\n{\n  return active;\n}\n\nvoid softmixer_set_mono(int mono)\n{\n  if(mono)\n    mix_mono = 1;\n  else\n    mix_mono = 0;\n}\n\nint softmixer_is_mono()\n{\n  return mix_mono;\n}\n\n/* private code */\n\nstatic void process_buffer_u8(uint8_t *buf, size_t samples);\nstatic void process_buffer_s8(int8_t *buf, size_t samples);\nstatic void process_buffer_u16(uint16_t *buf, size_t samples);\nstatic void process_buffer_s16(int16_t *buf, size_t samples);\nstatic void process_buffer_u32(uint32_t *buf, size_t samples);\nstatic void process_buffer_s32(int32_t *buf, size_t samples);\nstatic void process_buffer_float(float *buf, size_t samples);\nstatic void mix_mono_u8(uint8_t *buf, int channels, size_t samples);\nstatic void mix_mono_s8(int8_t *buf, int channels, size_t samples);\nstatic void mix_mono_u16(uint16_t *buf, int channels, size_t samples);\nstatic void mix_mono_s16(int16_t *buf, int channels, size_t samples);\nstatic void mix_mono_u32(uint32_t *buf, int channels, size_t samples);\nstatic void mix_mono_s32(int32_t *buf, int channels, size_t samples);\nstatic void mix_mono_float(float *buf, int channels, size_t samples);\n\nstatic void softmixer_read_config()\n{\n  char *cfname = create_file_name(SOFTMIXER_SAVE_FILE);\n\n  FILE *cf = fopen(cfname, \"r\");\n\n  if(cf==NULL)\n  {\n    logit (\"Unable to read softmixer configuration\");\n    return;\n  }\n\n  char *linebuffer=NULL;\n\n  int tmp;\n\n  while((linebuffer=read_line(cf)))\n  {\n    if(\n      strncasecmp\n      (\n          linebuffer\n        , SOFTMIXER_CFG_ACTIVE\n        , strlen(SOFTMIXER_CFG_ACTIVE)\n      ) == 0\n    )\n    {\n      if(sscanf(linebuffer, \"%*s %i\", &tmp)>0)\n        {\n          if(tmp>0)\n          {\n            active = 1;\n          }\n          else\n          {\n            active = 0;\n          }\n        }\n    }\n    if(\n      strncasecmp\n      (\n          linebuffer\n        , SOFTMIXER_CFG_AMP\n        , strlen(SOFTMIXER_CFG_AMP)\n      ) == 0\n    )\n    {\n      if(sscanf(linebuffer, \"%*s %i\", &tmp)>0)\n        {\n          if(RANGE(SOFTMIXER_MIN, tmp, SOFTMIXER_MAX))\n          {\n            mixer_amp = tmp;\n          }\n          else\n          {\n            logit (\"Tried to set softmixer amplification out of range.\");\n          }\n        }\n    }\n    if(\n      strncasecmp\n      (\n          linebuffer\n        , SOFTMIXER_CFG_VALUE\n        , strlen(SOFTMIXER_CFG_VALUE)\n      ) == 0\n    )\n    {\n      if(sscanf(linebuffer, \"%*s %i\", &tmp)>0)\n        {\n          if(RANGE(0, tmp, 100))\n          {\n            softmixer_set_value(tmp);\n          }\n          else\n          {\n            logit (\"Tried to set softmixer value out of range.\");\n          }\n        }\n    }\n    if(\n      strncasecmp\n      (\n          linebuffer\n        , SOFTMIXER_CFG_MONO\n        , strlen(SOFTMIXER_CFG_MONO)\n      ) == 0\n    )\n    {\n      if(sscanf(linebuffer, \"%*s %i\", &tmp)>0)\n        {\n          if(tmp>0)\n          {\n            mix_mono = 1;\n          }\n          else\n          {\n            mix_mono = 0;\n          }\n        }\n    }\n\n    free(linebuffer);\n  }\n\n\n  fclose(cf);\n}\n\nstatic void softmixer_write_config()\n{\n  char *cfname = create_file_name(SOFTMIXER_SAVE_FILE);\n\n  FILE *cf = fopen(cfname, \"w\");\n\n  if(cf==NULL)\n  {\n    logit (\"Unable to write softmixer configuration\");\n    return;\n  }\n\n  fprintf(cf, \"%s %i\\n\", SOFTMIXER_CFG_ACTIVE, active);\n  fprintf(cf, \"%s %i\\n\", SOFTMIXER_CFG_AMP, mixer_amp);\n  fprintf(cf, \"%s %i\\n\", SOFTMIXER_CFG_VALUE, mixer_val);\n  fprintf(cf, \"%s %i\\n\", SOFTMIXER_CFG_MONO, mix_mono);\n\n  fclose(cf);\n\n  logit (\"Softmixer configuration written\");\n}\n\nvoid softmixer_process_buffer(char *buf, size_t size, const struct sound_params *sound_params)\n{\n  int do_softmix, do_monomix;\n\n  debug (\"Processing %zu bytes...\", size);\n\n  do_softmix = active && (mixer_real != 100);\n  do_monomix = mix_mono && (sound_params->channels > 1);\n\n  if(!do_softmix && !do_monomix)\n    return;\n\n  long sound_endianness = sound_params->fmt & SFMT_MASK_ENDIANNESS;\n  long sound_format = sound_params->fmt & SFMT_MASK_FORMAT;\n\n  int samplewidth = sfmt_Bps(sound_format);\n  int is_float = (sound_params->fmt & SFMT_MASK_FORMAT) == SFMT_FLOAT;\n\n  int need_endianness_swap = 0;\n\n  if((sound_endianness != SFMT_NE) && (samplewidth > 1) && (!is_float))\n  {\n    need_endianness_swap = 1;\n  }\n\n  assert (size % (samplewidth * sound_params->channels) == 0);\n\n  /* setup samples to perform arithmetic */\n  if(need_endianness_swap)\n  {\n    debug (\"Converting endianness before mixing\");\n\n    if(samplewidth == 4)\n      audio_conv_bswap_32((int32_t *)buf, size / sizeof(int32_t));\n    else\n      audio_conv_bswap_16((int16_t *)buf, size / sizeof(int16_t));\n  }\n\n  switch(sound_format)\n  {\n    case SFMT_U8:\n      if(do_softmix)\n        process_buffer_u8((uint8_t *)buf, size);\n      if(do_monomix)\n        mix_mono_u8((uint8_t *)buf, sound_params->channels, size);\n      break;\n    case SFMT_S8:\n      if(do_softmix)\n        process_buffer_s8((int8_t *)buf, size);\n      if(do_monomix)\n        mix_mono_s8((int8_t *)buf, sound_params->channels, size);\n      break;\n    case SFMT_U16:\n      if(do_softmix)\n        process_buffer_u16((uint16_t *)buf, size / sizeof(uint16_t));\n      if(do_monomix)\n        mix_mono_u16((uint16_t *)buf, sound_params->channels, size / sizeof(uint16_t));\n      break;\n    case SFMT_S16:\n      if(do_softmix)\n        process_buffer_s16((int16_t *)buf, size / sizeof(int16_t));\n      if(do_monomix)\n        mix_mono_s16((int16_t *)buf, sound_params->channels, size / sizeof(int16_t));\n      break;\n    case SFMT_U32:\n      if(do_softmix)\n        process_buffer_u32((uint32_t *)buf, size / sizeof(uint32_t));\n      if(do_monomix)\n        mix_mono_u32((uint32_t *)buf, sound_params->channels, size / sizeof(uint32_t));\n      break;\n    case SFMT_S32:\n      if(do_softmix)\n        process_buffer_s32((int32_t *)buf, size / sizeof(int32_t));\n      if(do_monomix)\n        mix_mono_s32((int32_t *)buf, sound_params->channels, size / sizeof(int32_t));\n      break;\n    case SFMT_FLOAT:\n      if(do_softmix)\n        process_buffer_float((float *)buf, size / sizeof(float));\n      if(do_monomix)\n        mix_mono_float((float *)buf, sound_params->channels, size / sizeof(float));\n      break;\n  }\n\n  /* restore sample-endianness */\n  if(need_endianness_swap)\n  {\n    debug (\"Restoring endianness after mixing\");\n\n    if(samplewidth == 4)\n      audio_conv_bswap_32((int32_t *)buf, size / sizeof(int32_t));\n    else\n      audio_conv_bswap_16((int16_t *)buf, size / sizeof(int16_t));\n  }\n}\n\nstatic void process_buffer_u8(uint8_t *buf, size_t samples)\n{\n  size_t i;\n\n  debug (\"mixing\");\n\n  for(i=0; i<samples; i++)\n  {\n    int16_t tmp = buf[i];\n    tmp -= (UINT8_MAX>>1);\n    tmp *= mixer_real;\n    tmp /= 100;\n    tmp += (UINT8_MAX>>1);\n    tmp = CLAMP(0, tmp, UINT8_MAX);\n    buf[i] = (uint8_t)tmp;\n  }\n}\n\nstatic void process_buffer_s8(int8_t *buf, size_t samples)\n{\n  size_t i;\n\n  debug (\"mixing\");\n\n  for(i=0; i<samples; i++)\n  {\n    int16_t tmp = buf[i];\n    tmp *= mixer_real;\n    tmp /= 100;\n    tmp = CLAMP(INT8_MIN, tmp, INT8_MAX);\n    buf[i] = (int8_t)tmp;\n  }\n}\n\nstatic void process_buffer_u16(uint16_t *buf, size_t samples)\n{\n  size_t i;\n\n  debug (\"mixing\");\n\n  for(i=0; i<samples; i++)\n  {\n    int32_t tmp = buf[i];\n    tmp -= (UINT16_MAX>>1);\n    tmp *= mixer_real;\n    tmp /= 100;\n    tmp += (UINT16_MAX>>1);\n    tmp = CLAMP(0, tmp, UINT16_MAX);\n    buf[i] = (uint16_t)tmp;\n  }\n}\n\nstatic void process_buffer_s16(int16_t *buf, size_t samples)\n{\n  size_t i;\n\n  debug (\"mixing\");\n\n  for(i=0; i<samples; i++)\n  {\n    int32_t tmp = buf[i];\n    tmp *= mixer_real;\n    tmp /= 100;\n    tmp = CLAMP(INT16_MIN, tmp, INT16_MAX);\n    buf[i] = (int16_t)tmp;\n  }\n}\n\nstatic void process_buffer_u32(uint32_t *buf, size_t samples)\n{\n  size_t i;\n\n  debug (\"mixing\");\n\n  for(i=0; i<samples; i++)\n  {\n    int64_t tmp = buf[i];\n    tmp -= (UINT32_MAX>>1);\n    tmp *= mixer_real;\n    tmp /= 100;\n    tmp += (UINT32_MAX>>1);\n    tmp = CLAMP(0, tmp, UINT32_MAX);\n    buf[i] = (uint32_t)tmp;\n  }\n}\n\nstatic void process_buffer_s32(int32_t *buf, size_t samples)\n{\n  size_t i;\n\n  debug (\"mixing\");\n\n  for(i=0; i<samples; i++)\n  {\n    int64_t tmp = buf[i];\n    tmp *= mixer_real;\n    tmp /= 100;\n    tmp = CLAMP(INT32_MIN, tmp, INT32_MAX);\n    buf[i] = (int32_t)tmp;\n  }\n}\n\nstatic void process_buffer_float(float *buf, size_t samples)\n{\n  size_t i;\n\n  debug (\"mixing\");\n\n  for(i=0; i<samples; i++)\n  {\n    float tmp = buf[i];\n    tmp *= mixer_realf;\n    tmp = CLAMP(-1.0f, tmp, 1.0f);\n    buf[i] = tmp;\n  }\n}\n\n// Mono-Mixing\nstatic void mix_mono_u8(uint8_t *buf, int channels, size_t samples)\n{\n  int c;\n  size_t i = 0;\n\n  debug (\"making mono\");\n\n  assert (channels > 1);\n\n  while(i < samples)\n  {\n    int16_t mono = 0;\n\n    for(c=0; c<channels; c++)\n      mono += *buf++;\n\n    buf-=channels;\n\n    mono /= channels;\n    mono = MIN(mono, UINT8_MAX);  // can't be negative\n\n    for(c=0; c<channels; c++)\n      *buf++ = (uint8_t)mono;\n\n    i+=channels;\n  }\n}\n\nstatic void mix_mono_s8(int8_t *buf, int channels, size_t samples)\n{\n  int c;\n  size_t i = 0;\n\n  debug (\"making mono\");\n\n  assert (channels > 1);\n\n  while(i < samples)\n  {\n    int16_t mono = 0;\n\n    for(c=0; c<channels; c++)\n      mono += *buf++;\n\n    buf-=channels;\n\n    mono /= channels;\n    mono = CLAMP(INT8_MIN, mono, INT8_MAX);\n\n    for(c=0; c<channels; c++)\n      *buf++ = (int8_t)mono;\n\n    i+=channels;\n  }\n}\n\nstatic void mix_mono_u16(uint16_t *buf, int channels, size_t samples)\n{\n  int c;\n  size_t i = 0;\n\n  debug (\"making mono\");\n\n  assert (channels > 1);\n\n  while(i < samples)\n  {\n    int32_t mono = 0;\n\n    for(c=0; c<channels; c++)\n      mono += *buf++;\n\n    buf-=channels;\n\n    mono /= channels;\n    mono = MIN(mono, UINT16_MAX);  // can't be negative\n\n    for(c=0; c<channels; c++)\n      *buf++ = (uint16_t)mono;\n\n    i+=channels;\n  }\n}\n\nstatic void mix_mono_s16(int16_t *buf, int channels, size_t samples)\n{\n  int c;\n  size_t i = 0;\n\n  debug (\"making mono\");\n\n  assert (channels > 1);\n\n  while(i < samples)\n  {\n    int32_t mono = 0;\n\n    for(c=0; c<channels; c++)\n      mono += *buf++;\n\n    buf-=channels;\n\n    mono /= channels;\n    mono = CLAMP(INT16_MIN, mono, INT16_MAX);\n\n    for(c=0; c<channels; c++)\n      *buf++ = (int16_t)mono;\n\n    i+=channels;\n  }\n}\n\nstatic void mix_mono_u32(uint32_t *buf, int channels, size_t samples)\n{\n  int c;\n  size_t i = 0;\n\n  debug (\"making mono\");\n\n  assert (channels > 1);\n\n  while(i < samples)\n  {\n    int64_t mono = 0;\n\n    for(c=0; c<channels; c++)\n      mono += *buf++;\n\n    buf-=channels;\n\n    mono /= channels;\n    mono = MIN(mono, UINT32_MAX);  // can't be negative\n\n    for(c=0; c<channels; c++)\n      *buf++ = (uint32_t)mono;\n\n    i+=channels;\n  }\n}\n\nstatic void mix_mono_s32(int32_t *buf, int channels, size_t samples)\n{\n  int c;\n  size_t i = 0;\n\n  debug (\"making mono\");\n\n  assert (channels > 1);\n\n  while(i < samples)\n  {\n    int64_t mono = 0;\n\n    for(c=0; c<channels; c++)\n      mono += *buf++;\n\n    buf-=channels;\n\n    mono /= channels;\n    mono = CLAMP(INT32_MIN, mono, INT32_MAX);\n\n    for(c=0; c<channels; c++)\n      *buf++ = (int32_t)mono;\n\n    i+=channels;\n  }\n}\n\nstatic void mix_mono_float(float *buf, int channels, size_t samples)\n{\n  int c;\n  size_t i = 0;\n\n  debug (\"making mono\");\n\n  assert (channels > 1);\n\n  while(i < samples)\n  {\n    float mono = 0.0f;\n\n    for(c=0; c<channels; c++)\n      mono += *buf++;\n\n    buf-=channels;\n\n    mono /= channels;\n    mono = CLAMP(-1.0f, mono, 1.0f);\n\n    for(c=0; c<channels; c++)\n      *buf++ = mono;\n\n    i+=channels;\n  }\n}\n"
  },
  {
    "path": "softmixer.h",
    "content": "#ifndef SOFTMIXER_H\n#define SOFTMIXER_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define SOFTMIXER_MIN 0\n/* Allow amplification, might result in clipping... */\n#define SOFTMIXER_MAX 200\n\n#define SOFTMIXER_NAME \"Soft\"\n#define SOFTMIXER_NAME_OFF \"S.Off\"\n\n#define SOFTMIXER_CFG_ACTIVE \"Active:\"\n#define SOFTMIXER_CFG_AMP \"Amplification:\"\n#define SOFTMIXER_CFG_VALUE \"Value:\"\n#define SOFTMIXER_CFG_MONO \"Mono:\"\n\n#define SOFTMIXER_SAVE_OPTION \"Softmixer_SaveState\"\n#define SOFTMIXER_SAVE_FILE \"softmixer\"\n\nchar *softmixer_name();\n\nvoid softmixer_init();\nvoid softmixer_shutdown();\n\nint softmixer_get_value();\nvoid softmixer_set_value(const int val);\n\nint softmixer_is_active();\nvoid softmixer_set_active(int act);\n\nint softmixer_is_mono();\nvoid softmixer_set_mono(int mono);\n\nvoid softmixer_process_buffer(char *buf, const size_t size, const struct sound_params *sound_params);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "tags_cache.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005, 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <pthread.h>\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n#include <errno.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <time.h>\n#include <unistd.h>\n#include <dirent.h>\n\n#ifdef HAVE_DB_H\n# ifndef HAVE_U_INT\ntypedef unsigned char u_char;\ntypedef unsigned short u_short;\ntypedef unsigned int u_int;\ntypedef unsigned long int u_long;\n# endif\n# include <db.h>\n# define STRERROR_FN bdb_strerror\n#endif\n\n#define DEBUG\n\n#include \"common.h\"\n#include \"server.h\"\n#include \"playlist.h\"\n#include \"rbtree.h\"\n#include \"files.h\"\n#include \"tags_cache.h\"\n#include \"log.h\"\n#include \"audio.h\"\n\n#ifdef HAVE_DB_H\n# define DB_ONLY\n#else\n# define DB_ONLY ATTR_UNUSED\n#endif\n\n/* The name of the tags database in the cache directory. */\n#define TAGS_DB \"tags.db\"\n\n/* The name of the version tag file in the cache directory. */\n#define MOC_VERSION_TAG \"moc_version_tag\"\n\n/* The maximum length of the version tag (including trailing NULL). */\n#define VERSION_TAG_MAX 64\n\n/* Number used to create cache version tag to detect incompatibilities\n * between cache version stored on the disk and MOC/BerkeleyDB environment.\n *\n * If you modify the DB structure, increase this number.  You can also\n * temporarily set it to zero to disable cache activity during structural\n * changes which require multiple commits.\n */\n#define CACHE_DB_FORMAT_VERSION\t1\n\n/* How frequently to flush the tags database to disk.  A value of zero\n * disables flushing. */\n#define DB_SYNC_COUNT 5\n\n/* Element of a requests queue. */\nstruct request_queue_node\n{\n\tstruct request_queue_node *next;\n\tchar *file; /* file that this request is for (malloc()ed) */\n\tint tags_sel; /* which tags to read (TAGS_*) */\n};\n\nstruct request_queue\n{\n\tstruct request_queue_node *head;\n\tstruct request_queue_node *tail;\n};\n\nstruct tags_cache\n{\n\t/* BerkeleyDB's stuff for storing cache. */\n#ifdef HAVE_DB_H\n\tDB_ENV *db_env;\n\tDB *db;\n\tu_int32_t locker;\n#endif\n\n\tint max_items;\t\t/* maximum number of items in the cache. */\n\tstruct request_queue queues[CLIENTS_MAX]; /* requests queues for each\n\t\t\t\t\t\t     client */\n\tint stop_reader_thread; /* request for stopping read thread (if\n\t\t\t\t   non-zero) */\n\tpthread_cond_t request_cond; /* condition for signalizing new\n\t\t\t\t\trequests */\n\tpthread_mutex_t mutex; /* mutex for all above data (except db because\n\t\t\t\t  it's thread-safe) */\n\tpthread_t reader_thread; /* tid of the reading thread */\n};\n\nstruct cache_record\n{\n\ttime_t mod_time;\t\t/* last modification time of the file */\n\ttime_t atime;\t\t\t/* Time of last access. */\n\tstruct file_tags *tags;\n};\n\n/* BerkleyDB-provided error code to description function wrapper. */\n#ifdef HAVE_DB_H\nstatic inline char *bdb_strerror (int errnum)\n{\n\tchar *result;\n\n\tif (errnum > 0)\n\t\tresult = xstrerror (errnum);\n\telse\n\t\tresult = xstrdup (db_strerror (errnum));\n\n\treturn result;\n}\n#endif\n\nstatic void request_queue_init (struct request_queue *q)\n{\n\tassert (q != NULL);\n\n\tq->head = NULL;\n\tq->tail = NULL;\n}\n\nstatic void request_queue_clear (struct request_queue *q)\n{\n\tassert (q != NULL);\n\n\twhile (q->head) {\n\t\tstruct request_queue_node *o = q->head;\n\n\t\tq->head = q->head->next;\n\n\t\tfree (o->file);\n\t\tfree (o);\n\t}\n\n\tq->tail = NULL;\n}\n\n/* Remove items from the queue from the beginning to the specified file. */\nstatic void request_queue_clear_up_to (struct request_queue *q,\n                                              const char *file)\n{\n\tint stop = 0;\n\n\tassert (q != NULL);\n\n\twhile (q->head && !stop) {\n\t\tstruct request_queue_node *o = q->head;\n\n\t\tq->head = q->head->next;\n\n\t\tif (!strcmp (o->file, file))\n\t\t\tstop = 1;\n\n\t\tfree (o->file);\n\t\tfree (o);\n\t}\n\n\tif (!q->head)\n\t\tq->tail = NULL;\n}\n\nstatic void request_queue_add (struct request_queue *q, const char *file,\n                                                            int tags_sel)\n{\n\tassert (q != NULL);\n\n\tif (!q->head) {\n\t\tq->head = (struct request_queue_node *)xmalloc (\n\t\t\t\tsizeof(struct request_queue_node));\n\t\tq->tail = q->head;\n\t}\n\telse {\n\t\tassert (q->tail != NULL);\n\t\tassert (q->tail->next == NULL);\n\n\t\tq->tail->next = (struct request_queue_node *)xmalloc (\n\t\t\t\tsizeof(struct request_queue_node));\n\t\tq->tail = q->tail->next;\n\t}\n\n\tq->tail->file = xstrdup (file);\n\tq->tail->tags_sel = tags_sel;\n\tq->tail->next = NULL;\n}\n\nstatic int request_queue_empty (const struct request_queue *q)\n{\n\tassert (q != NULL);\n\n\treturn q->head == NULL;\n}\n\n/* Get the file name of the first element in the queue or NULL if the queue is\n * empty. Put tags to be read in *tags_sel. Returned memory is malloc()ed. */\nstatic char *request_queue_pop (struct request_queue *q, int *tags_sel)\n{\n\tstruct request_queue_node *n;\n\tchar *file;\n\n\tassert (q != NULL);\n\n\tif (q->head == NULL)\n\t\treturn NULL;\n\n\tn = q->head;\n\tq->head = n->next;\n\tfile = n->file;\n\t*tags_sel = n->tags_sel;\n\tfree (n);\n\n\tif (q->tail == n)\n\t\tq->tail = NULL; /* the queue is empty */\n\n\treturn file;\n}\n\n#ifdef HAVE_DB_H\nstatic size_t strlen_null (const char *s)\n{\n\treturn s ? strlen (s) : 0;\n}\n#endif\n\n#ifdef HAVE_DB_H\nstatic char *cache_record_serialize (const struct cache_record *rec, int *len)\n{\n\tchar *buf;\n\tchar *p;\n\tsize_t artist_len;\n\tsize_t album_len;\n\tsize_t title_len;\n\n\tartist_len = strlen_null (rec->tags->artist);\n\talbum_len = strlen_null (rec->tags->album);\n\ttitle_len = strlen_null (rec->tags->title);\n\n\t*len = sizeof(rec->mod_time)\n\t\t+ sizeof(rec->atime)\n\t\t+ sizeof(size_t) * 3 /* lengths of title, artist, time. */\n\t\t+ artist_len\n\t\t+ album_len\n\t\t+ title_len\n\t\t+ sizeof(rec->tags->track)\n\t\t+ sizeof(rec->tags->time);\n\n\tbuf = p = (char *)xmalloc (*len);\n\n\tmemcpy (p, &rec->mod_time, sizeof(rec->mod_time));\n\tp += sizeof(rec->mod_time);\n\n\tmemcpy (p, &rec->atime, sizeof(rec->atime));\n\tp += sizeof(rec->atime);\n\n\tmemcpy (p, &artist_len, sizeof(artist_len));\n\tp += sizeof(artist_len);\n\tif (artist_len) {\n\t\tmemcpy (p, rec->tags->artist, artist_len);\n\t\tp += artist_len;\n\t}\n\n\tmemcpy (p, &album_len, sizeof(album_len));\n\tp += sizeof(album_len);\n\tif (album_len) {\n\t\tmemcpy (p, rec->tags->album, album_len);\n\t\tp += album_len;\n\t}\n\n\tmemcpy (p, &title_len, sizeof(title_len));\n\tp += sizeof(title_len);\n\tif (title_len) {\n\t\tmemcpy (p, rec->tags->title, title_len);\n\t\tp += title_len;\n\t}\n\n\tmemcpy (p, &rec->tags->track, sizeof(rec->tags->track));\n\tp += sizeof(rec->tags->track);\n\n\tmemcpy (p, &rec->tags->time, sizeof(rec->tags->time));\n\tp += sizeof(rec->tags->time);\n\n\treturn buf;\n}\n#endif\n\n#ifdef HAVE_DB_H\nstatic int cache_record_deserialize (struct cache_record *rec,\n           const char *serialized, size_t size, int skip_tags)\n{\n\tconst char *p = serialized;\n\tsize_t bytes_left = size;\n\tsize_t str_len;\n\n\tassert (rec != NULL);\n\tassert (serialized != NULL);\n\n\tif (!skip_tags)\n\t\trec->tags = tags_new ();\n\telse\n\t\trec->tags = NULL;\n\n#define extract_num(var) \\\n\tdo { \\\n\t\tif (bytes_left < sizeof(var)) \\\n\t\t\tgoto err; \\\n\t\tmemcpy (&var, p, sizeof(var)); \\\n\t\tbytes_left -= sizeof(var); \\\n\t\tp += sizeof(var); \\\n\t} while (0)\n\n#define extract_str(var) \\\n\tdo { \\\n\t\tif (bytes_left < sizeof(str_len)) \\\n\t\t\tgoto err; \\\n\t\tmemcpy (&str_len, p, sizeof(str_len)); \\\n\t\tp += sizeof(str_len); \\\n\t\tif (bytes_left < str_len) \\\n\t\t\tgoto err; \\\n\t\tvar = xmalloc (str_len + 1); \\\n\t\tmemcpy (var, p, str_len); \\\n\t\tvar[str_len] = '\\0'; \\\n\t\tp += str_len; \\\n\t} while (0)\n\n\textract_num (rec->mod_time);\n\textract_num (rec->atime);\n\n\tif (!skip_tags) {\n\t\textract_str (rec->tags->artist);\n\t\textract_str (rec->tags->album);\n\t\textract_str (rec->tags->title);\n\t\textract_num (rec->tags->track);\n\t\textract_num (rec->tags->time);\n\n\t\tif (rec->tags->title)\n\t\t\trec->tags->filled |= TAGS_COMMENTS;\n\t\telse {\n\t\t\tif (rec->tags->artist)\n\t\t\t\tfree (rec->tags->artist);\n\t\t\trec->tags->artist = NULL;\n\n\t\t\tif (rec->tags->album)\n\t\t\t\tfree (rec->tags->album);\n\t\t\trec->tags->album = NULL;\n\t\t}\n\n\t\tif (rec->tags->time >= 0)\n\t\t\trec->tags->filled |= TAGS_TIME;\n\t}\n\n\treturn 1;\n\nerr:\n\tlogit (\"Cache record deserialization error at %tdB\", p - serialized);\n\ttags_free (rec->tags);\n\trec->tags = NULL;\n\treturn 0;\n}\n#endif\n\n/* Locked DB function prototype.\n * The function must not acquire or release DB locks. */\n#ifdef HAVE_DB_H\ntypedef void *t_locked_fn (struct tags_cache *, const char *,\n                                      int, int, DBT *, DBT *);\n#endif\n\n/* This function ensures that a DB function takes place while holding a\n * database record lock.  It also provides an initialised database thang\n * for the key and record. */\n#ifdef HAVE_DB_H\nstatic void *with_db_lock (t_locked_fn fn, struct tags_cache *c,\n                           const char *file, int tags_sel, int client_id)\n{\n\tint rc;\n\tvoid *result;\n\tDB_LOCK lock;\n\tDBT key, record;\n\n\tassert (c->db_env != NULL);\n\n\tmemset (&key, 0, sizeof (key));\n\tkey.data = (void *) file;\n\tkey.size = strlen (file);\n\n\tmemset (&record, 0, sizeof (record));\n\trecord.flags = DB_DBT_MALLOC;\n\n\trc = c->db_env->lock_get (c->db_env, c->locker, 0,\n\t\t\t&key, DB_LOCK_WRITE, &lock);\n\tif (rc)\n\t\tfatal (\"Can't get DB lock: %s\", db_strerror (rc));\n\n\tresult = fn (c, file, tags_sel, client_id, &key, &record);\n\n\trc = c->db_env->lock_put (c->db_env, &lock);\n\tif (rc)\n\t\tfatal (\"Can't release DB lock: %s\", db_strerror (rc));\n\n\tif (record.data)\n\t\tfree (record.data);\n\n\treturn result;\n}\n#endif\n\n#ifdef HAVE_DB_H\nstatic void tags_cache_remove_rec (struct tags_cache *c, const char *fname)\n{\n\tDBT key;\n\tint ret;\n\n\tassert (fname != NULL);\n\n\tdebug (\"Removing %s from the cache...\", fname);\n\n\tmemset (&key, 0, sizeof(key));\n\tkey.data = (void *)fname;\n\tkey.size = strlen (fname);\n\n\tret = c->db->del (c->db, NULL, &key, 0);\n\tif (ret)\n\t\tlogit (\"Can't remove item for %s from the cache: %s\",\n\t\t\t\tfname, db_strerror (ret));\n}\n#endif\n\n/* Remove the one element of the cache based on it's access time. */\n#ifdef HAVE_DB_H\nstatic void tags_cache_gc (struct tags_cache *c)\n{\n\tDBC *cur;\n\tDBT key;\n\tDBT serialized_cache_rec;\n\tint ret;\n\tchar *last_referenced = NULL;\n\ttime_t last_referenced_atime = time (NULL) + 1;\n\tint nitems = 0;\n\n\tc->db->cursor (c->db, NULL, &cur, 0);\n\n\tmemset (&key, 0, sizeof(key));\n\tmemset (&serialized_cache_rec, 0, sizeof(serialized_cache_rec));\n\n\tkey.flags = DB_DBT_MALLOC;\n\tserialized_cache_rec.flags = DB_DBT_MALLOC;\n\n\twhile (true) {\n\t\tstruct cache_record rec;\n\n#if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 6\n\t\tret = cur->c_get (cur, &key, &serialized_cache_rec, DB_NEXT);\n#else\n\t\tret = cur->get (cur, &key, &serialized_cache_rec, DB_NEXT);\n#endif\n\n\t\tif (ret != 0)\n\t\t\tbreak;\n\n\t\tif (cache_record_deserialize (&rec, serialized_cache_rec.data,\n\t\t\t\t\tserialized_cache_rec.size, 1)\n\t\t\t\t&& rec.atime < last_referenced_atime) {\n\t\t\tlast_referenced_atime = rec.atime;\n\n\t\t\tif (last_referenced)\n\t\t\t\tfree (last_referenced);\n\t\t\tlast_referenced = (char *)xmalloc (key.size + 1);\n\t\t\tmemcpy (last_referenced, key.data, key.size);\n\t\t\tlast_referenced[key.size] = '\\0';\n\t\t}\n\n\t\t// TODO: remove objects with serialization error.\n\n\t\tnitems++;\n\n\t\tfree (key.data);\n\t\tfree (serialized_cache_rec.data);\n\t}\n\n\tif (ret != DB_NOTFOUND)\n\t\tlog_errno (\"Searching for element to remove failed (cursor)\", ret);\n\n#if DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR < 6\n\tcur->c_close (cur);\n#else\n\tcur->close (cur);\n#endif\n\n\tdebug (\"Elements in cache: %d (limit %d)\", nitems, c->max_items);\n\n\tif (last_referenced) {\n\t\tif (nitems >= c->max_items)\n\t\t\ttags_cache_remove_rec (c, last_referenced);\n\t\tfree (last_referenced);\n\t}\n\telse\n\t\tdebug (\"Cache empty\");\n}\n#endif\n\n/* Synchronize cache every DB_SYNC_COUNT updates. */\n#ifdef HAVE_DB_H\nstatic void tags_cache_sync (struct tags_cache *c)\n{\n\tstatic int sync_count = 0;\n\n\tif (DB_SYNC_COUNT == 0)\n\t\treturn;\n\n\tsync_count += 1;\n\tif (sync_count >= DB_SYNC_COUNT) {\n\t\tsync_count = 0;\n\t\tc->db->sync (c->db, 0);\n\t}\n}\n#endif\n\n/* Add this tags object for the file to the cache. */\n#ifdef HAVE_DB_H\nstatic void tags_cache_add (struct tags_cache *c, const char *file,\n                                  DBT *key, struct file_tags *tags)\n{\n\tchar *serialized_cache_rec;\n\tint serial_len;\n\tstruct cache_record rec;\n\tDBT data;\n\tint ret;\n\n\tassert (tags != NULL);\n\n\tdebug (\"Adding/updating cache object\");\n\n\trec.mod_time = get_mtime (file);\n\trec.atime = time (NULL);\n\trec.tags = tags;\n\n\tserialized_cache_rec = cache_record_serialize (&rec, &serial_len);\n\tif (!serialized_cache_rec)\n\t\treturn;\n\n\tmemset (&data, 0, sizeof(data));\n\tdata.data = serialized_cache_rec;\n\tdata.size = serial_len;\n\n\ttags_cache_gc (c);\n\n\tret = c->db->put (c->db, NULL, key, &data, 0);\n\tif (ret)\n\t\terror_errno (\"DB put error\", ret);\n\n\ttags_cache_sync (c);\n\n\tfree (serialized_cache_rec);\n}\n#endif\n\n/* Read time tags for a file into tags structure (or create it if NULL). */\nstruct file_tags *read_missing_tags (const char *file,\n                 struct file_tags *tags, int tags_sel)\n{\n\tif (tags == NULL)\n\t\ttags = tags_new ();\n\n\tif (tags_sel & TAGS_TIME) {\n\t\tint time;\n\n\t\t/* Try to get it from the server's playlist first. */\n\t\ttime = audio_get_ftime (file);\n\n\t\tif (time != -1) {\n\t\t\ttags->time = time;\n\t\t\ttags->filled |= TAGS_TIME;\n\t\t\ttags_sel &= ~TAGS_TIME;\n\t\t}\n\t}\n\n\ttags = read_file_tags (file, tags, tags_sel);\n\n\treturn tags;\n}\n\n/* Read the selected tags for this file and add it to the cache. */\n#ifdef HAVE_DB_H\nstatic void *locked_read_add (struct tags_cache *c, const char *file,\n                              const int tags_sel, const int client_id,\n                              DBT *key, DBT *serialized_cache_rec)\n{\n\tint ret;\n\tstruct file_tags *tags = NULL;\n\n\tassert (c->db != NULL);\n\n\tret = c->db->get (c->db, NULL, key, serialized_cache_rec, 0);\n\tif (ret && ret != DB_NOTFOUND)\n\t\tlog_errno (\"Cache DB get error\", ret);\n\n\t/* If this entry is already present in the cache, we have 3 options:\n\t * we must read different tags (TAGS_*) or the tags are outdated\n\t * or this is an immediate tags read (client_id == -1) */\n\tif (ret == 0) {\n\t\tstruct cache_record rec;\n\n\t\tif (cache_record_deserialize (&rec, serialized_cache_rec->data,\n\t\t                              serialized_cache_rec->size, 0)) {\n\t\t\ttime_t curr_mtime = get_mtime (file);\n\n\t\t\tif (rec.mod_time != curr_mtime) {\n\t\t\t\tdebug (\"Tags in the cache are outdated\");\n\t\t\t\ttags_free (rec.tags);  /* remove them and reread tags */\n\t\t\t}\n\t\t\telse if ((rec.tags->filled & tags_sel) == tags_sel\n\t\t\t\t\t&& client_id == -1) {\n\t\t\t\tdebug (\"Tags are in the cache.\");\n\t\t\t\treturn rec.tags;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdebug (\"Tags in the cache are not what we want\");\n\t\t\t\ttags = rec.tags;  /* read additional tags */\n\t\t\t}\n\t\t}\n\t}\n\n\ttags = read_missing_tags (file, tags, tags_sel);\n\ttags_cache_add (c, file, key, tags);\n\n\treturn tags;\n}\n#endif\n\n/* Read the selected tags for this file and add it to the cache.\n * If client_id != -1, the server is notified using tags_response().\n * If client_id == -1, copy of file_tags is returned. */\nstatic struct file_tags *tags_cache_read_add (struct tags_cache *c DB_ONLY,\n                     const char *file, int tags_sel, int client_id)\n{\n\tstruct file_tags *tags = NULL;\n\n\tassert (file != NULL);\n\n\tdebug (\"Getting tags for %s\", file);\n\n#ifdef HAVE_DB_H\n\tif (c->max_items)\n\t\ttags = (struct file_tags *)with_db_lock (locked_read_add, c, file,\n\t\t                                         tags_sel, client_id);\n\telse\n#endif\n\t\ttags = read_missing_tags (file, tags, tags_sel);\n\n\tif (client_id != -1) {\n\t\ttags_response (client_id, file, tags);\n\t\ttags_free (tags);\n\t\ttags = NULL;\n\t}\n\n\t/* TODO: Remove the oldest items from the cache if we exceeded the maximum\n\t * cache size */\n\n\treturn tags;\n}\n\nstatic void *reader_thread (void *cache_ptr)\n{\n\tstruct tags_cache *c;\n\tint curr_queue = 0; /* index of the queue from where\n\t                       we will get the next request */\n\n\tlogit (\"Tags reader thread started\");\n\n\tassert (cache_ptr != NULL);\n\n\tc = (struct tags_cache *)cache_ptr;\n\n\tLOCK (c->mutex);\n\n\twhile (!c->stop_reader_thread) {\n\t\tint i;\n\t\tchar *request_file;\n\t\tint tags_sel = 0;\n\n\t\t/* Find the queue with a request waiting.  Begin searching at\n\t\t * curr_queue: we want to get one request from each queue,\n\t\t * and then move to the next non-empty queue. */\n\t\ti = curr_queue;\n\t\twhile (i < CLIENTS_MAX && request_queue_empty (&c->queues[i]))\n\t\t\ti++;\n\t\tif (i == CLIENTS_MAX) {\n\t\t\ti = 0;\n\t\t\twhile (i < curr_queue && request_queue_empty (&c->queues[i]))\n\t\t\t\ti++;\n\n\t\t\tif (i == curr_queue) {\n\t\t\t\tdebug (\"All queues empty, waiting\");\n\t\t\t\tpthread_cond_wait (&c->request_cond, &c->mutex);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\t\tcurr_queue = i;\n\n\t\trequest_file = request_queue_pop (&c->queues[curr_queue], &tags_sel);\n\t\tUNLOCK (c->mutex);\n\n\t\ttags_cache_read_add (c, request_file, tags_sel, curr_queue);\n\t\tfree (request_file);\n\n\t\tLOCK (c->mutex);\n\t\tcurr_queue = (curr_queue + 1) % CLIENTS_MAX;\n\t}\n\n\tUNLOCK (c->mutex);\n\n\tlogit (\"Exiting tags reader thread\");\n\n\treturn NULL;\n}\n\nstruct tags_cache *tags_cache_new (size_t max_size)\n{\n\tint i, rc;\n\tstruct tags_cache *result;\n\n\tresult = (struct tags_cache *)xmalloc (sizeof (struct tags_cache));\n\n#ifdef HAVE_DB_H\n\tresult->db_env = NULL;\n\tresult->db = NULL;\n#endif\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\trequest_queue_init (&result->queues[i]);\n\n#if CACHE_DB_FORMAT_VERSION\n\tresult->max_items = max_size;\n#else\n\tresult->max_items = 0;\n#endif\n\tresult->stop_reader_thread = 0;\n\tpthread_mutex_init (&result->mutex, NULL);\n\n\trc = pthread_cond_init (&result->request_cond, NULL);\n\tif (rc != 0)\n\t\tfatal (\"Can't create request_cond: %s\", xstrerror (rc));\n\n\trc = pthread_create (&result->reader_thread, NULL, reader_thread, result);\n\tif (rc != 0)\n\t\tfatal (\"Can't create tags cache thread: %s\", xstrerror (rc));\n\n\treturn result;\n}\n\nvoid tags_cache_free (struct tags_cache *c)\n{\n\tint i, rc;\n\n\tassert (c != NULL);\n\n\tLOCK (c->mutex);\n\tc->stop_reader_thread = 1;\n\tpthread_cond_signal (&c->request_cond);\n\tUNLOCK (c->mutex);\n\n#ifdef HAVE_DB_H\n\tif (c->db) {\n#ifndef NDEBUG\n\t\tc->db->set_errcall (c->db, NULL);\n\t\tc->db->set_msgcall (c->db, NULL);\n\t\tc->db->set_paniccall (c->db, NULL);\n#endif\n\t\tc->db->close (c->db, 0);\n\t\tc->db = NULL;\n\t}\n#endif\n\n#ifdef HAVE_DB_H\n\tif (c->db_env) {\n\t\tc->db_env->lock_id_free (c->db_env, c->locker);\n#ifndef NDEBUG\n\t\tc->db_env->set_errcall (c->db_env, NULL);\n\t\tc->db_env->set_msgcall (c->db_env, NULL);\n\t\tc->db_env->set_paniccall (c->db_env, NULL);\n#endif\n\t\tc->db_env->close (c->db_env, 0);\n\t\tc->db_env = NULL;\n\t}\n#endif\n\n\trc = pthread_join (c->reader_thread, NULL);\n\tif (rc != 0)\n\t\tfatal (\"pthread_join() on cache reader thread failed: %s\",\n\t\t        xstrerror (rc));\n\n\tfor (i = 0; i < CLIENTS_MAX; i++)\n\t\trequest_queue_clear (&c->queues[i]);\n\n\trc = pthread_mutex_destroy (&c->mutex);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy mutex\", rc);\n\trc = pthread_cond_destroy (&c->request_cond);\n\tif (rc != 0)\n\t\tlog_errno (\"Can't destroy request_cond\", rc);\n\n\tfree (c);\n}\n\n#ifdef HAVE_DB_H\nstatic void *locked_add_request (struct tags_cache *c, const char *file,\n                                 int tags_sel, int client_id,\n                                 DBT *key, DBT *serialized_cache_rec)\n{\n\tint db_ret;\n\tstruct cache_record rec;\n\n\tassert (c->db);\n\n\tdb_ret = c->db->get (c->db, NULL, key, serialized_cache_rec, 0);\n\n\tif (db_ret == DB_NOTFOUND)\n\t\treturn NULL;\n\n\tif (db_ret) {\n\t\terror_errno (\"Cache DB search error\", db_ret);\n\t\treturn NULL;\n\t}\n\n\tif (cache_record_deserialize (&rec, serialized_cache_rec->data,\n\t\t\t\tserialized_cache_rec->size, 0)) {\n\t\tif (rec.mod_time == get_mtime (file)\n\t\t\t\t&& (rec.tags->filled & tags_sel) == tags_sel) {\n\t\t\ttags_response (client_id, file, rec.tags);\n\t\t\ttags_free (rec.tags);\n\t\t\tdebug (\"Tags are present in the cache\");\n\t\t\treturn (void *)1;\n\t\t}\n\n\t\ttags_free (rec.tags);\n\t\tdebug (\"Found outdated or incomplete tags in the cache\");\n\t}\n\n\treturn NULL;\n}\n#endif\n\nvoid tags_cache_add_request (struct tags_cache *c, const char *file,\n                                        int tags_sel, int client_id)\n{\n\tvoid *rc = NULL;\n\n\tassert (c != NULL);\n\tassert (file != NULL);\n\tassert (LIMIT(client_id, CLIENTS_MAX));\n\n\tdebug (\"Request for tags for '%s' from client %d\", file, client_id);\n\n#ifdef HAVE_DB_H\n\tif (c->max_items)\n\t\trc = with_db_lock (locked_add_request, c, file, tags_sel, client_id);\n#endif\n\n\tif (!rc) {\n\t\tLOCK (c->mutex);\n\t\trequest_queue_add (&c->queues[client_id], file, tags_sel);\n\t\tpthread_cond_signal (&c->request_cond);\n\t\tUNLOCK (c->mutex);\n\t}\n}\n\nvoid tags_cache_clear_queue (struct tags_cache *c, int client_id)\n{\n\tassert (c != NULL);\n\tassert (LIMIT(client_id, CLIENTS_MAX));\n\n\tLOCK (c->mutex);\n\trequest_queue_clear (&c->queues[client_id]);\n\tdebug (\"Cleared requests queue for client %d\", client_id);\n\tUNLOCK (c->mutex);\n}\n\n/* Remove all pending requests from the queue for the given client up to\n * the request associated with the given file. */\nvoid tags_cache_clear_up_to (struct tags_cache *c, const char *file,\n                                                      int client_id)\n{\n\tassert (c != NULL);\n\tassert (LIMIT(client_id, CLIENTS_MAX));\n\tassert (file != NULL);\n\n\tLOCK (c->mutex);\n\tdebug (\"Removing requests for client %d up to file %s\", client_id,\n\t\t\tfile);\n\trequest_queue_clear_up_to (&c->queues[client_id], file);\n\tUNLOCK (c->mutex);\n}\n\n#if defined(HAVE_DB_H) && !defined(NDEBUG)\nstatic void db_err_cb (const DB_ENV *unused ATTR_UNUSED, const char *errpfx,\n                                                         const char *msg)\n{\n\tassert (msg);\n\n\tif (errpfx && errpfx[0])\n\t\tlogit (\"BDB said: %s: %s\", errpfx, msg);\n\telse\n\t\tlogit (\"BDB said: %s\", msg);\n}\n#endif\n\n#if defined(HAVE_DB_H) && !defined(NDEBUG)\nstatic void db_msg_cb (const DB_ENV *unused ATTR_UNUSED, const char *msg)\n{\n\tassert (msg);\n\n\tlogit (\"BDB said: %s\", msg);\n}\n#endif\n\n#if defined(HAVE_DB_H) && !defined(NDEBUG)\nstatic void db_panic_cb (DB_ENV *unused ATTR_UNUSED, int errval)\n{\n\tlog_errno (\"BDB said\", errval);\n}\n#endif\n\n/* Purge content of a directory. */\n#ifdef HAVE_DB_H\nstatic int purge_directory (const char *dir_path)\n{\n\tDIR *dir;\n\tstruct dirent *d;\n\n\tlogit (\"Purging %s...\", dir_path);\n\n\tdir = opendir (dir_path);\n\tif (!dir) {\n\t\tchar *err = xstrerror (errno);\n\t\tlogit (\"Can't open directory %s: %s\", dir_path, err);\n\t\tfree (err);\n\t\treturn 0;\n\t}\n\n\twhile ((d = readdir (dir))) {\n\t\tstruct stat st;\n\t\tchar *fpath;\n\t\tint len;\n\n\t\tif (!strcmp (d->d_name, \".\") || !strcmp (d->d_name, \"..\"))\n\t\t\tcontinue;\n\n\t\tlen = strlen (dir_path) + strlen (d->d_name) + 2;\n\t\tfpath = (char *)xmalloc (len);\n\t\tsnprintf (fpath, len, \"%s/%s\", dir_path, d->d_name);\n\n\t\tif (stat (fpath, &st) < 0) {\n\t\t\tchar *err = xstrerror (errno);\n\t\t\tlogit (\"Can't stat %s: %s\", fpath, err);\n\t\t\tfree (err);\n\t\t\tfree (fpath);\n\t\t\tclosedir (dir);\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (S_ISDIR(st.st_mode)) {\n\t\t\tif (!purge_directory (fpath)) {\n\t\t\t\tfree (fpath);\n\t\t\t\tclosedir (dir);\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\tlogit (\"Removing directory %s...\", fpath);\n\t\t\tif (rmdir (fpath) < 0) {\n\t\t\t\tchar *err = xstrerror (errno);\n\t\t\t\tlogit (\"Can't remove %s: %s\", fpath, err);\n\t\t\t\tfree (err);\n\t\t\t\tfree (fpath);\n\t\t\t\tclosedir (dir);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tlogit (\"Removing file %s...\", fpath);\n\n\t\t\tif (unlink (fpath) < 0) {\n\t\t\t\tchar *err = xstrerror (errno);\n\t\t\t\tlogit (\"Can't remove %s: %s\", fpath, err);\n\t\t\t\tfree (err);\n\t\t\t\tfree (fpath);\n\t\t\t\tclosedir (dir);\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\n\t\tfree (fpath);\n\t}\n\n\tclosedir (dir);\n\treturn 1;\n}\n#endif\n\n/* Create a MOC/db version string.\n *\n * @param buf Output buffer (at least VERSION_TAG_MAX chars long)\n */\n#ifdef HAVE_DB_H\nstatic const char *create_version_tag (char *buf)\n{\n\tint db_major;\n\tint db_minor;\n\n\tdb_version (&db_major, &db_minor, NULL);\n\n#ifdef PACKAGE_REVISION\n\tsnprintf (buf, VERSION_TAG_MAX, \"%d %d %d r%s\",\n\t          CACHE_DB_FORMAT_VERSION, db_major, db_minor, PACKAGE_REVISION);\n#else\n\tsnprintf (buf, VERSION_TAG_MAX, \"%d %d %d\",\n\t          CACHE_DB_FORMAT_VERSION, db_major, db_minor);\n#endif\n\n\treturn buf;\n}\n#endif\n\n/* Check version of the cache directory.  If it was created\n * using format not handled by this version of MOC, return 0. */\n#ifdef HAVE_DB_H\nstatic int cache_version_matches (const char *cache_dir)\n{\n\tchar *fname = NULL;\n\tchar disk_version_tag[VERSION_TAG_MAX];\n\tssize_t rres;\n\tFILE *f;\n\tint compare_result = 0;\n\n\tfname = (char *)xmalloc (strlen (cache_dir) + sizeof (MOC_VERSION_TAG) + 1);\n\tsprintf (fname, \"%s/%s\", cache_dir, MOC_VERSION_TAG);\n\n\tf = fopen (fname, \"r\");\n\tif (!f) {\n\t\tlogit (\"No %s in cache directory\", MOC_VERSION_TAG);\n\t\tfree (fname);\n\t\treturn 0;\n\t}\n\n\trres = fread (disk_version_tag, 1, sizeof (disk_version_tag) - 1, f);\n\tif (rres == sizeof (disk_version_tag) - 1) {\n\t\tlogit (\"On-disk version tag too long\");\n\t}\n\telse {\n\t\tchar *ptr, cur_version_tag[VERSION_TAG_MAX];\n\n\t\tdisk_version_tag[rres] = '\\0';\n\t\tptr = strrchr (disk_version_tag, '\\n');\n\t\tif (ptr)\n\t\t\t*ptr = '\\0';\n\t\tptr = strrchr (disk_version_tag, ' ');\n\t\tif (ptr && ptr[1] == 'r')\n\t\t\t*ptr = '\\0';\n\n\t\tcreate_version_tag (cur_version_tag);\n\t\tptr = strrchr (cur_version_tag, '\\n');\n\t\tif (ptr)\n\t\t\t*ptr = '\\0';\n\t\tptr = strrchr (cur_version_tag, ' ');\n\t\tif (ptr && ptr[1] == 'r')\n\t\t\t*ptr = '\\0';\n\n\t\tcompare_result = !strcmp (disk_version_tag, cur_version_tag);\n\t}\n\n\tfclose (f);\n\tfree (fname);\n\n\treturn compare_result;\n}\n#endif\n\n#ifdef HAVE_DB_H\nstatic void write_cache_version (const char *cache_dir)\n{\n\tchar cur_version_tag[VERSION_TAG_MAX];\n\tchar *fname = NULL;\n\tFILE *f;\n\tint rc;\n\n\tfname = (char *)xmalloc (strlen (cache_dir) + sizeof (MOC_VERSION_TAG) + 1);\n\tsprintf (fname, \"%s/%s\", cache_dir, MOC_VERSION_TAG);\n\n\tf = fopen (fname, \"w\");\n\tif (!f) {\n\t\tlog_errno (\"Error opening cache\", errno);\n\t\tfree (fname);\n\t\treturn;\n\t}\n\n\tcreate_version_tag (cur_version_tag);\n\trc = fwrite (cur_version_tag, strlen (cur_version_tag), 1, f);\n\tif (rc != 1)\n\t\tlogit (\"Error writing cache version tag: %d\", rc);\n\n\tfree (fname);\n\tfclose (f);\n}\n#endif\n\n/* Make sure that the cache directory exists and clear it if necessary. */\n#ifdef HAVE_DB_H\nstatic int prepare_cache_dir (const char *cache_dir)\n{\n\tif (mkdir (cache_dir, 0700) == 0) {\n\t\twrite_cache_version (cache_dir);\n\t\treturn 1;\n\t}\n\n\tif (errno != EEXIST) {\n\t\terror_errno (\"Failed to create directory for tags cache\", errno);\n\t\treturn 0;\n\t}\n\n\tif (!cache_version_matches (cache_dir)) {\n\t\tlogit (\"Tags cache directory is the wrong version, purging....\");\n\n\t\tif (!purge_directory (cache_dir))\n\t\t\treturn 0;\n\t\twrite_cache_version (cache_dir);\n\t}\n\n\treturn 1;\n}\n#endif\n\nvoid tags_cache_load (struct tags_cache *c DB_ONLY,\n                      const char *cache_dir DB_ONLY)\n{\n\tassert (c != NULL);\n\tassert (cache_dir != NULL);\n\n#ifdef HAVE_DB_H\n\tint ret;\n\n\tif (!c->max_items)\n\t\treturn;\n\n\tif (!prepare_cache_dir (cache_dir)) {\n\t\terror (\"Can't prepare cache directory!\");\n\t\tgoto err;\n\t}\n\n\tret = db_env_create (&c->db_env, 0);\n\tif (ret) {\n\t\terror_errno (\"Can't create DB environment\", ret);\n\t\tgoto err;\n\t}\n\n#ifndef NDEBUG\n\tc->db_env->set_errcall (c->db_env, db_err_cb);\n\tc->db_env->set_msgcall (c->db_env, db_msg_cb);\n\tret = c->db_env->set_paniccall (c->db_env, db_panic_cb);\n\tif (ret)\n\t\tlogit (\"Could not set DB panic callback\");\n#endif\n\n\tret = c->db_env->open (c->db_env, cache_dir,\n\t                       DB_CREATE | DB_PRIVATE | DB_INIT_MPOOL |\n\t                       DB_THREAD | DB_INIT_LOCK, 0);\n\tif (ret) {\n\t\terror (\"Can't open DB environment (%s): %s\",\n\t\t\t\tcache_dir, db_strerror (ret));\n\t\tgoto err;\n\t}\n\n\tret = c->db_env->lock_id (c->db_env, &c->locker);\n\tif (ret) {\n\t\terror_errno (\"Failed to get DB locker\", ret);\n\t\tgoto err;\n\t}\n\n\tret = db_create (&c->db, c->db_env, 0);\n\tif (ret) {\n\t\terror_errno (\"Failed to create cache db\", ret);\n\t\tgoto err;\n\t}\n\n#ifndef NDEBUG\n\tc->db->set_errcall (c->db, db_err_cb);\n\tc->db->set_msgcall (c->db, db_msg_cb);\n\tret = c->db->set_paniccall (c->db, db_panic_cb);\n\tif (ret)\n\t\tlogit (\"Could not set DB panic callback\");\n#endif\n\n\tret = c->db->open (c->db, NULL, TAGS_DB, NULL, DB_BTREE,\n\t                                DB_CREATE | DB_THREAD, 0);\n\tif (ret) {\n\t\terror_errno (\"Failed to open (or create) tags cache db\", ret);\n\t\tgoto err;\n\t}\n\n\treturn;\n\nerr:\n\tif (c->db) {\n#ifndef NDEBUG\n\t\tc->db->set_errcall (c->db, NULL);\n\t\tc->db->set_msgcall (c->db, NULL);\n\t\tc->db->set_paniccall (c->db, NULL);\n#endif\n\t\tc->db->close (c->db, 0);\n\t\tc->db = NULL;\n\t}\n\tif (c->db_env) {\n#ifndef NDEBUG\n\t\tc->db_env->set_errcall (c->db_env, NULL);\n\t\tc->db_env->set_msgcall (c->db_env, NULL);\n\t\tc->db_env->set_paniccall (c->db_env, NULL);\n#endif\n\t\tc->db_env->close (c->db_env, 0);\n\t\tc->db_env = NULL;\n\t}\n\tc->max_items = 0;\n\terror (\"Failed to initialise tags cache: caching disabled\");\n#endif\n}\n\n/* Immediately read tags for a file bypassing the request queue. */\nstruct file_tags *tags_cache_get_immediate (struct tags_cache *c,\n                                  const char *file, int tags_sel)\n{\n\tstruct file_tags *tags;\n\n\tassert (c != NULL);\n\tassert (file != NULL);\n\n\tdebug (\"Immediate tags read for %s\", file);\n\n\tif (!is_url (file))\n\t\ttags = tags_cache_read_add (c, file, tags_sel, -1);\n\telse\n\t\ttags = tags_new ();\n\n\treturn tags;\n}\n"
  },
  {
    "path": "tags_cache.h",
    "content": "#ifndef TAGS_CACHE_H\n#define TAGS_CACHE_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nstruct file_tags;\nstruct tags_cache;\n\n/* Administrative functions: */\nstruct tags_cache *tags_cache_new (size_t max_size);\nvoid tags_cache_free (struct tags_cache *c);\n\n/* Request queue manipulation functions: */\nvoid tags_cache_clear_queue (struct tags_cache *c, int client_id);\nvoid tags_cache_clear_up_to (struct tags_cache *c, const char *file,\n                                                      int client_id);\n\n/* Cache DB manipulation functions: */\nvoid tags_cache_load (struct tags_cache *c, const char *cache_dir);\nvoid tags_cache_add_request (struct tags_cache *c, const char *file,\n                                        int tags_sel, int client_id);\nstruct file_tags *tags_cache_get_immediate (struct tags_cache *c,\n                                  const char *file, int tags_sel);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "themes/Makefile.am",
    "content": "themesdir = $(pkgdatadir)/themes\nthemes_DATA = transparent-background \\\n\t      example_theme \\\n\t      nightly_theme \\\n\t      green_theme \\\n\t      yellow_red_theme \\\n\t      black_theme \\\n\t      moca_theme \\\n\t      red_theme \\\n\t      darkdot_theme\nEXTRA_DIST = $(themes_DATA)\n"
  },
  {
    "path": "themes/black_theme",
    "content": "# Black theme by Arn <arn.alduomacil@gmail.com>\n\nbackground\t\t\t\t= white\t\tblack\nframe\t\t\t\t\t= white\t\tblack\nwindow_title\t\t\t= white\t\tblack\ndirectory\t\t\t\t= white\t\tblack\nselected_directory\t\t= white\t\tblack\treverse\nplaylist\t\t\t\t= white\t\tblack\nselected_playlist\t\t= white\t\tblack\treverse\nfile\t\t\t\t\t= white\t\tblack\nselected_file\t\t\t= white\t\tblack\treverse\nmarked_file\t\t\t\t= white\t\tblack\tbold\nmarked_selected_file\t= white\t\tblack\tbold,reverse\ninfo\t\t\t\t\t= white\t\tblack\tunderline\nstatus\t\t\t\t\t= white\t\tblack\ntitle\t\t\t\t\t= white\t\tblack\tbold\nstate\t\t\t\t\t= white\t\tblack\ncurrent_time\t\t\t= white\t\tblack\tbold\ntime_left\t\t\t\t= white\t\tblack\tbold\ntotal_time\t\t\t\t= white\t\tblack\tbold\ntime_total_frames\t\t= white\t\tblack\nsound_parameters\t\t= white\t\tblack\tbold\nlegend\t\t\t\t\t= white\t\tblack\ndisabled\t\t\t\t= white\t\tblack\nenabled\t\t\t\t\t= white\t\tblack\tbold\nempty_mixer_bar\t\t\t= white\t\tblack\nfilled_mixer_bar\t\t= white\t\tblack\treverse\nempty_time_bar\t\t\t= white\t\tblack\nfilled_time_bar\t\t\t= white\t\tblack\treverse\nentry\t\t\t\t\t= white\t\tblack\nentry_title\t\t\t\t= white\t\tblack\nerror\t\t\t\t\t= white\t\tblack\tbold,underline\nmessage\t\t\t\t\t= white\t\tblack\nplist_time\t\t\t\t= white\t\tblack\n"
  },
  {
    "path": "themes/darkdot_theme",
    "content": "# Theme to match the 'darkdot' vim theme, by David Lazar (david_bv|at|yahoo|com)\n\nbackground\t\t= default\tdefault\nframe\t\t\t= white\t\tdefault\nwindow_title\t\t= white\t\tdefault\ndirectory\t\t= blue\t\tdefault\tbold\nselected_directory\t= black\t\tcyan\nplaylist\t\t= white\t\tdefault\tbold\nselected_playlist\t= black\t\tcyan\nfile\t\t\t= white\t\tdefault\nselected_file\t\t= black\t\tcyan\nmarked_file\t\t= white\t\tdefault\tbold\nmarked_selected_file\t= white\t\tcyan\tbold\ninfo\t\t\t= white\t\tdefault\nstatus\t\t\t= white\t\tdefault\ntitle\t\t\t= white\t\tdefault bold\nstate\t\t\t= white\t\tdefault\tbold\ncurrent_time\t\t= white\t\tdefault\tbold\ntime_left\t\t= black\t\tdefault\tbold\ntotal_time\t\t= white\t\tdefault\ntime_total_frames\t= white\t\tdefault\nsound_parameters\t= white\t\tdefault bold\nlegend\t\t\t= white\t\tdefault\ndisabled\t\t= black\t\tdefault bold\nenabled\t\t\t= white\t\tdefault\tbold\nempty_mixer_bar\t\t= white\t\tdefault\nfilled_mixer_bar\t= black\t\twhite\nempty_time_bar\t\t= white\t\tdefault\nfilled_time_bar\t\t= black\t\twhite\nentry\t\t\t= white\t\tdefault\nentry_title\t\t= white\t\tdefault\nerror\t\t\t= white\t\tred\tbold\nmessage\t\t\t= white\t\tdefault\nplist_time\t\t= white\t\tdefault\n"
  },
  {
    "path": "themes/example_theme",
    "content": "# Example color theme for MOC.\n# You can use a theme by copying it to ~/.moc/themes directory and using\n# Theme config option or -T command line option.\n#\n# Fill free to make your own themes and send me them. It will be included in\n# official MOC releases or on the MOC web site.\n#\n# The format of this file is:\n# Lines beginning with # are comments.\n# Blank lines are ignored.\n# Every other line is expected to be in format:\n#\n# ELEMENT = FOREGROUND_COLOR BACKGROUND_COLOR [ATTRIBUTE[,ATTRIBUTE,..]]\n#\n# or\n#\n# colordef COLOR = RED GREEN BLUE\n#\n# Where names are case insensitive.\n#\n# ELEMENT is an element of MOC interface. This can be:\n# background         - default background for regions when nothing is displayed\n# frame              - frames for windows\n# window_title       - the title of the window (eg name of the current\n#                      directory)\n# directory          - a directory in the menu\n# selected_directory - a directory that is selected using arrows\n# playlist           - playlist file\n# selected_playlist  - see selected directory\n# file               - an ordinary file in the menu (mp3, ogg, ...)\n# selected_file      - see selected directory\n# marked_file        - a file that is currently being played\n# marked_selected_file - a file that is currently being played and is also\n#                      selected using arrows\n# info               - information shown at the right side of files\n# selected_info      - see selected directory\n# marked_info        - a file (its time) that is currently being played\n# marked_selected_info - a file (its time) that is currently being played\n#                      and is also selected using arrows\n# status             - the status line with a message\n# title              - the title of the file that is currently being played\n# state              - the state: play, stop, or paused (>, [], ||)\n# current_time       - current time of playing\n# time_left          - the time left to the end of playing the current file\n# total_time         - the length of the currently played file\n# time_total_frames  - the brackets outside the total time of a file ([10:13])\n# sound_parameters   - the frequency and bitrate numbers\n# legend             - \"KHz\" and \"Kbps\"\n# disabled           - disabled element ([STEREO])\n# enabled            - enabled element\n# empty_mixer_bar    - \"empty\" part of the volume bar\n# filled_mixer_bar   - \"filled\" part of the volume bar\n# empty_time_bar     - \"empty\" part of the time bar\n# filled_time_bar    - \"filled\" part of the time bar\n# entry              - place wher user can type a search query or a file name\n# entry_title        - the title of an entry\n# error              - error message\n# message            - information message\n# plist_time         - total time of displayed items\n#\n# FOREGOUND_COLOR and BACKGROUND_COLOR can have one of the following values:\n# black, red, green, yellow, blue, magenta, cyan, white, default (can be\n# transparent), grey (not standard, but works)\n#\n# Optional ATTRIBUTE parameters can be (from ncurses manual):\n# normal      - default (no highlight)\n# standout    - best highlighting mode of the terminal\n# underline   - underlining\n# reverse     - reverse video\n# blink       - blinking\n# dim         - half bright\n# bold        - extra bright or bold\n# protect     - protected mode\n#\n# You can specify a list of attributes separated by commas: attr1,attr2,attr3.\n# Don't use spaces anywhere in such a list.\n#\n# With colordef you can change the definition of a color. It works only if\n# your terminal supports it, if not those lines will be silently ignored.\n# COLOR must be a valid color name and the RED GREEN and BLUE are numbers\n# from 0 to 1000. Example:\n#\n# colordef red = 1000 0 0\n#\n# HINT: you have only 8 colors, but combined with attributes bold and/or\n# reversed you actually get more colors.\n#\n# If you don't specify some elements, the default values will be used.\n#\n# Here follows the default configuration:\nbackground\t\t= white\t\tblue\nframe\t\t\t= white\t\tblue\nwindow_title\t\t= white\t\tblue\ndirectory\t\t= white\t\tblue\tbold\nselected_directory\t= white\t\tblack\tbold\nplaylist\t\t= white\t\tblue\tbold\nselected_playlist\t= white\t\tblack\tbold\nfile\t\t\t= white\t\tblue\nselected_file\t\t= white\t\tblack\nmarked_file\t\t= green\t\tblue\tbold\nmarked_selected_file\t= green\t\tblack\tbold\ninfo\t\t\t= blue\t\tblue\tbold\nselected_info\t\t= blue\t\tblack\tbold\nmarked_info\t\t= blue\t\tblue\tbold\nmarked_selected_info\t= blue\t\tblack\tbold\nstatus\t\t\t= white\t\tblue\ntitle\t\t\t= white\t\tblue\tbold\nstate\t\t\t= white\t\tblue\tbold\ncurrent_time\t\t= white\t\tblue\tbold\ntime_left\t\t= white\t\tblue\tbold\ntotal_time\t\t= white\t\tblue\tbold\ntime_total_frames\t= white\t\tblue\nsound_parameters\t= white\t\tblue\tbold\nlegend\t\t\t= white\t\tblue\ndisabled\t\t= blue\t\tblue\tbold\nenabled\t\t\t= white\t\tblue\tbold\nempty_mixer_bar\t\t= white\t\tblue\nfilled_mixer_bar\t= black\t\tcyan\nempty_time_bar\t\t= white\t\tblue\nfilled_time_bar\t\t= black\t\tcyan\nentry\t\t\t= white\t\tblue\nentry_title\t\t= black\t\tcyan\nerror\t\t\t= red\t\tblue\tbold\nmessage\t\t\t= green\t\tblue\tbold\nplist_time\t\t= white\t\tblue\n"
  },
  {
    "path": "themes/green_theme",
    "content": "# green theme by Jacek Lehmann\n# best viewed on shaded or black terminal\n\nbackground\t\t= default\tdefault\nframe\t\t\t= black\t\tdefault\nwindow_title\t\t= green\t\tdefault\ndirectory\t\t= red \t\tdefault\nselected_directory\t= yellow\tdefault\nplaylist\t\t= blue\t\tdefault\nselected_playlist\t= magenta\tdefault\nfile\t\t\t= green\t\tdefault\nselected_file\t\t= cyan\t\tdefault\nmarked_file\t\t= green\t\tdefault\tbold\nmarked_selected_file\t= cyan\t\tdefault\tbold\ninfo\t\t\t= cyan\t\tdefault\nstatus\t\t\t= magenta\tdefault\ntitle\t\t\t= green\t\tdefault\nstate\t\t\t= magenta\tdefault\ncurrent_time\t\t= magenta\tdefault\ntime_left\t\t= cyan\t\tdefault\ntotal_time\t\t= cyan\t\tdefault\ntime_total_frames\t= magenta\tdefault\nsound_parameters\t= cyan\t\tdefault\nlegend\t\t\t= magenta\tdefault\ndisabled\t\t= black\t\tdefault\nenabled\t\t\t= yellow\tdefault\nempty_mixer_bar\t\t= green\t\tdefault\nfilled_mixer_bar\t= black\t\tgreen\nempty_time_bar\t\t= green\t\tdefault\nfilled_time_bar\t\t= black\t\tgreen\nentry\t\t\t= yellow\tdefault\nentry_title\t\t= red\t\tdefault\nerror\t\t\t= red\t\tdefault\nmessage\t\t\t= yellow\tdefault\nplist_time\t\t= magenta\tdefault\n"
  },
  {
    "path": "themes/moca_theme",
    "content": "#\n# Theme: moca\n# Author: Nicola Vitale <nivit@email.it>\n#\nbackground\t\t= white\t\tblack\nframe\t\t\t= white\t\tblack\nwindow_title\t\t= yellow\tblack\tbold\ndirectory\t\t= white\t\tblack\nselected_directory\t= white\t\tblack\tbold\nplaylist\t\t= white\t\tblack\nselected_playlist\t= cyan\t\tblack\tbold\nfile\t\t\t= white\t\tblack\nselected_file\t\t= yellow\tred\tbold\nmarked_file\t\t= cyan\t\tblack\tblink,bold\nmarked_selected_file\t= cyan\t\tred\tblink,bold\ninfo\t\t\t= magenta\tblack\tbold\nstatus\t\t\t= yellow\tblack\tbold\ntitle\t\t\t= cyan\t\tblack\tbold\nstate\t\t\t= red\t\tblack\tbold\ncurrent_time\t\t= green\t\tblack\tbold\ntime_left\t\t= magenta\tblack\tbold\ntotal_time\t\t= red\t\tblack\tbold\ntime_total_frames\t= red\t\tblack\tbold\nsound_parameters\t= white\t\tblack\tbold\nlegend\t\t\t= white\t\tblack\ndisabled\t\t= white\t\tblack\tbold\nenabled\t\t\t= blue\t\tblack\tbold\nempty_mixer_bar\t\t= cyan\t\tblue\nfilled_mixer_bar\t= blue\t\tcyan\nempty_time_bar\t\t= green\t\tmagenta\nfilled_time_bar\t\t= magenta\tgreen\nentry\t\t\t= white\t\tblack\nentry_title\t\t= magenta\tblack\tbold\nerror\t\t\t= red\t\tblack\tbold\nmessage\t\t\t= green\t\tblack\tbold\nplist_time\t\t= red\t\tblack\tbold\n"
  },
  {
    "path": "themes/nightly_theme",
    "content": "# Author: Wim Speekenbrink <w.speek@wanadoo.nl>\n\nbackground          = blue      black\nframe               = blue      black   bold\nwindow_title        = blue      black   bold\ndirectory           = blue      black   bold\nselected_directory  = black     magenta\nplaylist            = blue      black   bold\nselected_playlist   = black     magenta\nfile                = blue      black   bold\nselected_file       = black     magenta\nmarked_file         = green     black   bold\nmarked_selected_file = green    magenta bold\ninfo                = green     black   bold\nstatus              = blue      black   bold\ntitle               = green     black   bold\nstate               = blue      black   bold\ncurrent_time        = magenta   black   bold\ntime_left           = magenta   black   bold\ntotal_time          = magenta   black   bold\ntime_total_frames   = blue      black   bold\nsound_parameters    = magenta   black   bold\nlegend              = blue      black   bold\ndisabled            = black     black\nenabled             = blue      black   bold\nempty_mixer_bar     = blue      black   bold\nfilled_mixer_bar    = black     magenta\nempty_time_bar      = blue      black   bold\nfilled_time_bar     = black     magenta\nentry               = green     black   bold\nentry_title         = green     black   bold\nerror               = red       black   bold\nmessage             = green     black   bold\nplist_time          = green\tblack\tbold\n"
  },
  {
    "path": "themes/red_theme",
    "content": "# rednblack theme by yyz\n# works good on a light term\n\n#property\t\tfg\tbg\tfx\nbackground =\t\tdefault\tdefault\nframe =\t\t\tblack\tdefault\nwindow_title =\t\tblack\tdefault\ndirectory =\t\tred\tdefault\nselected_directory =\tblack\tdefault\tbold\nplaylist =\t\tblue\tdefault\nselected_playlist =\tblack\tdefault\tbold\nfile =\t\t\tred\tdefault\nselected_file =\t\tblack\tdefault\tbold\nmarked_file =\t\tred\tdefault\tunderline\nmarked_selected_file = black    default\tbold\ninfo =\t\t\tred\tdefault\nstatus =\t\tblack\tdefault\ntitle =\t\t\tred\tdefault\nstate =\t\t\tblack\tdefault\ncurrent_time =\t\tblack\tdefault\ntime_left =\t\tblack\tdefault\ntotal_time =\t\tblack\tdefault\ntime_total_frames =\tblack\tdefault\nsound_parameters =\tblack\tdefault\nlegend =\t\tblack\tdefault\ndisabled =\t\twhite\tdefault\nenabled =\t\tblack\tdefault\nempty_mixer_bar =\tblack\tdefault\nfilled_mixer_bar =\tblack\tred\nempty_time_bar =\tblack\tdefault\nfilled_time_bar =\tblack\tred\nentry =\t\t\tblack\tdefault\nentry_title =\t\tred\tdefault\nerror =\t\t\tred\tdefault\nmessage =\t\tblack\tdefault\nplist_time =\t\tblack\tdefault\n"
  },
  {
    "path": "themes/transparent-background",
    "content": "# Transparent background theme by Marcin Michałowski\n\nbackground\t\t= default\t\tdefault\nframe\t\t\t= white\t\tdefault\nwindow_title\t\t= white\t\tdefault\ndirectory\t\t= white\t\tdefault\tbold\nselected_directory\t= white\t\tblue\tbold\nplaylist\t\t= white\t\tdefault\tbold\nselected_playlist\t= white\t\tblue\tbold\nfile\t\t\t= white\t\tdefault\nselected_file\t\t= white\t\tblue\nmarked_file\t\t= green\t\tdefault\tbold\nmarked_selected_file\t= green\t\tblue\tbold\ninfo\t\t\t= blue\t\tdefault\tbold\nstatus\t\t\t= white\t\tdefault\ntitle\t\t\t= white\t\tdefault\tbold\nstate\t\t\t= white\t\tdefault\tbold\ncurrent_time\t\t= white\t\tdefault\tbold\ntime_left\t\t= white\t\tdefault\tbold\ntotal_time\t\t= white\t\tdefault\tbold\ntime_total_frames\t= white\t\tdefault\nsound_parameters\t= white\t\tdefault\tbold\nlegend\t\t\t= white\t\tdefault\ndisabled\t\t= blue\t\tdefault\tbold\nenabled\t\t\t= white\t\tdefault\tbold\nempty_mixer_bar\t\t= white\t\tdefault\nfilled_mixer_bar\t= black\t\tcyan\nempty_time_bar\t\t= white\t\tdefault\nfilled_time_bar\t\t= black\t\tcyan\nentry\t\t\t= white\t\tdefault\nentry_title\t\t= black\t\tcyan\nerror\t\t\t= red\t\tdefault\tbold\nmessage\t\t\t= green\t\tdefault\tbold\nplist_time\t\t= white\t\tdefault\tbold\n"
  },
  {
    "path": "themes/variant_red_theme",
    "content": "# Variant Red Theme by centralkidede\n\nbackground\t\t= default\tdefault\nframe\t\t\t= white\t\tdefault\nwindow_title\t\t= white\t\tdefault\ndirectory\t\t= white\t\tdefault\t\tbold\nselected_directory\t= white\t\tred\t\tbold\nplaylist\t\t= white\t\tdefault\t\tbold\nselected_playlist\t= white\t\tred\t\tbold\nfile\t\t\t= white\t\tdefault\t\nselected_file\t\t= white\t\tred\nmarked_file\t\t= red\t\tdefault\t\tbold\nmarked_selected_file\t= white\t\tred\t\tbold\ninfo\t\t\t= red\t\tdefault\t\tbold\nselected_info\t\t= red\t\tdefault\t\tbold\nstatus\t\t\t= black\t\tdefault\ntitle\t\t\t= black\t\twhite\t\tbold\nstate\t\t\t= black\t\twhite\t\tbold\ncurrent_time\t\t= black\t\twhite\t\tbold\ntime_left\t\t= black\t\twhite\t\tbold\ntotal_time\t\t= black\t\twhite\t\tbold\ntime_total_frames\t= black\t\twhite\nsound_parameters\t= black\t\twhite\t\tbold\nlegend\t\t\t= black\t\twhite\ndisabled\t\t= black\t\tdefault\t\tbold\nenabled\t\t\t= white\t\tdefault\t\tbold\nempty_mixer_bar\t\t= black\t\twhite\nfilled_mixer_bar\t= white\t\tred\nempty_time_bar\t\t= white\t\tdefault\nfilled_time_bar\t\t= black\t\tred\nentry\t\t\t= white\t\tred\nentry_title\t\t= black\t\tred\nerror\t\t\t= red\t\tdefault\t\tbold\nmessage\t\t\t= red\t\tdefault\t\tbold\nplist_time\t\t= black\t\twhite\t\tbold\n"
  },
  {
    "path": "themes/yellow_red_theme",
    "content": "# Yellow/Red theme - mostly Yellow. By Morten Grunnet Buhl\n# Doesn't work on terminals when underline is not supported (like Linux console)\n\nbackground\t\t= red\t\tdefault\nframe\t\t\t= red\t\tdefault\nwindow_title\t\t= yellow\tdefault underline,dim\ndirectory\t\t= yellow\tdefault\nselected_directory\t= yellow\tdefault underline\nplaylist\t\t= yellow\tdefault\nselected_playlist\t= yellow\tdefault\nfile\t\t\t= yellow\tdefault\nselected_file\t\t= yellow\tdefault underline\nmarked_file\t\t= yellow\tdefault\tbold\nmarked_selected_file\t= red\t\tdefault\tbold,underline\ninfo\t\t\t= yellow\tdefault\nstatus\t\t\t= yellow\tdefault\ntitle\t\t\t= yellow\tdefault\nstate\t\t\t= yellow\tdefault\ncurrent_time\t\t= yellow\tdefault\ntime_left\t\t= yellow\tdefault\ntotal_time\t\t= yellow\tdefault\ntime_total_frames\t= yellow\tdefault\nsound_parameters\t= yellow\tdefault\nlegend\t\t\t= yellow\tdefault\ndisabled\t\t= red\t\tdefault dim\nenabled\t\t\t= yellow\tdefault bold\nempty_mixer_bar\t\t= yellow\tred\nfilled_mixer_bar\t= red\t\tyellow\nempty_time_bar\t\t= yellow\tdefault\nfilled_time_bar\t\t= default\tyellow\nentry\t\t\t= yellow\tdefault\nentry_title\t\t= yellow\tdefault bold\nerror\t\t\t= cyan\t\tdefault\nmessage\t\t\t= yellow\tdefault\nplist_time\t\t= yellow\tdefault\n"
  },
  {
    "path": "themes.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2004 - 2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#if defined HAVE_NCURSESW_CURSES_H\n# include <ncursesw/curses.h>\n#elif defined HAVE_NCURSESW_H\n# include <ncursesw.h>\n#elif defined HAVE_NCURSES_CURSES_H\n# include <ncurses/curses.h>\n#elif defined HAVE_NCURSES_H\n# include <ncurses.h>\n#elif defined HAVE_CURSES_H\n# include <curses.h>\n#endif\n\n#include <stdio.h>\n#include <assert.h>\n#include <string.h>\n#include <strings.h>\n#include <errno.h>\n\n#include \"common.h\"\n#include \"interface.h\"\n#include \"themes.h\"\n#include \"files.h\"\n#include \"options.h\"\n\n/* ncurses extension */\n#ifndef COLOR_DEFAULT\n# define COLOR_DEFAULT\t-2\n#endif\n\n/* hidden color? */\n#ifndef COLOR_GREY\n# define COLOR_GREY\t10\n#endif\n\nstatic char current_theme[PATH_MAX];\n\nstatic int colors[CLR_LAST];\n\n/* Counter used for making colors (init_pair()) */\nstatic short pair_count = 1;\n\n/* Initialize a color item of given index (CLR_*) with colors and\n * attributes. Do nothing if the item is already initialized. */\nstatic void make_color (const enum color_index index, const short foreground,\n\t\tconst short background,\tconst attr_t attr)\n{\n\tassert (pair_count < COLOR_PAIRS);\n\tassert (index < CLR_LAST);\n\n\tif (colors[index] == -1) {\n\t\tinit_pair (pair_count, foreground, background);\n\t\tcolors[index] = COLOR_PAIR (pair_count) | attr;\n\n\t\tpair_count++;\n\t}\n}\n\nstatic void set_default_colors ()\n{\n\tmake_color (CLR_BACKGROUND, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_FRAME, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_WIN_TITLE, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_MENU_ITEM_DIR, COLOR_WHITE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_MENU_ITEM_DIR_SELECTED, COLOR_WHITE, COLOR_BLACK,\n\t\t\tA_BOLD);\n\tmake_color (CLR_MENU_ITEM_PLAYLIST, COLOR_WHITE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_MENU_ITEM_PLAYLIST_SELECTED, COLOR_WHITE, COLOR_BLACK,\n\t\t\tA_BOLD);\n\tmake_color (CLR_MENU_ITEM_FILE, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_MENU_ITEM_FILE_SELECTED, COLOR_WHITE,\n\t\t\tCOLOR_BLACK, A_NORMAL);\n\tmake_color (CLR_MENU_ITEM_FILE_MARKED, COLOR_GREEN, COLOR_BLUE,\n\t\t\tA_BOLD);\n\tmake_color (CLR_MENU_ITEM_FILE_MARKED_SELECTED, COLOR_GREEN,\n\t\t\tCOLOR_BLACK, A_BOLD);\n\tmake_color (CLR_MENU_ITEM_INFO, COLOR_BLUE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_MENU_ITEM_INFO_SELECTED, COLOR_BLUE, COLOR_BLACK, A_BOLD);\n\tmake_color (CLR_MENU_ITEM_INFO_MARKED, COLOR_BLUE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_MENU_ITEM_INFO_MARKED_SELECTED, COLOR_BLUE, COLOR_BLACK, A_BOLD);\n\tmake_color (CLR_STATUS, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_TITLE, COLOR_WHITE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_STATE, COLOR_WHITE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_TIME_CURRENT, COLOR_WHITE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_TIME_LEFT, COLOR_WHITE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_TIME_TOTAL_FRAMES, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_TIME_TOTAL, COLOR_WHITE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_SOUND_PARAMS, COLOR_WHITE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_LEGEND, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_INFO_DISABLED, COLOR_BLUE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_INFO_ENABLED, COLOR_WHITE, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_MIXER_BAR_EMPTY, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_MIXER_BAR_FILL, COLOR_BLACK, COLOR_CYAN, A_NORMAL);\n\tmake_color (CLR_TIME_BAR_EMPTY, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_TIME_BAR_FILL, COLOR_BLACK, COLOR_CYAN, A_NORMAL);\n\tmake_color (CLR_ENTRY, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n\tmake_color (CLR_ENTRY_TITLE, COLOR_BLACK, COLOR_CYAN, A_BOLD);\n\tmake_color (CLR_ERROR, COLOR_RED, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_MESSAGE, COLOR_GREEN, COLOR_BLUE, A_BOLD);\n\tmake_color (CLR_PLIST_TIME, COLOR_WHITE, COLOR_BLUE, A_NORMAL);\n}\n\n/* Set default colors for black and white terminal. */\nstatic void set_bw_colors ()\n{\n\tcolors[CLR_BACKGROUND] = A_NORMAL;\n\tcolors[CLR_FRAME] = A_NORMAL;\n\tcolors[CLR_WIN_TITLE] = A_NORMAL;\n\tcolors[CLR_MENU_ITEM_DIR] = A_NORMAL;\n\tcolors[CLR_MENU_ITEM_DIR_SELECTED] = A_REVERSE;\n\tcolors[CLR_MENU_ITEM_PLAYLIST] = A_NORMAL;\n\tcolors[CLR_MENU_ITEM_PLAYLIST_SELECTED] = A_REVERSE;\n\tcolors[CLR_MENU_ITEM_FILE] = A_NORMAL;\n\tcolors[CLR_MENU_ITEM_FILE_SELECTED] = A_REVERSE;\n\tcolors[CLR_MENU_ITEM_FILE_MARKED] = A_BOLD;\n\tcolors[CLR_MENU_ITEM_FILE_MARKED_SELECTED] = A_BOLD | A_REVERSE;\n\tcolors[CLR_MENU_ITEM_INFO] = A_NORMAL;\n\tcolors[CLR_MENU_ITEM_INFO_SELECTED] = A_REVERSE;\n\tcolors[CLR_MENU_ITEM_INFO_MARKED] = A_BOLD;\n\tcolors[CLR_MENU_ITEM_INFO_MARKED_SELECTED] = A_BOLD | A_REVERSE;\n\tcolors[CLR_STATUS] = A_NORMAL;\n\tcolors[CLR_TITLE] = A_BOLD;\n\tcolors[CLR_STATE] = A_BOLD;\n\tcolors[CLR_TIME_CURRENT] = A_BOLD;\n\tcolors[CLR_TIME_LEFT] = A_BOLD;\n\tcolors[CLR_TIME_TOTAL_FRAMES] = A_NORMAL;\n\tcolors[CLR_TIME_TOTAL] = A_BOLD;\n\tcolors[CLR_SOUND_PARAMS] = A_BOLD;\n\tcolors[CLR_LEGEND] = A_NORMAL;\n\tcolors[CLR_INFO_DISABLED] = A_BOLD;\n\tcolors[CLR_INFO_ENABLED] = A_BOLD;\n\tcolors[CLR_MIXER_BAR_EMPTY] = A_NORMAL;\n\tcolors[CLR_MIXER_BAR_FILL] = A_REVERSE;\n\tcolors[CLR_TIME_BAR_EMPTY] = A_NORMAL;\n\tcolors[CLR_TIME_BAR_FILL] = A_REVERSE;\n\tcolors[CLR_ENTRY] = A_NORMAL;\n\tcolors[CLR_ENTRY_TITLE] = A_BOLD;\n\tcolors[CLR_ERROR] = A_BOLD;\n\tcolors[CLR_MESSAGE] = A_BOLD;\n\tcolors[CLR_PLIST_TIME] = A_NORMAL;\n}\n\nstatic void theme_parse_error (const int line, const char *msg)\n{\n\tinterface_fatal (\"Parse error in theme file line %d: %s\", line, msg);\n}\n\n/* Find the index of a color element by name. Return CLR_WRONG if not found. */\nstatic enum color_index find_color_element_name (const char *name)\n{\n\tsize_t ix;\n\tstatic struct\n\t{\n\t\tchar *name;\n\t\tenum color_index idx;\n\t} color_tab[] = {\n\t\t{ \"background\",\t\tCLR_BACKGROUND },\n\t\t{ \"frame\",\t\tCLR_FRAME },\n\t\t{ \"window_title\",\tCLR_WIN_TITLE },\n\t\t{ \"directory\",\t\tCLR_MENU_ITEM_DIR },\n\t\t{ \"selected_directory\", CLR_MENU_ITEM_DIR_SELECTED },\n\t\t{ \"playlist\",\t\tCLR_MENU_ITEM_PLAYLIST },\n\t\t{ \"selected_playlist\",\tCLR_MENU_ITEM_PLAYLIST_SELECTED },\n\t\t{ \"file\",\t\tCLR_MENU_ITEM_FILE },\n\t\t{ \"selected_file\",\tCLR_MENU_ITEM_FILE_SELECTED },\n\t\t{ \"marked_file\",\tCLR_MENU_ITEM_FILE_MARKED },\n\t\t{ \"marked_selected_file\", CLR_MENU_ITEM_FILE_MARKED_SELECTED },\n\t\t{ \"info\",\t\tCLR_MENU_ITEM_INFO },\n\t\t{ \"selected_info\",\tCLR_MENU_ITEM_INFO_SELECTED },\n\t\t{ \"marked_info\",\tCLR_MENU_ITEM_INFO_MARKED },\n\t\t{ \"marked_selected_info\", CLR_MENU_ITEM_INFO_MARKED_SELECTED },\n\t\t{ \"status\",\t\tCLR_STATUS },\n\t\t{ \"title\",\t\tCLR_TITLE },\n\t\t{ \"state\",\t\tCLR_STATE },\n\t\t{ \"current_time\",\tCLR_TIME_CURRENT },\n\t\t{ \"time_left\",\t\tCLR_TIME_LEFT },\n\t\t{ \"total_time\",\t\tCLR_TIME_TOTAL },\n\t\t{ \"time_total_frames\",\tCLR_TIME_TOTAL_FRAMES },\n\t\t{ \"sound_parameters\",\tCLR_SOUND_PARAMS },\n\t\t{ \"legend\",\t\tCLR_LEGEND },\n\t\t{ \"disabled\",\t\tCLR_INFO_DISABLED },\n\t\t{ \"enabled\",\t\tCLR_INFO_ENABLED },\n\t\t{ \"empty_mixer_bar\",\tCLR_MIXER_BAR_EMPTY },\n\t\t{ \"filled_mixer_bar\",\tCLR_MIXER_BAR_FILL },\n\t\t{ \"empty_time_bar\",\tCLR_TIME_BAR_EMPTY },\n\t\t{ \"filled_time_bar\",\tCLR_TIME_BAR_FILL },\n\t\t{ \"entry\",\t\tCLR_ENTRY },\n\t\t{ \"entry_title\",\tCLR_ENTRY_TITLE },\n\t\t{ \"error\",\t\tCLR_ERROR },\n\t\t{ \"message\",\t\tCLR_MESSAGE },\n\t\t{ \"plist_time\",\t\tCLR_PLIST_TIME }\n\t};\n\n\tassert (name != NULL);\n\n\tfor (ix = 0; ix < ARRAY_SIZE(color_tab); ix += 1) {\n\t\tif (!strcasecmp(color_tab[ix].name, name))\n\t\t\treturn color_tab[ix].idx;\n\t}\n\n\treturn CLR_WRONG;\n}\n\n/* Find the curses color by name. Return -1 if the color is unknown. */\nstatic short find_color_name (const char *name)\n{\n\tsize_t ix;\n\tstatic struct\n\t{\n\t\tchar *name;\n\t\tshort color;\n\t} color_tab[] = {\n\t\t{ \"black\",\tCOLOR_BLACK },\n\t\t{ \"red\",\tCOLOR_RED },\n\t\t{ \"green\",\tCOLOR_GREEN },\n\t\t{ \"yellow\",\tCOLOR_YELLOW },\n\t\t{ \"blue\",\tCOLOR_BLUE },\n\t\t{ \"magenta\",\tCOLOR_MAGENTA },\n\t\t{ \"cyan\",\tCOLOR_CYAN },\n\t\t{ \"white\",\tCOLOR_WHITE },\n\t\t{ \"default\",\tCOLOR_DEFAULT },\n\t\t{ \"grey\",\tCOLOR_GREY }\n\t};\n\n\tfor (ix = 0; ix < ARRAY_SIZE(color_tab); ix += 1) {\n\t\tif (!strcasecmp(color_tab[ix].name, name))\n\t\t\treturn color_tab[ix].color;\n\t}\n\n\treturn -1;\n}\n\nstatic int new_colordef (const int line_num, const char *name, const short red,\n\t\tconst short green, const short blue, const int errors_are_fatal)\n{\n\tshort color = find_color_name (name);\n\n\tif (color == -1) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num, \"bad color name\");\n\t\treturn 0;\n\t}\n\tif (can_change_color())\n\t\tinit_color (color, red, green, blue);\n\n\treturn 1;\n}\n\n/* Find path to the theme for the given name. Returned memory is static. */\nstatic char *find_theme_file (const char *name)\n{\n\tint rc;\n\tstatic char path[PATH_MAX];\n\n\tpath[sizeof(path)-1] = 0;\n\tif (name[0] == '/') {\n\n\t\t/* Absolute path */\n\t\tstrncpy (path, name, sizeof(path));\n\t\tif (path[sizeof(path)-1])\n\t\t\tinterface_fatal (\"Theme path too long!\");\n\t\treturn path;\n\t}\n\n\t/* Try the user directory */\n\trc = snprintf(path, sizeof(path), \"%s/%s\",\n\t              create_file_name(\"themes\"), name);\n\tif (rc >= ssizeof(path))\n\t\tinterface_fatal (\"Theme path too long!\");\n\tif (file_exists(path))\n\t\treturn path;\n\n\t/* Try the system directory */\n\trc = snprintf(path, sizeof(path), \"%s/%s\", SYSTEM_THEMES_DIR, name);\n\tif (rc >= ssizeof(path))\n\t\tinterface_fatal (\"Theme path too long!\");\n\tif (file_exists(path))\n\t\treturn path;\n\n\t/* File related to the current directory? */\n\tstrncpy (path, name, sizeof(path));\n\tif (path[sizeof(path)-1])\n\t\tinterface_fatal (\"Theme path too long!\");\n\treturn path;\n}\n\n/* Parse a theme element line. strtok() should be already invoked and consumed\n * the element name.\n * On error: if errors_are_fatal is true,\n * theme_parse_error() is invoked, otherwise 0 is returned. */\nstatic int parse_theme_element (const int line_num, const char *name,\n\t\tconst int errors_are_fatal)\n{\n\tchar *tmp;\n\tchar *foreground, *background, *attributes;\n\tattr_t curses_attr = 0;\n\tenum color_index element;\n\tshort clr_fore, clr_back;\n\n\tif (!(tmp = strtok(NULL, \" \\t\")) || strcmp(tmp, \"=\")) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num, \"expected '='\");\n\t\treturn 0;\n\t}\n\tif (!(foreground = strtok(NULL, \" \\t\"))) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num,\n\t\t\t\t\t\"foreground color not specified\");\n\t\treturn 0;\n\t}\n\tif (!(background = strtok(NULL, \" \\t\"))) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num,\n\t\t\t\t\t\"background color not specified\");\n\t\treturn 0;\n\t}\n\tif ((attributes = strtok(NULL, \" \\t\"))) {\n\t\tchar *attr;\n\n\t\tif ((tmp = strtok(NULL, \" \\t\"))) {\n\t\t\tif (errors_are_fatal)\n\t\t\t\ttheme_parse_error (line_num,\n\t\t\t\t\t\"unexpected chars at the end of line\");\n\t\t\treturn 0;\n\t\t}\n\n\t\tattr = strtok (attributes, \",\");\n\n\t\tdo {\n\t\t\tif (!strcasecmp(attr, \"normal\"))\n\t\t\t\tcurses_attr |= A_NORMAL;\n\t\t\telse if (!strcasecmp(attr, \"standout\"))\n\t\t\t\tcurses_attr |= A_STANDOUT;\n\t\t\telse if (!strcasecmp(attr, \"underline\"))\n\t\t\t\tcurses_attr |= A_UNDERLINE;\n\t\t\telse if (!strcasecmp(attr, \"reverse\"))\n\t\t\t\tcurses_attr |= A_REVERSE;\n\t\t\telse if (!strcasecmp(attr, \"blink\"))\n\t\t\t\tcurses_attr |= A_BLINK;\n\t\t\telse if (!strcasecmp(attr, \"dim\"))\n\t\t\t\tcurses_attr |= A_DIM;\n\t\t\telse if (!strcasecmp(attr, \"bold\"))\n\t\t\t\tcurses_attr |= A_BOLD;\n\t\t\telse if (!strcasecmp(attr, \"protect\"))\n\t\t\t\tcurses_attr |= A_PROTECT;\n\t\t\telse {\n\t\t\t\tif (errors_are_fatal)\n\t\t\t\t\ttheme_parse_error (line_num,\n\t\t\t\t\t\t\"unknown attribute\");\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t} while ((attr = strtok(NULL, \",\")));\n\t}\n\n\tif ((element = find_color_element_name(name)) == CLR_WRONG) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num, \"unknown element\");\n\t\treturn 0;\n\t}\n\tif ((clr_fore = find_color_name(foreground)) == -1) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num,\n\t\t\t\t\t\"bad foreground color name\");\n\t\treturn 0;\n\t}\n\tif ((clr_back = find_color_name(background)) == -1) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num,\n\t\t\t\t\t\"bad background color name\");\n\t\treturn 0;\n\t}\n\n\tmake_color (element, clr_fore, clr_back, curses_attr);\n\n\treturn 1;\n}\n\n/* Parse a color value. strtok() should be already invoked and should \"point\"\n * to the number. If errors_are_fatal, use theme_parse_error() on error,\n * otherwise return -1. */\nstatic short parse_rgb_color_value (const int line_num,\n\t\tconst int errors_are_fatal)\n{\n\tchar *tmp;\n\tchar *end;\n\tlong color;\n\n\tif (!(tmp = strtok(NULL, \" \\t\"))) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num, \"3 color values expected\");\n\t\treturn -1;\n\t}\n\tcolor = strtol (tmp, &end, 10);\n\tif (*end) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num,\n\t\t\t\t\t\"color value is not a valid number\");\n\t\treturn -1;\n\t}\n\tif (!RANGE(0, color, 1000)) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num,\n\t\t\t\t\t\"color value should be in range 0-1000\");\n\t\treturn -1;\n\t}\n\n\treturn color;\n}\n\n/* Parse a theme color definition. strtok() should be already invoked and\n * consumed 'colordef'. On error: if errors_are_fatal is true,\n * theme_parse_error() is invoked, otherwise 0 is returned. */\nstatic int parse_theme_colordef (const int line_num,\n\t\tconst int errors_are_fatal)\n{\n\tchar *name;\n\tchar *tmp;\n\tshort red, green, blue;\n\n\tif (!(name = strtok(NULL, \" \\t\"))) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num, \"expected color name\");\n\t\treturn 0;\n\t}\n\tif (!(tmp = strtok(NULL, \" \\t\")) || strcmp(tmp, \"=\")) {\n\t\tif (errors_are_fatal)\n\t\t\ttheme_parse_error (line_num, \"expected '='\");\n\t\treturn 0;\n\t}\n\n\tred = parse_rgb_color_value (line_num, errors_are_fatal);\n\tgreen = parse_rgb_color_value (line_num, errors_are_fatal);\n\tblue = parse_rgb_color_value (line_num, errors_are_fatal);\n\tif (red == -1 || green == -1 || blue == -1)\n\t\treturn 0;\n\n\tif (!new_colordef(line_num, name, red, green, blue, errors_are_fatal))\n\t\treturn 0;\n\n\treturn 1;\n}\n\n/* The lines should be in format:\n *\n * ELEMENT = FOREGROUND BACKGROUND [ATTRIBUTE[,ATTRIBUTE,..]]\n * or:\n * colordef COLORNAME = RED GREEN BLUE\n *\n * Blank lines and beginning with # are ignored, see example_theme.\n *\n * On error: if errors_are_fatal is true, interface_fatal() is invoked,\n * otherwise 0 is returned. */\nstatic int parse_theme_line (const int line_num, char *line,\n\t\tconst int errors_are_fatal)\n{\n\tchar *name;\n\n\tif (line[0] == '#' || !(name = strtok(line, \" \\t\"))) {\n\n\t\t/* empty line or a comment */\n\t\treturn 1;\n\t}\n\n\tif (!strcasecmp(name, \"colordef\"))\n\t\treturn parse_theme_colordef (line_num, errors_are_fatal);\n\treturn parse_theme_element (line_num, name, errors_are_fatal);\n}\n\n/* Load a color theme. If errors_are_fatal is true, errors cause\n * interface_fatal(), otherwise 0 is returned on error. */\nstatic int load_color_theme (const char *name, const int errors_are_fatal)\n{\n\tFILE *file;\n\tchar *line;\n\tint result = 1;\n\tint line_num = 0;\n\tchar *theme_file = find_theme_file (name);\n\n\tif (!(file = fopen(theme_file, \"r\"))) {\n\t\tif (errors_are_fatal)\n\t\t\tinterface_fatal (\"Can't open theme file: %s\", xstrerror (errno));\n\t\treturn 0;\n\t}\n\n\twhile (result && (line = read_line (file))) {\n\t\tline_num++;\n\t\tresult = parse_theme_line (line_num, line, errors_are_fatal);\n\t\tfree (line);\n\t}\n\n\tfclose (file);\n\n\treturn result;\n}\n\nstatic void reset_colors_table ()\n{\n\tint i;\n\n\tpair_count = 1;\n\tfor (i = 0; i < CLR_LAST; i++)\n\t\tcolors[i] = -1;\n}\n\nvoid theme_init (bool has_xterm)\n{\n\treset_colors_table ();\n\n\tif (has_colors ()) {\n\t\tchar *file;\n\n\t\tif ((file = options_get_str (\"ForceTheme\"))) {\n\t\t\tload_color_theme (file, 1);\n\t\t\tstrncpy (current_theme, find_theme_file (file), PATH_MAX);\n\t\t}\n\t\telse if (has_xterm && (file = options_get_str (\"XTermTheme\"))) {\n\t\t\tload_color_theme (file, 1);\n\t\t\tstrncpy (current_theme, find_theme_file (file), PATH_MAX);\n\t\t}\n\t\telse if ((file = options_get_str (\"Theme\"))) {\n\t\t\tload_color_theme (file, 1);\n\t\t\tstrncpy (current_theme, find_theme_file (file), PATH_MAX);\n\t\t}\n\t\telse\n\t\t\tsnprintf (current_theme, PATH_MAX, \"%s/example_theme\",\n\t\t\t                                   SYSTEM_THEMES_DIR);\n\n\t\tset_default_colors ();\n\t}\n\telse\n\t\tset_bw_colors ();\n}\n\nint get_color (const enum color_index index)\n{\n\treturn colors[index];\n}\n\nvoid themes_switch_theme (const char *file)\n{\n\tif (has_colors()) {\n\t\treset_colors_table ();\n\t\tif (!load_color_theme(file, 0)) {\n\t\t\tinterface_error (\"Error loading theme!\");\n\t\t\treset_colors_table ();\n\t\t}\n\t\telse\n\t\t\tstrncpy (current_theme, file, PATH_MAX);\n\n\t\tset_default_colors ();\n\t}\n}\n\nconst char *get_current_theme ()\n{\n\treturn current_theme;\n}\n"
  },
  {
    "path": "themes.h",
    "content": "#ifndef THEMES_H\n#define THEMES_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nenum color_index\n{\n\tCLR_BACKGROUND,\n\tCLR_FRAME,\n\tCLR_WIN_TITLE,\n\tCLR_MENU_ITEM_DIR,\n\tCLR_MENU_ITEM_DIR_SELECTED,\n\tCLR_MENU_ITEM_PLAYLIST,\n\tCLR_MENU_ITEM_PLAYLIST_SELECTED,\n\tCLR_MENU_ITEM_FILE,\n\tCLR_MENU_ITEM_FILE_SELECTED,\n\tCLR_MENU_ITEM_FILE_MARKED,\n\tCLR_MENU_ITEM_FILE_MARKED_SELECTED,\n\tCLR_MENU_ITEM_INFO,\n\tCLR_MENU_ITEM_INFO_SELECTED,\n\tCLR_MENU_ITEM_INFO_MARKED,\n\tCLR_MENU_ITEM_INFO_MARKED_SELECTED,\n\tCLR_STATUS,\n\tCLR_TITLE,\n\tCLR_STATE,\n\tCLR_TIME_CURRENT,\n\tCLR_TIME_LEFT,\n\tCLR_TIME_TOTAL,\n\tCLR_TIME_TOTAL_FRAMES,\n\tCLR_SOUND_PARAMS,\n\tCLR_LEGEND,\n\tCLR_INFO_DISABLED,\n\tCLR_INFO_ENABLED,\n\tCLR_MIXER_BAR_EMPTY,\n\tCLR_MIXER_BAR_FILL,\n\tCLR_TIME_BAR_EMPTY,\n\tCLR_TIME_BAR_FILL,\n\tCLR_ENTRY,\n\tCLR_ENTRY_TITLE,\n\tCLR_ERROR,\n\tCLR_MESSAGE,\n\tCLR_PLIST_TIME,\n\tCLR_LAST, /* Fake element to get number of colors */\n\tCLR_WRONG\n};\n\nvoid theme_init (bool has_xterm);\nint get_color (const enum color_index);\nvoid themes_switch_theme (const char *file);\nconst char *get_current_theme ();\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "tools/README",
    "content": "                            MOC TOOLS DIRECTORY\n\n\n1. Introduction\n\nThis directory contains debugging and testing tools which are intended for\ndevelopers and maintainers.\n\n\n2. The Tools\n\n2.1 MD5 Check\n\nThe 'md5check.sh' script is intended to verify that the samples which\narrive at MOC's player code are the same as those produced by the\nlibrary the decoder is using.  It does this by having MOC compute the\nMD5 sum of the audio file as it is playing it and writing the result to\nthe server's log file.  The log file is then read by the 'md5check.sh'\ntool and the MD5 sum checked against one computed from the audio file\nusing the library's native decoding program.\n\nIt is important to use the program associated with the decoder library\nif one exists because other programs may not produce exactly the same\nsamples, particularly for lossy formats.  It is also possible that bugs\nin the library mean the samples produced are not correct, but the tool\nis intended to test the passage of the samples through the driver and\nnot the fidelity of the library used.\n\n2.2 Test File Generation\n\nThe 'maketests.sh' script generates test files in the directory in which\nit is run.  The test files are 10 seconds of sinewave audio generated by\nSoX.  Each file's name is in a format which represents its characteristics\nand these can be checked against those MOC uses with the '-e' option of\n'md5scheck.sh'.\n\nAll filenames start with 'sinewave-' and the script will refuse to run if\nany files starting with that name already exist.  It is wise to run this\nscript in an empty directory.  It generates a lot of files.\n"
  },
  {
    "path": "tools/maketests.sh",
    "content": "#!/bin/bash\n\n#\n# MOC - music on console\n# Copyright (C) 2004-2005 Damian Pietras <daper@daper.net>\n#\n# maketests.sh Copyright (C) 2012 John Fitzgerald\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation; either version 2 of the License, or\n# (at your option) any later version.\n#\n\n#\n# TODO: - Add other supported formats.\n#\n\nAUDIO=false\nVIDEO=false\nFFMPEG=\"$(which avconv 2>/dev/null || which ffmpeg 2>/dev/null)\"\nSOX=\"$(which sox 2>/dev/null)\"\nSYNTH=\"synth 10 sine 440 vol 0.5\"\n\n# Clean error termination.\nfunction die {\n  echo $@ > /dev/stderr\n  exit 1\n}\n\n[[ -x \"$SOX\" ]] || die This script requires the SoX package.\n\n# Provide usage information.\nfunction usage () {\n  echo \"Usage: ${0##*/} [-a] [--audio] [-h] [--video] [FORMAT ...]\"\n}\n\n# Provide help information.\nfunction help () {\n  echo\n  echo \"MOC test file generation tool\"\n  echo\n  usage\n  echo\n  echo \"  -a|--all        Generate all formats\"\n  echo \"     --audio      Generate all audio formats\"\n  echo \"  -h|--help       This help information\"\n  echo \"     --video      Generate all video formats\"\n  echo\n  echo \"Supported audio formats: flac mp3 vorbis wav\"\n  echo \"Supported video formats: vob\"\n  echo\n}\n\n# Generate FLAC audio test files.\nfunction flac {\n  echo \"Generating FLAC audio test files\"\n  for r in 8000 16000 24000 32000 48000 96000 192000 \\\n           11025 22050 44100 88200 176400\n  do\n    for b in 16 32\n    do\n      $SOX -b$b -c1 -r$r -e signed -n -L sinewave-s${b}le-1-$r.flac $SYNTH\n      $SOX -b$b -c2 -r$r -e signed -n -L sinewave-s${b}le-2-$r.flac $SYNTH\n    done\n  done\n}\n\n# Generate MP3 audio test files.\nfunction mp3 {\n  echo \"Generating MP3 audio test files\"\n  for r in 8000 16000 24000 32000 48000 11025 22050 44100\n  do\n    for c in 1 2\n    do\n      $SOX -b8 -c$c -r$r -n sinewave-u8-$c-$r.mp3 $SYNTH\n      for b in 16 24 32\n      do\n        $SOX -b$b -c$c -r$r -n -L sinewave-s${b}le-$c-$r.mp3 $SYNTH\n        $SOX -b$b -c$c -r$r -n -B sinewave-s${b}be-$c-$r.mp3 $SYNTH\n      done\n    done\n  done\n}\n\n# Generate VOB video test files.\nfunction vob {\n  [[ -x \"$FFMPEG\" ]] || return\n  echo \"Generating VOB video test files\"\n  for r in 16000 22050 24000 32000 44100 48000\n  do\n    for c in 1 2\n    do\n      $FFMPEG -f rawvideo -pix_fmt yuv420p -s 320x240 -r 30000/1001 \\\n              -i /dev/zero \\\n              -f s16le -c pcm_s16le -ac 2 -ar 48000 \\\n              -i <($SOX -q -b16 -c2 -r 48000 -e signed -n -L -t s16 - $SYNTH) \\\n              -vcodec mpeg2video -acodec mp2 -shortest -ac $c -ar $r \\\n              -y sinewave-s16le-$c-$r.vob > /dev/null  2>&1\n    done\n  done\n}\n\n# Generate Ogg/Vorbis audio test files.\nfunction vorbis {\n  echo \"Generating Ogg/Vorbis audio test files\"\n  for r in 8000 16000 24000 32000 48000 96000 192000 \\\n           11025 22050 44100 88200 176400\n  do\n    $SOX -b16 -c1 -r$r -e signed -n -L sinewave-s16le-1-$r.ogg $SYNTH\n    $SOX -b16 -c2 -r$r -e signed -n -L sinewave-s16le-2-$r.ogg $SYNTH\n  done\n}\n\n# Generate WAV audio test files.\nfunction wav {\n  echo \"Generating WAV audio test files\"\n  for r in 8000 16000 24000 32000 48000 96000 192000 \\\n           11025 22050 44100 88200 176400\n  do\n    for c in 1 2\n    do\n      $SOX -b8 -c$c -r$r -n sinewave-u8-$c-$r.wav $SYNTH\n      $SOX -b16 -c$c -r$r -n -B sinewave-s16be-$c-$r.wav $SYNTH\n      for b in 16 24 32\n      do\n        $SOX -b$b -c$c -r$r -n -L sinewave-s${b}le-$c-$r.wav $SYNTH\n      done\n    done\n  done\n}\n\n# Directory safety check.\nls sinewave-* > /dev/null 2>&1 && {\n  echo\n  echo \"This script generates many filenames starting with 'sinewave-' in the\"\n  echo \"current directory which already contains similarly named files.\"\n  echo \"Running it in this directory is a really, really bad idea.  (In fact,\"\n  echo \"it's probably not wise to run this script in a non-empty directory at\"\n  echo \"all.)  So we're aborting in the interests of safety.\"\n  echo\n  exit 1\n} > /dev/stderr\n\n# Process command line options.\nfor OPTS\ndo\n  case $1 in\n   -a|--all) AUDIO=true\n             VIDEO=true\n             ;;\n    --audio) AUDIO=true\n             ;;\n  -h|--help) help\n             exit 0\n             ;;\n    --video) VIDEO=true\n             ;;\n         -*) echo Unrecognised option: $1\n             usage > /dev/stderr\n             exit 1\n             ;;\n          *) break\n             ;;\n  esac\n  shift\ndone\n\n# Generate all audio formats.\n$AUDIO && {\n  flac\n  mp3\n  vorbis\n  wav\n}\n\n# Generate all video formats.\n$VIDEO && {\n  vob\n}\n\n# Generate specified formats.\nfor ARGS\ndo\n  case $1 in\n  flac|mp3|vorbis|wav)\n      $1\n      ;;\n  vob)\n      $1\n      ;;\n  *)  echo \"*** Unsupported format: $1\"\n      echo\n      ;;\n  esac\n  shift\ndone\n\nexit 0\n"
  },
  {
    "path": "tools/md5check.sh",
    "content": "#!/bin/bash\n\n#\n# MOC - music on console\n# Copyright (C) 2004-2005 Damian Pietras <daper@daper.net>\n#\n# md5check.sh Copyright (C) 2012 John Fitzgerald\n#\n# This program is free software; you can redistribute it and/or modify\n# it under the terms of the GNU General Public License as published by\n# the Free Software Foundation; either version 2 of the License, or\n# (at your option) any later version.\n#\n\n#\n# TODO: - Make format, rate and channels explicit where possible.\n#       - Add remaining decoder check functions.\n#       - Fix decoders with genuine MD5 mismatches.\n#       - Fix decoders with genuine format mismatches.\n#       - Handle format mismatches which aren't genuine.\n#       - Possibly rewrite in Perl for speed.\n#\n\ndeclare -A UNKNOWN\ndeclare -A UNSUPPORTED\n\nEXTRA=false\nIGNORE=false\nRC=0\nSILENT=false\nTREMOR=false\nVERBOSE=false\n\n# Clean error termination.\nfunction die {\n  echo '***' $@ > /dev/stderr\n  exit 1\n}\n\n# Provide usage information.\nfunction usage () {\n  echo \"Usage: ${0##*/} [-e] [-s|-v] [LOGFILE]\"\n  echo \"       ${0##*/} -h\"\n}\n\n# Provide help information.\nfunction help () {\n  echo\n  echo \"MOC MD5 sum checking tool\"\n  echo\n  usage\n  echo\n  echo \"  -e|--extra      Perform extra checks\"\n  echo \"  -h|--help       This help information\"\n  echo \"  -i|--ignore     Ignore known problems\"\n  echo \"  -s|--silent     Output no results\"\n  echo \"  -v|--verbose    Output all results\"\n  echo\n  echo \"  LOGFILE         MOC server log file name, or '-' for stdin\"\n  echo \"                  (default: 'mocp_server_log' in the current directory)\"\n  echo\n  echo \"Exit codes:       0 - No errors or mismatches\"\n  echo \"                  1 - Error occurred\"\n  echo \"                  2 - Mismatch found\"\n  echo\n}\n\n# Check the FAAD decoder's samples.\nFAAD=$(which faad 2>/dev/null)\nfunction aac () {\n  local ENDIAN OPTS\n\n  [[ -x \"$FAAD\" ]] || die faad2 not installed\n\n  [[ \"${FMT:0:1}\" = \"f\" ]] && ENDIAN=le\n  OPTS=\"-w -q -f2\"\n\n  [[ \"${FMT:1:2}\" = \"16\" ]] && OPTS=\"$OPTS -b1\"\n  [[ \"${FMT:1:2}\" = \"24\" ]] && OPTS=\"$OPTS -b2\"\n  [[ \"${FMT:1:2}\" = \"32\" ]] && OPTS=\"$OPTS -b3\"\n\n  SUM2=$($FAAD $OPTS \"$FILE\" | md5sum)\n  LEN2=$($FAAD $OPTS \"$FILE\" | wc -c)\n}\n\n# Check the FFmpeg decoder's samples.\nFFMPEG=$(which avconv 2>/dev/null || which ffmpeg 2>/dev/null)\nfunction ffmpeg () {\n  local ENDIAN OPTS\n\n  [[ -x \"$FFMPEG\" ]] || die ffmpeg/avconv not installed\n\n  [[ \"${FMT:0:1}\" = \"f\" ]] && ENDIAN=le\n  OPTS=\"-ac $CHANS -ar $RATE -f $FMT$ENDIAN\"\n\n  SUM2=\"$($FFMPEG -i \"$FILE\" $OPTS - </dev/null 2>/dev/null | md5sum)\"\n  LEN2=$($FFMPEG -i \"$FILE\" $OPTS - </dev/null 2>/dev/null | wc -c)\n\n  [[ \"$($FFMPEG -i \"$FILE\" </dev/null 2>&1)\" =~ Audio:\\ .*\\ (mono|stereo) ]] || \\\n    IGNORE_SUM=$IGNORE\n}\n\n# Check the FLAC decoder's samples.\nSOX=$(which sox 2>/dev/null)\nfunction flac () {\n  local OPTS\n\n  [[ -x \"$SOX\" ]] || die \"SoX (for flac) not installed\"\n\n  [[ \"${FMT:0:1}\" = \"s\" ]] && OPTS=\"-e signed\" || OPTS=\"-e unsigned\"\n  [[ \"${FMT:1:1}\" = \"8\" ]] && OPTS=\"$OPTS -b8 -L\"\n  [[ \"${FMT:1:2}\" = \"16\" ]] && OPTS=\"$OPTS -b16\"\n  [[ \"${FMT:1:2}\" = \"24\" ]] && OPTS=\"$OPTS -b24\"\n  [[ \"${FMT:1:2}\" = \"32\" ]] && OPTS=\"$OPTS -b32\"\n  [[ \"$FMT\" =~ \"le\" ]] && OPTS=\"$OPTS -L\"\n  [[ \"$FMT\" =~ \"be\" ]] && OPTS=\"$OPTS -B\"\n  OPTS=\"$OPTS -r$RATE -c$CHANS\"\n\n  SUM2=$($SOX \"$FILE\" $OPTS -t raw - | md5sum)\n  LEN2=$($SOX \"$FILE\" $OPTS -t raw - | wc -c)\n}\n\n# Check the Ogg/Vorbis decoder's samples.\nOGGDEC=$(which oggdec 2>/dev/null)\nfunction vorbis () {\n  [[ -x \"$OGGDEC\" ]] || die oggdec not installed\n  SUM2=\"$($OGGDEC -RQ -o - \"$FILE\" | md5sum)\"\n  LEN2=$($OGGDEC -RQ -o - \"$FILE\" | wc -c)\n}\n\n# Check the LibSndfile decoder's samples.\nSOX=$(which sox 2>/dev/null)\nfunction sndfile () {\n  # LibSndfile doesn't have a decoder, use SoX.\n  [[ -x \"$SOX\" ]] || die \"sox (for sndfile) not installed\"\n  SUM2=\"$($SOX \"$FILE\" -t f32 - | md5sum)\"\n  LEN2=$($SOX \"$FILE\" -t f32 - | wc -c)\n  [[ \"$NAME\" == *-s32le-* ]] && IGNORE_SUM=$IGNORE\n}\n\n# Check the MP3 decoder's samples.\nSOX=$(which sox 2>/dev/null)\nfunction mp3 () {\n  # Lame's decoder only does 16-bit, use SoX.\n  [[ -x \"$SOX\" ]] || die \"sox (for mp3) not installed\"\n  SUM2=\"$($SOX \"$FILE\" -t s32 - | md5sum)\"\n  LEN2=$($SOX \"$FILE\" -t s32 - | wc -c)\n  IGNORE_SUM=$IGNORE\n  IGNORE_LEN=$IGNORE\n}\n\n# Check the Speex decoder's samples.\nSPEEX=$(which speexdec 2>/dev/null)\nfunction speex () {\n  [[ -x \"$SPEEX\" ]] || die speexdec not installed\n  SUM2=\"$($SPEEX \"$FILE\" - 2>/dev/null | md5sum)\"\n  LEN2=$($SPEEX \"$FILE\" - 2>/dev/null | wc -c)\n  IGNORE_SUM=$IGNORE\n  IGNORE_LEN=$IGNORE\n}\n\n# Process command line options.\nfor OPTS\ndo\n  case $1 in\n    -e|--extra) EXTRA=true\n                ;;\n   -i|--ignore) IGNORE=true\n                ;;\n  -v|--verbose) VERBOSE=true\n                SILENT=false\n                ;;\n   -s|--silent) SILENT=true\n                VERBOSE=false\n                ;;\n     -h|--help) help\n                exit 0\n                ;;\n          --|-) break\n                ;;\n            -*) echo Unrecognised option: $1\n                usage > /dev/stderr\n                exit 1\n                ;;\n             *) break\n                ;;\n  esac\n  shift\ndone\n\n# Allow for log file parameter.\nLOG=\"${1:-mocp_server_log}\"\n[[ \"$LOG\" = \"-\" ]] && LOG=/dev/stdin\n\n# Output formatting.\n$SILENT || echo\n\n# Process server log file.\nwhile read\ndo\n\n  # Reject log file if circular logging has been used.\n  [[ \"$REPLY\" =~ \"Circular Log Starts\" ]] && \\\n      die MD5 sums cannot be checked when circular logging was used\n\n  # Extract MOC revision header.\n  [[ \"$REPLY\" =~ \"This is Music On Console\" ]] && \\\n      REVN=\"$(expr \"$REPLY\" : '.*\\(Music .*\\)')\"\n\n  # Check for Tremor decoder.\n  [[ \"$REPLY\" =~ Loaded\\ [0-9]+\\ decoders:.*vorbis\\(tremor\\) ]] && \\\n     TREMOR=true\n\n  # Extract file's full pathname.\n  [[ \"$REPLY\" =~ \"Playing item\" ]] && \\\n     FILE=\"$(expr \"$REPLY\" : '.* item [0-9]*: \\(.*\\)')\"\n\n  # Ignore all non-MD5 lines.\n  [[ \"$REPLY\" =~ \"MD5\" ]] || continue\n\n  # Extract fields of interest.\n  NAME=\"$(expr \"$REPLY\" : '.*MD5(\\([^)]*\\))')\"\n  set -- $(expr \"$REPLY\" : '.*MD5([^)]*) = \\(.*\\)')\n  SUM=$1\n  LEN=$2\n  $TREMOR && [[ \"$3\" = \"vorbis\" ]] && DEC=tremor || DEC=$3\n  FMT=$4\n  CHANS=$5\n  RATE=$6\n\n  # Check that we have the full pathname and it's not a dangling symlink.\n  [[ \"$NAME\" = \"$(basename \"$FILE\")\" ]] || die Filename mismatch\n  [[ -L \"$FILE\" && ! -f \"$FILE\" ]] && continue\n\n  # Get the independant MD5 sum and length of audio file.\n  case $DEC in\n  aac|ffmpeg|flac|mp3|sndfile|speex|vorbis)\n      IGNORE_LEN=false\n      IGNORE_SUM=false\n      $DEC\n      SUM2=$(expr \"$SUM2\" : '\\([^ ]*\\)')\n      ;;\n  modplug|musepack|sidplay2|timidity|tremor|wavpack)\n      $IGNORE && continue\n      [[ \"${UNSUPPORTED[$DEC]}\" ]] || {\n        echo -e \"*** Decoder not yet supported: $DEC\\n\" > /dev/stderr\n        UNSUPPORTED[$DEC]=\"Y\"\n      }\n      continue\n      ;;\n  *)  [[ \"${UNKNOWN[$DEC]}\" ]] || {\n        echo -e \"*** Unknown decoder: $DEC\\n\" > /dev/stderr\n        UNKNOWN[$DEC]=\"Y\"\n      }\n      continue\n      ;;\n  esac\n\n  # Compare results.\n  BADFMT=false\n  $EXTRA && [[ \"$NAME\" == \"sinewave-*\" ]] && {\n    set -- $(echo $NAME | tr '.-' ' ')\n    FMT2=${2/24/32/}\n    CHANS2=$3\n    RATE2=$4\n    [[ \"$FMT\" = \"$FMT2\" ]] || BADFMT=true\n    [[ \"$CHANS\" = \"$CHANS2\" ]] || BADFMT=true\n    [[ \"$RATE\" = \"$RATE2\" ]] || BADFMT=true\n  }\n  BADSUM=false; $IGNORE_SUM || [[ \"$SUM\" = \"$SUM2\" ]] || BADSUM=true\n  BADLEN=false; $IGNORE_LEN || [[ \"$LEN\" = \"$LEN2\" ]] || BADLEN=true\n\n  # Set exit code.\n  $BADFMT || $BADSUM || $BADLEN && RC=2\n\n  # Determine output requirements.\n  $SILENT && continue\n  $BADFMT || $BADSUM || $BADLEN || $VERBOSE || continue\n\n  # Report result.\n  [[ \"$REVN\" ]] && {\n    echo \"Test Results for $REVN\"\n    echo\n    REVN=\n  }\n  echo \"$NAME:\"\n  echo \"    $SUM $LEN $DEC $FMT $CHANS $RATE\"\n  echo \"    $SUM2 $LEN2\"\n  $BADFMT && echo \"*** Format mismatch\"\n  $BADSUM && echo \"*** MD5 sum mismatch\"\n  $BADLEN && echo \"*** Length mismatch\"\n  echo\n\ndone < $LOG\n\n$SILENT || {\n  case \"$RV\" in\n  1)\n    echo \"No mismatches found\"\n    ;;\n  2)\n    echo \"NOTE: This tool is still being refined.  Do not accept mismatches\"\n    echo \"      at face value; they may be due to factors such as sample size\"\n    echo \"      differences.  But it does provide a reason to investigate\"\n    echo \"      such mismatches further (and further refine this tool if false).\"\n    echo\n    ;;\n  esac\n}\n\nexit $RC\n"
  },
  {
    "path": "utf8.c",
    "content": "/*\n * MOC - music on console\n * Copyright (C) 2005,2006 Damian Pietras <daper@daper.net>\n *\n * This program is free software; you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation; either version 2 of the License, or\n * (at your option) any later version.\n *\n */\n\n#ifdef HAVE_CONFIG_H\n# include \"config.h\"\n#endif\n\n#include <stdio.h>\n#include <stdarg.h>\n\n#ifdef HAVE_ICONV\n# include <iconv.h>\n#endif\n#ifdef HAVE_NL_TYPES_H\n# include <nl_types.h>\n#endif\n#ifdef HAVE_LANGINFO_H\n# include <langinfo.h>\n#endif\n\n#if defined HAVE_NCURSESW_CURSES_H\n# include <ncursesw/curses.h>\n#elif defined HAVE_NCURSESW_H\n# include <ncursesw.h>\n#elif defined HAVE_NCURSES_CURSES_H\n# include <ncurses/curses.h>\n#elif defined HAVE_NCURSES_H\n# include <ncurses.h>\n#elif defined HAVE_CURSES_H\n# include <curses.h>\n#endif\n\n#include <assert.h>\n#include <string.h>\n#include <errno.h>\n#include <wchar.h>\n\n#include \"common.h\"\n#include \"log.h\"\n#include \"options.h\"\n#include \"utf8.h\"\n#include \"rcc.h\"\n\nstatic char *terminal_charset = NULL;\nstatic int using_utf8 = 0;\n\nstatic iconv_t iconv_desc = (iconv_t)(-1);\nstatic iconv_t files_iconv_desc = (iconv_t)(-1);\nstatic iconv_t xterm_iconv_desc = (iconv_t)(-1);\n\n\n/* Return a malloc()ed string converted using iconv().\n * If for_file_name is not 0, use the conversion defined for file names.\n * For NULL returns NULL. */\nchar *iconv_str (const iconv_t desc, const char *str)\n{\n\tchar buf[512];\n#ifdef FREEBSD\n\tconst char *inbuf;\n#else\n\tchar *inbuf;\n#endif\n\tchar *outbuf;\n\tchar *str_copy;\n\tsize_t inbytesleft, outbytesleft;\n\tchar *converted;\n\n\tif (!str)\n\t\treturn NULL;\n\tif (desc == (iconv_t)(-1))\n\t\treturn xstrdup (str);\n\n\tinbuf = str_copy = xstrdup (str);\n\toutbuf = buf;\n\tinbytesleft = strlen(inbuf);\n\toutbytesleft = sizeof(buf) - 1;\n\n\ticonv (desc, NULL, NULL, NULL, NULL);\n\n\twhile (inbytesleft) {\n\t\tif (iconv(desc, &inbuf, &inbytesleft, &outbuf,\n\t\t\t\t\t&outbytesleft)\n\t\t\t\t== (size_t)(-1)) {\n\t\t\tif (errno == EILSEQ) {\n\t\t\t\tinbuf++;\n\t\t\t\tinbytesleft--;\n\t\t\t\tif (!--outbytesleft) {\n\t\t\t\t\t*outbuf = 0;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t*(outbuf++) = '#';\n\t\t\t}\n\t\t\telse if (errno == EINVAL) {\n\t\t\t\t*(outbuf++) = '#';\n\t\t\t\t*outbuf = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telse if (errno == E2BIG) {\n\t\t\t\toutbuf[sizeof(buf)-1] = 0;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t*outbuf = 0;\n\tconverted = xstrdup (buf);\n\tfree (str_copy);\n\n\treturn converted;\n}\n\nchar *files_iconv_str (const char *str)\n{\n    return iconv_str (files_iconv_desc, str);\n}\n\nchar *xterm_iconv_str (const char *str)\n{\n    return iconv_str (xterm_iconv_desc, str);\n}\n\nint xwaddstr (WINDOW *win, const char *str)\n{\n\tint res;\n\n\tif (using_utf8)\n\t\tres = waddstr (win, str);\n\telse {\n\t\tchar *lstr = iconv_str (iconv_desc, str);\n\n\t\tres = waddstr (win, lstr);\n\t\tfree (lstr);\n\t}\n\n\treturn res;\n}\n\n/* Convert multi-byte sequence to wide characters.  Change invalid UTF-8\n * sequences to '?'.  'dest' can be NULL as in mbstowcs().\n * If 'invalid_char' is not NULL it will be set to 1 if an invalid character\n * appears in the string, otherwise 0. */\nstatic size_t xmbstowcs (wchar_t *dest, const char *src, size_t len,\n\t\tint *invalid_char)\n{\n\tmbstate_t ps;\n\tsize_t count = 0;\n\n\tassert (src != NULL);\n\tassert (!dest || len > 0);\n\n\tmemset (&ps, 0, sizeof(ps));\n\n\tif (dest)\n\t\tmemset (dest, 0, len * sizeof(wchar_t));\n\n\tif (invalid_char)\n\t\t*invalid_char = 0;\n\n\twhile (src && (len || !dest)) {\n\t\tsize_t res;\n\n\t\tres = mbsrtowcs (dest, &src, len, &ps);\n\t\tif (res != (size_t)-1) {\n\t\t\tcount += res;\n\t\t\tsrc = NULL;\n\t\t}\n\t\telse {\n\t\t\tsize_t converted;\n\n\t\t\tsrc++;\n\t\t\tif (dest) {\n\t\t\t\tconverted = wcslen (dest);\n\t\t\t\tdest += converted;\n\t\t\t\tcount += converted;\n\t\t\t\tlen -= converted;\n\n\t\t\t\tif (len > 1) {\n\t\t\t\t\t*dest = L'?';\n\t\t\t\t\tdest++;\n\t\t\t\t\t*dest = L'\\0';\n\t\t\t\t\tlen--;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\t*(dest - 1) = L'\\0';\n\t\t\t}\n\t\t\telse\n\t\t\t\tcount++;\n\t\t\tmemset (&ps, 0, sizeof(ps));\n\n\t\t\tif (invalid_char)\n\t\t\t\t*invalid_char = 1;\n\t\t}\n\t}\n\n\treturn count;\n}\n\nint xwaddnstr (WINDOW *win, const char *str, const int n)\n{\n\tint res, width, inv_char;\n\twchar_t *ucs;\n\tchar *mstr, *lstr;\n\tsize_t size, num_chars;\n\n\tassert (n > 0);\n\tassert (str != NULL);\n\n\tmstr = iconv_str (iconv_desc, str);\n\n\tsize = xmbstowcs (NULL, mstr, -1, NULL) + 1;\n\tucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size);\n\txmbstowcs (ucs, mstr, size, &inv_char);\n\twidth = wcswidth (ucs, WIDTH_MAX);\n\n\tif (width == -1) {\n\t\tsize_t clidx;\n\t\tfor (clidx = 0; clidx < size - 1; clidx++) {\n\t\t\tif (wcwidth (ucs[clidx]) == -1)\n\t\t\t\tucs[clidx] = L'?';\n\t\t}\n\t\twidth = wcswidth (ucs, WIDTH_MAX);\n\t\tinv_char = 1;\n\t}\n\n\tif (width > n) {\n\t\twhile (width > n)\n\t\t\twidth -= wcwidth (ucs[--size]);\n\t\tucs[size] = L'\\0';\n\t}\n\n\tnum_chars = wcstombs (NULL, ucs, 0);\n\tlstr = (char *)xmalloc (num_chars + 1);\n\n\tif (inv_char)\n\t\twcstombs (lstr, ucs, num_chars + 1);\n\telse\n\t\tsnprintf (lstr, num_chars + 1, \"%s\", mstr);\n\n\tres = waddstr (win, lstr);\n\n\tfree (ucs);\n\tfree (lstr);\n\tfree (mstr);\n\treturn res;\n}\n\nint xmvwaddstr (WINDOW *win, const int y, const int x, const char *str)\n{\n\tint res;\n\n\tif (using_utf8)\n\t\tres = mvwaddstr (win, y, x, str);\n\telse {\n\t\tchar *lstr = iconv_str (iconv_desc, str);\n\n\t\tres = mvwaddstr (win, y, x, lstr);\n\t\tfree (lstr);\n\t}\n\n\treturn res;\n}\n\nint xmvwaddnstr (WINDOW *win, const int y, const int x, const char *str,\n\t\tconst int n)\n{\n\tint res;\n\n\tif (using_utf8)\n\t\tres = mvwaddnstr (win, y, x, str, n);\n\telse {\n\t\tchar *lstr = iconv_str (iconv_desc, str);\n\n\t\tres = mvwaddnstr (win, y, x, lstr, n);\n\t\tfree (lstr);\n\t}\n\n\treturn res;\n}\n\nint xwprintw (WINDOW *win, const char *fmt, ...)\n{\n\tva_list va;\n\tint res;\n\tchar *buf;\n\n\tva_start (va, fmt);\n\tbuf = format_msg_va (fmt, va);\n\tva_end (va);\n\n\tif (using_utf8)\n\t\tres = waddstr (win, buf);\n\telse {\n\t\tchar *lstr = iconv_str (iconv_desc, buf);\n\n\t\tres = waddstr (win, lstr);\n\t\tfree (lstr);\n\t}\n\n\tfree (buf);\n\n\treturn res;\n}\n\nstatic void iconv_cleanup ()\n{\n\tif (iconv_desc != (iconv_t)(-1)\n\t\t\t&& iconv_close(iconv_desc) == -1)\n\t\tlog_errno (\"iconv_close() failed\", errno);\n}\n\nvoid utf8_init ()\n{\n#ifdef HAVE_NL_LANGINFO_CODESET\n#ifdef HAVE_NL_LANGINFO\n\tterminal_charset = xstrdup (nl_langinfo(CODESET));\n\tassert (terminal_charset != NULL);\n\n\tif (!strcmp(terminal_charset, \"UTF-8\")) {\n#ifdef HAVE_NCURSESW\n\t\tlogit (\"Using UTF8 output\");\n\t\tusing_utf8 = 1;\n#else /* HAVE_NCURSESW */\n\t\tterminal_charset = xstrdup (\"US-ASCII\");\n\t\tlogit (\"Using US-ASCII conversion - compiled without libncursesw\");\n#endif /* HAVE_NCURSESW */\n\t}\n\telse\n\t\tlogit (\"Terminal character set: %s\", terminal_charset);\n#else /* HAVE_NL_LANGINFO */\n\tterminal_charset = xstrdup (\"US-ASCII\");\n\tlogit (\"Assuming US-ASCII terminal character set\");\n#endif /* HAVE_NL_LANGINFO */\n#endif /* HAVE_NL_LANGINFO_CODESET */\n\n\tif (!using_utf8 && terminal_charset) {\n\t\ticonv_desc = iconv_open (terminal_charset, \"UTF-8\");\n\t\tif (iconv_desc == (iconv_t)(-1))\n\t\t\tlog_errno (\"iconv_open() failed\", errno);\n\t}\n\n\tif (options_get_bool (\"FileNamesIconv\"))\n\t\tfiles_iconv_desc = iconv_open (\"UTF-8\", \"\");\n\n\tif (options_get_bool (\"NonUTFXterm\"))\n\t\txterm_iconv_desc = iconv_open (\"\", \"UTF-8\");\n}\n\nvoid utf8_cleanup ()\n{\n\tif (terminal_charset)\n\t\tfree (terminal_charset);\n\ticonv_cleanup ();\n}\n\n/* Return the number of columns the string occupies when displayed. */\nsize_t strwidth (const char *s)\n{\n\twchar_t *ucs;\n\tsize_t size;\n\tsize_t width;\n\n\tassert (s != NULL);\n\n\tsize = xmbstowcs (NULL, s, -1, NULL) + 1;\n\tucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size);\n\txmbstowcs (ucs, s, size, NULL);\n\twidth = wcswidth (ucs, WIDTH_MAX);\n\tfree (ucs);\n\n\treturn width;\n}\n\n/* Return a malloc()ed string containing the tail of 'str' up to a\n * maximum of 'len' characters (in columns occupied on the screen). */\nchar *xstrtail (const char *str, const int len)\n{\n\twchar_t *ucs;\n\twchar_t *ucs_tail;\n\tsize_t size;\n\tint width;\n\tchar *tail;\n\n\tassert (str != NULL);\n\tassert (len > 0);\n\n\tsize = xmbstowcs(NULL, str, -1, NULL) + 1;\n\tucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size);\n\txmbstowcs (ucs, str, size, NULL);\n\tucs_tail = ucs;\n\n\twidth = wcswidth (ucs, WIDTH_MAX);\n\tassert (width >= 0);\n\n\twhile (width > len)\n\t\twidth -= wcwidth (*ucs_tail++);\n\n\tsize = wcstombs (NULL, ucs_tail, 0) + 1;\n\ttail = (char *)xmalloc (size);\n\twcstombs (tail, ucs_tail, size);\n\n\tfree (ucs);\n\n\treturn tail;\n}\n"
  },
  {
    "path": "utf8.h",
    "content": "#ifndef UTF8_H\n#define UTF8_H\n\n#if defined HAVE_NCURSESW_CURSES_H\n# include <ncursesw/curses.h>\n#elif defined HAVE_NCURSESW_H\n# include <ncursesw.h>\n#elif defined HAVE_NCURSES_CURSES_H\n# include <ncurses/curses.h>\n#elif defined HAVE_NCURSES_H\n# include <ncurses.h>\n#elif defined HAVE_CURSES_H\n# include <curses.h>\n#endif\n\n#include <stdarg.h>\n#ifdef HAVE_ICONV\n# include <iconv.h>\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* parameter passed to wcswidth() as a maximum width */\n#define WIDTH_MAX\t2048\n\nvoid utf8_init ();\nvoid utf8_cleanup ();\nint xwaddstr (WINDOW *win, const char *str);\nint xwaddnstr (WINDOW *win, const char *str, const int n);\nint xmvwaddstr (WINDOW *win, const int y, const int x, const char *str);\nint xmvwaddnstr (WINDOW *win, const int y, const int x, const char *str,\n\t\tconst int n);\nint xwprintw (WINDOW *win, const char *fmt, ...) ATTR_PRINTF(2, 3);\nsize_t strwidth (const char *s);\nchar *xstrtail (const char *str, const int len);\nchar *iconv_str (const iconv_t desc, const char *str);\nchar *files_iconv_str (const char *str);\nchar *xterm_iconv_str (const char *str);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  }
]