--- /dev/null
+# normal ignores
+.*
+*~
+*.[ao]
+*.lo
+*.so
+tags
+!.gitignore
+
+# top-level ignores
+/*.spec
+/config
+/config.mk
+/cmus
+/cmus-remote
+
+# Cygwin stuff
+*.exe
+/cmus.base
+/cmus.def
+/cmus.exp
--- /dev/null
+Maintainers
+-----------
+Gregory Petrosyan <gregory.petrosyan@gmail.com>
+Jason Woofenden <jason@jasonwoof.com>
+
+Original Author
+---------------
+Timo Hirvonen <tihirvon@gmail.com>
+
+NOTE: This list is not complete. Especially small changes/bug fixes may
+ not be listed here. See the git repository for full list of
+ contributors.
+
+Credits
+-------
+original help window code and mad.charset option by Sergey Kuleshov
+<svyatogor@gentoo.org>
+
+artist/album mode idea and "display artist/album as a tree instead of two
+windows" idea from divxero <divxero@gmx.net>
+
+play queue idea and other misc ideas from Martin Stubenschrott
+<stubenschrott@gmx.net>
+
+original RPM spec file by Eugene Vlasov <eugene@ikz.ru>
+
+Claes Nästen <pekdon@gmail.com>
+ :seek command
+ --volume option for cmus-remote
+
+Frank Terbeck <ft@bewatermyfriend.org>
+ dynamic keybindings patch
+
+alex <pukpuk@gmx.de>
+ Sun output plugin
+ Tremor support for vorbis plugin
+ NetBSD and OpenBSD port
+ Various bug fixes
+
+Chun-Yu Shei <cshei@cs.indiana.edu>
+ mpc plugin
+ gapless MP3 playback
+
+Johannes Weißl <jargon@molb.org>
+ ao plugin
+
+Gregory Petrosyan <gregory.petrosyan@gmail.com>
+ PulseAudio output plugin
+
+Philipp 'ph3-der-loewe' Schafft <lion@lion.leolix.org>
+ RoarAudio output plugin
+
+Jason Woofenden <jason@jasonwoof.com>
+ Tutorial
+ cmus-unofficial patch-commiter
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null
+.*
+*.1
+*.7
+ttman
--- /dev/null
+@title CMUS-REMOTE 1 05/11/2006 cmus
+
+@h1 NAME
+
+cmus-remote - control cmus
+
+
+@h1 SYNOPSIS
+
+cmus-remote [*OPTION*]... [`FILE`|`DIR`|`PLAYLIST`]...@br
+cmus-remote *-C* `COMMAND`...@br
+cmus-remote
+
+
+@h1 DESCRIPTION
+
+Add `FILE/DIR/PLAYLIST` to playlist, library (*-l*) or play queue (*-q*).
+
+If no arguments are given cmus-remote reads raw commands from stdin (one
+command per line). Raw commands are cmus' command mode commands. These same
+commands are used in configuration files and key bindings. *cmus*(1) contains
+full list of commands. For consistency also searching is supported:
+*-C /text*.
+
+When *-C* is given all command line arguments are treated as raw commands.
+
+@h1 OPTIONS
+
+--server SOCKET
+ Connect using socket *SOCKET* instead of `~/.cmus/socket`.
+
+--help
+ Display usage information and exit.
+
+--version
+ Display version information and exit.
+
+-p, --play
+ Start playing.
+
+-u, --pause
+ Toggle pause.
+
+-s, --stop
+ Stop playing.
+
+-n, --next
+ Skip forward in playlist.
+
+-r, --prev
+ Skip backward in playlist.
+
+-R, --repeat
+ Toggle repeat.
+
+-S, --shuffle
+ Toggle shuffle.
+
+-v, --volume VOL
+ Change volume. See *vol* command in *cmus*(1).
+
+-k, --seek SEEK
+ Seek. See *seek* command in *cmus*(1).
+
+-Q
+ Get player status information. Same as *-C status*. Note that
+ *status* is a special command only available to cmus-remote.
+
+-l, --library
+ Modify library instead of playlist.
+
+-P, --playlist
+ Modify playlist (default).
+
+-q, --queue
+ Modify play queue instead of playlist.
+
+-c, --clear
+ Clear playlist, library (*-l*) or play queue (*-q*).
+
+-C, --raw
+ Treat arguments (instead of stdin) as raw commands.
+
+@h1 EXAMPLES
+
+Add playlists/files/directories/URLs to library view (1 & 2):
+
+ @pre
+ $ cmus-remote -l music.m3u \\
+ http://live.urn1350.net:8080/urn_high.ogg
+ @endpre
+
+Load (clear and add) playlist to playlist view (3):
+
+ @pre
+ $ cmus-remote -c music.m3u
+ @endpre
+
+Three different ways to toggle repeat:
+
+ @pre
+ $ cmus-remote -R
+ $ cmus-remote -C "toggle repeat"
+ $ cmus-remote
+ toggle repeat
+ ^D
+ @endpre
+
+Query settings or key bindings:
+
+ @pre
+ $ cmus-remote -C "set repeat?"
+ setting: 'repeat=false'
+ $ cmus-remote -C "showbind common a"
+ bind common a win-add-l
+ @endpre
+
+Dump the playlist to stdout:
+
+ @pre
+ $ cmus-remote -C "save -p -"
+ [...]
+ @endpre
+
+Search works too:
+
+ @pre
+ $ cmus-remote -C /beatles
+ @endpre
+
+@h1 SEE ALSO
+
+*cmus*(1)
+
+@h1 AUTHOR
+
+Written by Timo Hirvonen <tihirvon\@gmail.com>
--- /dev/null
+@title cmus-tutorial 7 14/02/2010 cmus
+
+
+@h1 NAME
+
+cmus - C\* Music Player tutorial
+
+
+@h1 CONTENTS
+
+Step 1: Starting Cmus
+
+Step 2: Adding Music
+
+Step 3: Playing Tracks From The Library
+
+Step 4: Managing The Queue
+
+Step 5: The Playlist
+
+Step 6: Find that track
+
+Step 7: Customization
+
+Step 8: Quit
+
+Step 9: Further Reading
+
+
+@h1 Step 1: Starting Cmus
+
+When you first launch cmus (just type `cmus` in a terminal and press Enter) it
+will open to the album/artist view, which looks something like this:
+
+@pre
++---------------------------------------------------------------------+
+| Artist / Album Track Library |
+| | |
+| | |
+| | |
+| | |
+| | |
+| | |
+| | |
+| |
+| . 00:00 - 00:00 vol: 100 all from library | C |
+| |
++---------------------------------------------------------------------+
+@endpre
+
+This is the view where your artists and albums will be displayed.
+
+
+@h1 Step 2: Adding Music
+
+Press *5* to switch to the file-browser view so we can add some music. You
+should see something like this:
+
+@pre
++---------------------------------------------------------------------+
+| Browser - /home/jasonwoof |
+| ../ |
+| Desktop/ |
+| MySqueak/ |
+| audio-projects/ |
+| audio/ |
+| bin/ |
+| config/ |
+| |
+| . 00:00 - 00:00 vol: 100 all from library | C |
+| |
++---------------------------------------------------------------------+
+@endpre
+
+Now, use the arrow keys, Enter and Backspace to navigate to where you have
+audio files stored. To add music to your cmus library, use the arrow keys to
+hilight a file or folder, and press *a*. When you press *a* cmus will move you
+to the next line down (so that it is easy to add a bunch of files/folders in a
+row) and start adding the file/folder you pressed *a* on to your library. This
+can take a while if you added a folder with a lot in it. As files are added,
+you will see the second time in the bottom right go up. This is the total
+duration of all the music in the cmus library.
+
+Note: cmus does not move, duplicate or change your files. It just remembers
+where they are and caches the metadata (duration, artist, etc.)
+
+Just to be on the safe side, lets save. Type *:save* and press Enter.
+
+Note: Cmus automatically saves your settings and library and everything when
+you quit, so you probably won't use the save command much.
+
+
+@h1 Step 3: Playing Tracks From The Library
+
+Press *2* to go to the simple library view. You should see something like
+this:
+
+@pre
++---------------------------------------------------------------------+
+| Library ~/.cmus/lib.pl - 31 tracks sorted by artist album discnumbe |
+| Flying Lizards . Money (That's What I Want) 02:31 |
+| Jason Woofenden . VoR Theme 2009 01:20 |
+| Keali'i Reichel 06. Wanting Memories 1994 04:28 |
+| Molly Lewis . Tom Cruise Crazy 03:13 |
+| NonMemory . pista1 2009 03:18 |
+| NonMemory 01. pista1 2009-04-21 04:13 |
+| Ray Charles 06. Halleluja I Love Her So 02:33 |
+| |
+| . 00:00 - 2:16:25 vol: 100 all from library | C |
+| |
++---------------------------------------------------------------------+
+@endpre
+
+Use the up and down arrow keys to select a track you'd like to hear, and press
+Enter to play it. Here's some keys to control play:
+
+Press *c* to pause/unpause
+Press right/left to seek by 10 seconds
+Press *<*/*>* seek by one minute
+
+cmus has some great options to control what plays next (if anything) when the
+track ends. The state of these settings are shown in the bottom right corner.
+The first of these shows what collection of tracks (currently "all from
+library") we are playing. Press *m* to cycle through the different options for
+this setting. To the right of that (past the "|") cmus shows the state of three
+toggles. Only toggles which are "on" are shown, so now we only see the *C*.
+Here are the toggles:
+
+[C]ontinue
+
+ When this is off, cmus will always stop at the end of the track. You can
+toggle this setting by pressing *shift-C*.
+
+[R]epeat
+
+ If this is on (and continue is on), when cmus reaches the end of the group
+of tracks you're playing (selected with the *m* key) it will start again from
+the beginning. Press *r* to toggle this setting.
+
+[S]huffle
+
+ When this is on, cmus will choose a random order to play all the tracks
+once. Press *s* to toggle this option.
+
+
+@h1 Step 4: Managing The Queue
+
+Lets say you're listening to a song, and you want to select which song will
+play next, without interrupting the currently playing song. No problem! Just go
+to the song you want to hear next (in any of the views) and press *e*. The
+queue is FIFO, meaning if you queue up another track, it will play after the
+one you already had queued up.
+
+Note: The queue is not effected by the "shuffle" option described above.
+
+Press *4* to view/edit the queue. This view works and looks a lot like the
+simple library view. The main difference is that you can change the order of
+the tracks with the *p* and *P* keys. You can press *shift-D* to remove a track
+from the queue.
+
+When cmus is ready to play another track (it's reached the end of a track and
+the "continue" setting is on) it will remove the top entry from the queue and
+start playing it.
+
+
+@h1 Step 5: The Playlist
+
+The playlist works like another library (like view *2*) except that (like
+the queue) you manually set the order of the tracks. This can be quite useful
+if you want to create a mix of specific tracks or if you want to listen to an
+audio book without having the chapters play when you're playing "all from
+library".
+
+The playlist is on view *3*. But before we go there, lets add some tracks.
+Press *2* to go to the simple library view, go to a track you want and press
+*y* to add it to the playlist. The only visual feedback you'll get that
+anything happened is that the hilight will move down one row. Add a few more so
+you have something to work with.
+
+Now press *3* to go to the playlist.
+
+Just like the queue, you can use the *p*, *P* and *D* keys to move and delete
+tracks from the playlist.
+
+Note: Changing the view (e.g. by pressing *3*) does not affect what cmus will
+play next. To put cmus into "play from the playlist" mode, press Enter on one
+of the tracks in the playlist. To switch modes without interrupting the
+currently-playing song, you can press *shift-M*.
+
+
+@h1 Step 6: Find that track
+
+This step shows various ways you can find track(s) you're looking for.
+
+Search: Press *2* to be sure you're on the simple library view, then press */*
+to start a search. Type a word or two from the track you're looking for. cmus
+will search for tracks that have all those words in them. Press enter to get
+the keyboard out of the search command, and *n* to find the next match.
+
+Tree View: Press *1* to select the tree view. Scroll to the artist, press
+*space* to show their albums, scroll to the album you want, then press tab so
+the keyboard controls the right column. Press tab again to get back to the left
+column.
+
+Filters: See the reference manual (see Further Reading below) for a detailed
+description on how to quickly (and temporarily) hide most of your music.
+
+
+@h1 Step 7: Customization
+
+Cmus has some very cool settings you can tweak, like changing the way tracks
+are displayed (e.g. to display disk numbers), enabling replaygain support or
+changing the keybindings.
+
+Press *7* for a quick overview of the current keybindings and settings.
+
+To change a setting or keybind, just select it (up/down keys) and press enter.
+This will put the command for the current setting in the command now (bottom
+left of your screen), which you can edit to put in a new value/key.
+
+Please see the reference manual (see Further Reading below) for a detailed
+description of all the commands and settings available.
+
+
+@h1 Step 8: Quit
+
+When you're done, type *:q* and press Enter to quit. This will save your
+settings, library, playlist and queue.
+
+
+@h1 Step 9: Further Reading
+
+Cmus comes with a great reference manual. Now that you've got the basics down
+it should be intelligible. Try *man cmus* in a terminal. If that's not
+installed, try opening up `cmus.txt` from the `Doc` directory, or read the latest
+version online:
+
+`http://gitorious.org/cmus/cmus/blobs/master/Doc/cmus.txt`
+
+There are more commands and features not covered hear like loading and saving
+playlists, controlling cmus remotely with `cmus-remote`, etc.
+
--- /dev/null
+@title CMUS 1 31/01/2010 cmus
+
+@h1 NAME
+
+cmus - C\* Music Player
+
+
+@h1 SYNOPSIS
+
+cmus [*options*]
+
+
+@h1 DESCRIPTION
+
+cmus is a small ncurses based music player. It supports various output
+methods by output-plugins. It has got completely configurable keybindings and
+it can be controlled from the outside via *cmus-remote*(1).
+
+@h1 OPTIONS
+
+--listen ADDR
+ Listen to ADDR (UNIX socket) instead of `~/.cmus/socket`.
+ ADDR is either a UNIX socket or host[:port].
+
+ *WARNING*: Using host[:port] is insecure even with password!
+ It might be useful though in LAN if you want multiple local users to
+ able to control cmus. Never make cmus listen to the internet.
+
+ NOTE: Don't use this option to run multiple instances as same user.
+ That would corrupt the track metadata cache.
+
+--plugins
+ List available plugins and exit.
+
+--show-cursor
+ Keep cursor always visible. This is useful for screen readers.
+
+--help
+ Display usage information and exit.
+
+--version
+ Display version information and exit.
+
+
+@h1 VIEWS
+
+There are 7 views in cmus. Press keys 1-7 to change active view.
+
+Library view (1)
+ Display all tracks in so-called *library*. Tracks are sorted
+ artist/album tree. Artist sorting is done alphabetically. Albums are
+ sorted by year.
+
+Sorted library view (2)
+ Displays same content as view 1, but as a simple list which is
+ automatically sorted by user criteria.
+
+Playlist view (3)
+ Displays editable playlist with optional sorting.
+
+Play Queue view (4)
+ Displays queue of tracks which are played next. These tracks are
+ played before anything else (i.e. the playlist or library).
+
+Browser (5)
+ Directory browser. In this view, music can be added to either the
+ library, playlist or queue from the filesystem.
+
+Filters view (6)
+ Lists user defined filters.
+
+Settings view (7)
+ Lists keybindings, unbound commands and options. Remove bindings with
+ *D* or *del*, change bindings and variables with *enter* and toggle
+ variables with *space*.
+
+@h1 COMMAND LINE
+
+Everything in cmus is implemented as commands which can be typed at command
+line or bound to a key. To enter command mode type *:*. To execute a command
+press *ENTER* or to cancel press *ESC*. Use up/down arrows to browse command
+history. Use *TAB* to complete commands and parameters, you can tab complete
+almost anything. You don't need to type full command names if the command is
+unambiguous (no other commands starting with the same characters).
+
+Examples:
+
+ @pre
+ # add files, short for ':add ~/music'
+ :a ~/music
+
+ # change output plugin
+ :set output_plugin=oss
+
+ # start playing
+ # you could just press 'x' which is the default
+ # binding for this command
+ :player-play
+
+ # clear current view (library, playlist or play queue)
+ :clear
+ @endpre
+
+
+@h1 SEARCHING
+
+Search mode works like the command mode, to enter search mode press */* and
+then type the search words and press *ENTER*. Press *n* to search next or *N*
+to search previous match using the same search words. Type *?* to search
+backwards.
+
+In views 1-4 words are compared to artist, album and title tags. Type
+*//WORDS* or *??WORDS* to search only artists/albums in view 1 or titles in
+views 2-4. If the file doesn't have tags words are compared to filename
+without path.
+
+Searching works in views 5-7 too and its logic should be pretty obvious.
+
+
+@h1 PLAYLIST EDITING
+
+@h2 Selecting Tracks
+
+Editing commands affect the currently marked tracks or if there are no marked
+tracks the currently selected track (or selected artist/album in view 1).
+
+Mark selected track by pressing *SPACE*. Marked tracks appear with a gray
+background. You can only mark tracks in the list views (2-4).
+
+@h2 Copying Tracks Between Views
+
+You can copy marked or selected tracks from views 1-5.
+
+@li *a*
+copy tracks to the library (1-2)
+
+@li *y*
+copy tracks to the playlist (3)
+
+@li *e*
+append tracks to the play queue (4)
+
+@li *E*
+prepend tracks to the play queue (4)
+
+@h2 Moving Tracks
+
+In views 2-4 you can move tracks withing the list. Note that moving is
+disabled if the view is auto-sorted (see *lib_sort* and *pl_sort* options).
+
+Pressing *p* moves marked tracks to the position immediately after the
+selected track. *P* moves them to the position immediately before the
+selected track. If there are no marked tracks then the selected track is
+moved down (*p*) or up (*P*).
+
+NOTE: Changing active filters reloads view 2 so it isn't a good idea to
+manually order tracks in the view.
+
+@h2 Removing Tracks
+
+Press *D* or *delete* to remove marked or selected tracks in the current view
+(1-4). The tracks will be removed immediately from the view without asking
+for confirmation. In the browser and filters views the same keys are used to
+remove a file or filter (will ask for confirmation).
+
+
+@h1 STATUS LINE
+
+Right hand side of the status line (second row from the bottom, black text on
+a grey background) consists of the following fields:
+
+@pre
+aaa_mode & play_sorted & play_library | continue repeat shuffle
+@endpre
+
+NOTE: *aaa_mode* and *play_sorted* will be displayed only if *play_library* is
+*true* because these are meaningless when playing the playlist (view 3).
+
+Pressing *m*, *o*, *M*, *C*, *r* and *s* keys should make it easier to
+understand what all those fields mean.
+
+See CONFIGURATION OPTIONS section for more information about these options.
+
+
+@h1 KEYBINDINGS
+
+Here's list of default keybindings. See *unbind* and *bind* commands in the
+COMMANDS section.
+
+
+@h2 Common Context
+@pre
+q quit -i
+^C echo Type :quit<enter> to exit cmus.
+I echo {}
+b player-next
+c player-pause
+x player-play
+z player-prev
+v player-stop
+^L refresh
+n search-next
+N search-prev
+. seek +1m
+l, right seek +5
+, seek -1m
+h, left seek -5
+m toggle aaa_mode
+C toggle continue
+M toggle play_library
+o toggle play_sorted
+r toggle repeat
+^R toggle repeat_current
+t toggle show_remaining_time
+s toggle shuffle
+F push filter<space>
+L push live-filter<space>
+u update-cache
+1 view tree
+2 view sorted
+3 view playlist
+4 view queue
+5 view browser
+6 view filters
+7 view settings
+! push shell<space>
+] vol +0 +1
+[ vol +1 +0
++, = vol +10%
+} vol -0 -1
+{ vol -1 -0
+- vol -10%
+enter win-activate
+E win-add-Q
+a win-add-l
+y win-add-p
+e win-add-q
+G, end win-bottom
+down, j win-down
+p win-mv-after
+P win-mv-before
+tab win-next
+^F, page_down win-page-down
+^B, page_up win-page-up
+D, delete win-remove
+i win-sel-cur
+space win-toggle
+g, home win-top
+k, up win-up
+@endpre
+
+@h2 Browser Context
+@pre
+space win-activate
+backspace browser-up
+i toggle show_hidden
+u win-update
+@endpre
+
+
+@h1 LIBRARY VIEW SORTING
+
+The library view (the tree-like one; not the sorted library view, for which
+the sorting is controlled by the user by setting lib_sort - see `CONFIGURATION
+OPTIONS`), is sorted automatically by cmus using the information found in the
+tagging information provided by the audio files.
+
+Generally, in the library view cmus uses three levels of sorting: the first
+level would be the artist name, the second one the album and finally the
+actual track.
+
+At first, cmus checks if the "filename" looks like an URL, if it does, the
+item is given the special artist and album name *<Stream>*.
+
+If it is a file, it is checked if the artist and album tags are set. If not,
+cmus assigns the special name *<No Name>* for the unset tag.
+
+As the first level, cmus sorts alphanumerically by the value of the artist
+tag. (<Stream> and <No Name> will be used as if they where normal names.)
+If a special sorting tag is available, it's value will be used instead.
+
+For album names, alphanumerical sorting is not the primary method, though.
+To decide, how the second level should be sorted, cmus looks at the date of
+the first track of each album. Sorting is done from young to old. Of course,
+if one artist happens to have more then one album from one year,
+alphanumerical sorting will be used after sorting by date.
+
+If the date header is not set, the album will be placed on top of the list (in
+fact, the internal integer value for unset album tags is -1).
+
+The method for third sorting level (the track) is very similar to album
+sorting. First two numerical values are checked (discnumber and tracknumber).
+If sorting is still ambiguous, sorting will be done alphanumerically by the
+value of the track's `filename` (not track name!).
+
+For simple albums, that is it. There is a special case, though. Albums, that
+feature various artists, also known as samplers or compilations.
+
+If a track belongs to a compilation is again decided by the existence and
+value of special tagging information. First, it is checked if cmus should use
+a special artist name (e.g.: `'Fatboy Slim'` for a DJ set). If so, that one
+will be used instead of the real artist name.
+
+If that special name tag is not set, cmus checks if another tag is
+set. If that is the case, the album will be given the special artist
+name *<Various Artists>*. Albums filed under *<Various Artists>* are sorted
+alphanumerically by album name instead of by date.
+
+That way, you do not end up with compilation tracks scattered around your
+library view.
+
+The problem with compilation tagging is, that there is no generic tag or
+method, that can be regarded as a standard across all different formats,
+supported by cmus.
+
+For mp3, the special-name tag would be the id3v2 *TPE2* frame. The
+mark-as-compilation tag is the *TCMP* frame (which is a user defined id3v2.3
+frame, used at least by amarok and apple's iTunes[tm]).
+
+For vorbis style tags (for example in ogg vorbis and flac files), the
+special-name tag is *ALBUMARTIST* and the mark-as-compilation tag is
+*COMPILATION*. Vorbis tags names are case insensitive.
+
+
+@h1 COMMANDS
+
+This section describes cmus' commands. You can bind a key to any of these
+commands, put these commands to configuration files and execute them in
+command mode. Also cmus-remote uses these commands in its protocol.
+
+Optional parameters are in brackets, obligatory parameters in angle brackets
+and default key bindings in parenthesis.
+
+add [-l] [-p] [-q] [-Q] <file|dir|url|playlist>
+ Add file/dir/url/playlist to the specified view or the current view.
+
+ @li -l
+ add to library
+
+ @li -p
+ add to playlist
+
+ @li -q
+ add play queue
+
+ @li -Q
+ prepend to play queue
+
+ URL is a Shoutcast stream (http://...) or a CDDA URL (cdda://...)
+ (see *PLAYING AUDIO DISCS*).
+
+ Supported playlist: plain, .m3u, .pls.
+
+bind [-f] <context> <key> <command>
+ Add a key binding.
+
+ @li -f
+ overwrite existing binding
+
+ Use tab to expand contexts, keys and commands. Command is any command
+ listed in this section.
+
+ Valid key contexts
+ common, library (1-2), playlist (3), queue (4), browser (5),
+ filters (6)
+
+ There's one context for each view. Common is a special context on
+ which bound keys work in every view.
+
+ You can override specific keys in common context for a view. For
+ example *i* selects the current track in views 1-3 but in browser it
+ is overridden to toggle showing of hidden files.
+
+browser-up (*backspace*)
+ Change to parent directory in browser view (5). This command only
+ makes sense to be bound to the *browser* key context although it's
+ possible to use this even if browser view is not active.
+
+cd [directory]
+ Changes the current working directory. Changes the directory
+ displayed in browser view too.
+
+clear [-l] [-p] [-q]
+ Remove all tracks from the specified view or the current view.
+
+ @li -l
+ clear library
+
+ @li -p
+ clear playlist
+
+ @li -q
+ clear play queue
+
+colorscheme <name>
+ Change color scheme. Color schemes are found in `/usr/share/cmus/` or
+ `~/.cmus/` and have .theme filename extension.
+
+echo <arg>...
+ Display arguments on the command line.
+
+ If the arguments contain *{}* it is replaced with file name of the
+ first selected track.
+
+ NOTE: unlike with *run* the *{}* is replaced with only the first
+ selected filename.
+
+ Default bindings:
+
+ @pre
+ common I echo {}
+ common ^C echo Type :quit<enter> to exit cmus.
+ @endpre
+
+factivate <user-defined-filter>...
+ Select and activate the given user defined filters (displayed in the
+ filters view). Filter names are separated by spaces. This command is
+ mostly useful when bound to a key, to change active filters very
+ quickly. If no arguments given then all filters are unactivated.
+
+ If you prefix a filter name with "!" then the filter value is negated
+ before activation.
+
+filter <filter-expression>
+ Use this command when you want to temporarily filter contents of the
+ library views without having separately define (fset) and activate the
+ filter. The filter is not saved.
+
+fset <name>=<filter-expression>
+ Define (or replace existing) filter and add it to filters view (6).
+
+invert
+ Invert the marking of tracks in playlist and queue views. See *mark*
+ and *unmark*.
+
+live-filter <simple-filter-expression|short-filter-expression>
+ Use this command when you want to temporarily filter contents of the
+ library views without having separately define (fset) and activate the
+ filter. The filter is not saved.
+
+load [-l] [-p] <playlist>
+ Load a playlist to the specified view or to the current view.
+
+ @li -l
+ load to library views
+
+ @li -p
+ load to playlist view
+
+lqueue [NUM]
+ Queue NUM (default 1) random albums from the library. See also
+ *tqueue*.
+
+mark <filter-expression>
+ Mark tracks in playlist and queue view by using a filter expression.
+
+player-next (*b*)
+ Skip to the next track.
+
+player-pause (*c*)
+ Toggle pause.
+
+player-play [filename] (*x*)
+ Play the given track, or, if none is specified, [re]play the current
+ track from the beginning.
+
+player-prev (*z*)
+ Skip to the previous track.
+
+player-stop (*v*)
+ Stop playback.
+
+prev-view
+ Go to previously used view.
+
+push <text>
+ Enter command mode with the command line pre-set to text. Example:
+
+ bind common w push filter artist=
+
+ Text can contain spaces and even trailing spaces will be honored.
+ This command can only be bound to a key but not used in the command
+ line directly.
+
+quit [-i] (*q*, *:wq*)
+ Exit cmus.
+
+ @li -i
+ ask before exiting
+
+refresh (*^L*)
+ Redraw the terminal window.
+
+run <command>
+ Run command for the marked tracks OR the selected one if none marked.
+
+ By default file names are appended to the command. If the command
+ contains *{}* it is replaced with list of filenames.
+
+ NOTE: In view 1 you can run a command for all files in the selected
+ album or artist.
+
+save [-e] [-l] [-L] [-p] [-q] [file] (*:w*)
+ Save the specified view's or the current view's contents to a playlist
+ file. In extended mode (-e), also save metadata.
+
+ @li -l
+ save library views
+
+ @li -L
+ save filtered library views
+
+ @li -p
+ save playlist view
+
+ @li -q
+ save queue view
+
+ If no filename given the old filename is used. "-" outputs to stdout
+ (works only remotely).
+
+search-next (*n*)
+ If a search pattern has been entered before, search forward for the
+ next match in the current view. See *SEARCHING* above.
+
+search-prev (*N*)
+ If a search pattern has been entered before, search backwards for the
+ previous match in the current view. See *SEARCHING* above.
+
+seek [+-](<num>[mh] | [HH:]MM:SS)
+ Seek to absolute or relative position. Position can be given in
+ seconds, minutes (m), hours (h) or HH:MM:SS format where HH: is
+ optional.
+
+ Seek 1 minute backward
+ :seek -1m
+
+ Seek 5 seconds forward
+ :seek +5
+
+ Seek to absolute position 1h
+ :seek 1h
+
+ Seek 90 seconds forward
+ :seek +1:30
+
+ Default bindings:
+
+ @pre
+ common , :seek -1m
+ common . :seek +1m
+ common l :seek +5
+ common h :seek -5
+ common right :seek +5
+ common left :seek -5
+ @endpre
+
+set <option>=<value>
+ Set value of an option. See *OPTIONS*.
+
+set <option>
+ Display option value. Vim compatible *set <option>?* is also
+ supported.
+
+shell <command>
+ Execute a command via /bin/sh.
+
+showbind <context> <key>
+ Show key binding.
+
+shuffle
+ Reshuffle the shuffle lists for both library and playlist views.
+
+source <filename>
+ Read and execute commands from <filename>.
+
+toggle <option>
+ Toggle value of a toggle-able option (all booleans and tristate
+ *aaa_mode*).
+
+tqueue [NUM]
+ Queue NUM (default 1) random tracks from the library. See also
+ *lqueue*.
+
+unbind [-f] <context> <key>
+ Remove a key binding. Use tab to cycle through bound keys.
+
+ -f
+ Don't throw an error if the binding is not known
+
+unmark
+ Unmark all tracks (see *mark*).
+
+update-cache [-f]
+ Update track metadata cache (~/.cmus/cache). Only files with changed
+ modification time or removed files are considered.
+
+ -f
+ Update all files. Same as quit, rm -f ~/.cmus/cache, start cmus.
+
+view <name or 1-7>
+ Switches active view.
+
+vol [+-]NUM[%] [[+-]NUM[%]]
+ Set, increase or decrease volume.
+
+ If you give *vol* just one argument it changes both channels. Two
+ values make it possible to change the left and right channel
+ independently.
+
+ To increase or decrease volume prefix the value with *-* or *+*,
+ otherwise value is treated as absolute volume.
+
+ Both absolute and relative values can be given as percentage units
+ (suffixed with *%*) or as internal values (hardware may have volume in
+ range 0-31 for example).
+
+ Default bindings:
+
+ @pre
+ common = :vol +10%
+ common + :vol +10%
+ common - :vol -10%
+ common [ :vol +1% +0%
+ common ] :vol +0% +1%
+ common { :vol -1% -0%
+ common } :vol -0% -1%
+ @endpre
+
+win-activate (*enter*)
+ In views 1-3 start playing the selected track. In view 5 start
+ playing the selected track or change to the selected directory. In
+ view 6 activate the selected filters. In settings view (7) change
+ binding or variable.
+
+win-add-l (*a*)
+ Add the currently marked or selected track(s) (views 3-4), or the
+ currently selected file or directory (view 5) to the library.
+
+ Analogous to *:add -l*
+
+win-add-p (*y*)
+ Add the currently marked or selected track(s) (views 1-2, 4), or the
+ currently selected file or directory (view 5) to the playlist.
+
+ Analogous to *:add -p*
+
+win-add-Q (*E*)
+ Prepend the currently marked or selected track(s) (views 1-3), or the
+ currently selected file or directory (view 5) to the play queue.
+
+ Analogous to *:add -Q*
+
+win-add-q (*e*)
+ Add the currently marked or selected track(s) (views 1-3), or the
+ currently selected file or directory (view 5) to the play queue.
+
+ Analogous to *:add -q*
+
+win-bottom (*G*, *end*)
+ Goto bottom of the current window.
+
+win-down (*j*, *down*)
+ Goto down one row in the current window.
+
+win-mv-after (*p*)
+ If no tracks are marked, move the selected track down one row. If any
+ tracks are marked, move the marked tracks after the currently selected
+ track. This command works in unsorted playlist and play queue view.
+
+win-mv-before (*P*)
+ If no tracks are marked, move the selected track up one row. If any
+ tracks are marked, move the marked tracks before the currently
+ selected track. This command works in unsorted playlist and play
+ queue view.
+
+win-next (*tab*)
+ Activate next window. Only relevant in view 1.
+
+win-page-bottom
+ Goto the bottom of the visible part of the current window.
+
+win-page-down (*^F*, *page_down*)
+ Goto down one page in the current window.
+
+win-page-middle
+ Goto the middle of the visible part of the current window.
+
+win-page-top
+ Goto the top of the visible part of the current window.
+
+win-page-up (*^B*, *page_up*)
+ Goto up one page in the current window.
+
+win-remove (*D*, *delete*)
+ Remove the selected entry. For tracks no confirmations are made. For
+ files (view 5), filters (view 6) and bindings (view 7) user has to
+ confirm the action.
+
+win-sel-cur (*i*)
+ Select the current track (position in library or playlist, not
+ necessarily same as the currently playing track). Works only in views
+ 1-3, does nothing in other views.
+
+win-toggle (*space*)
+ Expand albums in library view (1), mark tracks in views 2-4, toggle
+ selection of a filter in view 6, toggle variable value in view 7.
+
+win-top (*g*, *home*)
+ Goto top of the current window.
+
+win-up (*k*, *up*)
+ Goto up one row in the current window.
+
+win-update (*u*)
+ Checks the modification time of the files in the library, and updates
+ metadata for changed files. Removes non-existent files from the
+ library.
+
+ Reloads contents of directory in the browser view.
+
+ Only works in views 1-2 and 5, does nothing in other views.
+
+win-update-cache [-f]
+ Same as *update-cache*, but only for marked / selected tracks.
+ Only works in views 1-2, does nothing in other views.
+
+
+@h1 CONFIGURATION OPTIONS
+
+This section describes cmus' options that can be altered with the *set* and
+*toggle* commands. Default values are in parenthesis, possible values in
+brackets.
+
+auto_reshuffle (true)
+ Reshuffle playlist when end of shuffle list is reached.
+
+aaa_mode (all) [all, artist, album]
+ Defines what tracks should be played in the library view. Not used in
+ the other views. For example if set to *artist* the player behaves
+ like there were only the files of the currently playing artist in the
+ library.
+
+altformat_current [`Format String`]
+ Alternative format string for the line displaying currently playing
+ track.
+
+altformat_playlist [`Format String`]
+ Alternative format string for the list views (2-4).
+
+altformat_title [`Format String`]
+ Alternative format string for terminal title. NOTE: not all
+ terminals support changing window title.
+
+altformat_trackwin [`Format String`]
+ Alternative format string for the tree view's (1) track window.
+
+buffer_seconds (10) [1-300]
+ Size of player buffer in seconds.
+
+color_cmdline_bg (default) [`Color`]
+ Command line background color.
+
+color_cmdline_fg (default) [`Color`]
+ Command line foreground color.
+
+color_cmdline_attr (default) [`Attributes`]
+ Command line attributes.
+
+color_error (lightred) [`Color`]
+ Color of error messages displayed on the command line.
+
+color_info (lightyellow) [`Color`]
+ Color of informational messages displayed on the command line.
+
+color_separator (blue) [`Color`]
+ Color of the separator line between windows in view (1).
+
+color_statusline_bg (gray) [`Color`]
+ Status line background color.
+
+color_statusline_fg (black) [`Color`]
+ Status line foreground color.
+
+color_statusline_attr (default) [`Attributes`]
+ Status line attributes.
+
+color_titleline_bg (blue) [`Color`]
+ Background color of the line displaying currently playing track.
+
+color_titleline_fg (white) [`Color`]
+ Foreground color of the line displaying currently playing track.
+
+color_titleline_attr (default) [`Attributes`]
+ Attributes of the line displaying currently playing track.
+
+color_win_bg (default) [`Color`]
+ Window background color.
+
+color_win_cur (lightyellow) [`Color`]
+ Color of currently playing track.
+
+color_win_cur_sel_bg (blue) [`Color`]
+ Background color of the selected row which is also the currently
+ playing track in active window.
+
+color_win_cur_sel_fg (lightyellow) [`Color`]
+ Foreground color of the selected row which is also the currently
+ playing track in active window.
+
+color_win_cur_sel_attr (default) [`Attributes`]
+ Attributes of the selected row which is also the currently
+ playing track in active window.
+
+color_win_dir (lightblue) [`Color`]
+ Color of directories in browser.
+
+color_win_fg (default) [`Color`]
+ Window foreground color.
+
+color_win_attr (default) [`Attributes`]
+ Window attributes.
+
+color_win_inactive_cur_sel_bg (gray) [`Color`]
+ Background color of the selected row which is also the currently
+ playing track in inactive window.
+
+color_win_inactive_cur_sel_fg (lightyellow) [`Color`]
+ Foreground color of the selected row which is also the currently
+ playing track in inactive window.
+
+color_win_inactive_cur_sel_attr (default) [`Attributes`]
+ Attributes of the selected row which is also the currently
+ playing track in inactive window.
+
+color_win_inactive_sel_bg (gray) [`Color`]
+ Background color of selected row in inactive window.
+
+color_win_inactive_sel_fg (black) [`Color`]
+ Foreground color of selected row in inactive window.
+
+color_win_inactive_sel_attr (default) [`Attributes`]
+ Attributes of selected row in inactive window.
+
+color_win_sel_bg (blue) [`Color`]
+ Background color of selected row in active window.
+
+color_win_sel_fg (white) [`Color`]
+ Foreground color of selected row in active window.
+
+color_win_sel_attr (default) [`Attributes`]
+ Attributes of selected row in active window.
+
+color_win_title_bg (blue) [`Color`]
+ Background color of window titles (topmost line of the screen).
+
+color_win_title_fg (white) [`Color`]
+ Foreground color of window titles (topmost line of the screen).
+
+color_win_title_attr (default) [`Attributes`]
+ Attributes of window titles (topmost line of the screen).
+
+confirm_run (true)
+ Ask for confirmation before executing *:run*
+
+continue (true)
+ Continue playing after current track finishes.
+
+device (/dev/cdrom)
+ CDDA device file.
+
+display_artist_sort_name (false)
+ If enabled, always displays artist names used for sorting instead of
+ regular ones in tree view (e.g. "Artist, The" instead of "The Artist"),
+ so that artists column looks alphabetically sorted.
+
+format_current [`Format String`]
+ Format string for the line displaying currently playing track.
+
+format_playlist [`Format String`]
+ Format string for the list views (2-4).
+
+format_playlist_va [`Format String`]
+ Format string for the list views (2-4), if a track is assumed to be a
+ part of compilation (see `LIBRARY VIEW SORTING` for details).
+
+format_title [`Format String`]
+ Format string for terminal title.
+
+ NOTE: not all terminals support changing window title.
+
+format_trackwin [`Format String`]
+ Format string for the tree view's (1) track window.
+
+format_trackwin_va [`Format String`]
+ Format string for the tree view's (1) track window, if a track
+ is assumed to be a part of compilation (see `LIBRARY VIEW SORTING`
+ for details).
+
+smart_artist_sort (true)
+ If enabled, makes tree view sorting ignore "The" in front of artist
+ names, preventing artists starting with "The" from clumping together.
+ Real `artistsort` tags override this option, when present.
+
+id3_default_charset (ISO-8859-1)
+ Default character set to use for ID3v1 and broken ID3v2 tags.
+
+ NOTE: This is used only if the tag is not valid UTF-8.
+
+icecast_default_charset (ISO-8859-1)
+ Default character set to use for non-UTF-8 icecast stream metadata.
+
+ NOTE: This is used only if the metadata is not valid UTF-8.
+
+lib_sort (artist album discnumber tracknumber title filename) [`Sort Keys`]
+ Sort keys for the sorted library view (2).
+
+output_plugin [roar, pulse, alsa, arts, oss, sun]
+ Name of output plugin.
+
+pl_sort () [`Sort Keys`]
+ Sort keys for the playlist view (3). Empty value disables sorting and
+ enables manually moving tracks.
+
+play_library (true)
+ Play tracks from the library instead of playlist.
+
+play_sorted (false)
+ Play tracks from the library in the sorted view (2) order instead of
+ tree view (1) order. Used only when play_library is true.
+
+repeat (false)
+ Repeat after all tracks played.
+
+repeat_current (false)
+ Repeat current track forever.
+
+replaygain (disabled)
+ Enable Replay Gain. Default is "disabled".
+ Set to "track", "album", "track-preferred" or "album-preferred".
+
+replaygain_limit (true)
+ Use replay gain limiting when clipping.
+
+replaygain_preamp (6.0)
+ Replay gain preamplification in decibels.
+
+resume (false)
+ Resume playback on startup.
+
+scroll_offset (2) [0-9999]
+ Minimal number of screen lines to keep above and below the cursor.
+
+show_hidden (false)
+ Display hidden files in browser.
+
+show_current_bitrate (false)
+ Display current bitrate in the status lines.
+
+show_remaining_time (false)
+ Display remaining time instead of elapsed time.
+
+shuffle (false)
+ Play in shuffled order. Shuffle works in the library views (1-2) and
+ playlist view (3).
+
+skip_track_info (false)
+ Don't load metadata when adding tracks. Useful when using network file system
+ and having huge amount of files.
+ Tags can be loaded using 'update-cache' or 'win-update-cache' commands.
+
+softvol (false)
+ Use software volume control.
+
+ NOTE: You should probably set this to false when using *ao* as
+ *output_plugin* to output to wav files.
+
+softvol_state (100 100)
+ Used to save left and right channel values for software volume
+ control. Two integers in range 0..100 separated by a space. This
+ option is not usually changed directly since *vol* command does same
+ thing if *softvol* is true.
+
+status_display_program () [command]
+ This command, if not empty, is run every time cmus' status changes.
+ It can be used to display currently playing track on desktop
+ background or panel for example. See
+ `/usr/share/doc/cmus/examples/cmus-status-display`.
+
+wrap_search (true)
+ Controls whether the search wraps around the end.
+
+@h2 Colors
+
+Color is integer in range -1..255.
+
+The following color names are recognized:
+
+Terminal's default color, -1
+ default
+
+Fg & bg, 0..7
+ black, red, green, yellow, blue, magenta, cyan, gray
+
+Fg, 8..15
+ darkgray, lightred, lightgreen, lightyellow, lightblue, lightmagenta,
+ lightcyan, white
+
+@h2 Attributes
+
+Attributes is a set of names "standout|bold":
+
+`default` does nothing, if you put it with other attributes
+the other attributes will be used.
+
+`standout` makes the text standout.
+
+`bold` makes the text bold.
+
+`reverse` reverses the text colors.
+
+`underline` underlines the text.
+
+`blink` makes the text blink.
+
+@h2 Format Strings
+
+Format strings control display of tracks in library, playlist and play queue
+views.
+
+NOTE: *altformat_\** options are used when there are no tags available.
+
+Special Keys:
+
+ %a %{artist} @br
+ %A %{albumartist} @br
+ %l %{album} @br
+ %D %{discnumber} @br
+ %n %{tracknumber} @br
+ %t %{title} @br
+ %g %{genre} @br
+ %c %{comment} @br
+ %y %{date} @br
+ %d %{duration} @br
+ %f %{path} @br
+ %F %{filename} @br
+ %{originaldate} @br
+ %{bitrate} @br
+ %{codec} @br
+ %{codec_profile} @br
+ %{rg_track_gain} @br
+ %{rg_track_peak} @br
+ %{rg_album_gain} @br
+ %{rg_album_peak} @br
+ %{arranger} @br
+ %{composer} @br
+ %{conductor} @br
+ %{lyricist} @br
+ %{performer} @br
+ %{remixer} @br
+ %{label} @br
+ %{publisher} @br
+ %{work} @br
+ %{opus} @br
+ %{partnumber} @br
+ %{part} @br
+ %{subtitle} @br
+ %{media} @br
+ %=
+ start align right (use at most once)
+ %%
+ literal *%*
+
+You can use printf style formatting (width, alignment, padding). As an
+extension, the width can have a %-suffix, to specify a percentage of the
+terminal width. @br
+To see current value of an option type *:set option=<TAB>*.
+
+Note: With %{bitrate}, you'll have to append the unit yourself, as mentioned
+in the example below.
+
+Examples:
+
+ @pre
+ :set format_trackwin= %02n. %t (%y)%= %d
+ :set format_current= %n. %-30t %40F (%y)%= %d
+ :set format_current= %a - %l - %02n. %t%= %{bitrate}Kbps %g %y
+ :set format_playlist= %f%= %6{rg_track_gain} dB %8{rg_track_peak}
+ :set format_playlist= %-25%a %-15%l %3n. %t%= %y %d
+ @endpre
+
+@h2 Sort Keys
+
+Sort option (lib_sort, pl_sort) value is space separated list of the following
+sort keys:
+
+ artist, album, title, tracknumber, discnumber, date, originaldate,
+ genre, comment, albumartist, filename, filemtime, bitrate, codec,
+ media, codec_profile, rg_track_gain, rg_track_peak, rg_album_gain,
+ rg_album_peak
+
+
+@h1 PLUGIN OPTIONS
+
+dsp.alsa.device
+ PCM device for ALSA plugin, usually "default".
+
+mixer.alsa.channel
+ Mixer channel for ALSA Plugin, usually "pcm", "master" or "headphone".
+ To see all possible values run "alsamixer" or "amixer".
+
+mixer.alsa.device
+ Mixer device for ALSA plugin, usually "default".
+
+mixer.pulse.restore_volume
+ Restore the volume at startup using PulseAudio. Otherwise, cmus sets
+ the volume to 100%, which does not mix well with "flat volumes"
+ feature of PA. Defaults to "1"; set to "0" to turn off.
+
+dsp.ao.buffer_size
+ Buffer size, default is 16kB (but you may want to try bigger values if
+ you experience buffer under-runs).
+
+dsp.ao.driver
+ Output driver for libao plugin. Example values: "alsa09", "esd",
+ "irix", "oss", "sun", "aixs", "wav".
+
+ NOTE: of the file output drivers only "wav" is supported.
+
+dsp.ao.wav_counter
+ Counter used for making filename. Used only if *dsp.ao.driver* is
+ "wav". For example if this is 1 and *dsp.ao.wav_dir* is "/home/user"
+ then PCM data is outputted to "/home/user/01.wav". This counter is
+ incremented every time playback is stopped.
+
+ NOTE: you probably want to set *continue* to *false* (press *C*),
+ otherwise playback is not stopped between tracks and all PCM data is
+ outputted to one wav file (useful if you want to join files). Also
+ unsetting shuffle and repeat might be good idea.
+
+dsp.ao.wav_dir
+ Output directory for libao plugin, default is your home directory.
+ Used only if *dsp.ao.driver* is "wav".
+
+input.cdio.cddb_url
+ CDDB URL (default: freedb.freedb.org:8880). Use HTTP protocol if prefixed
+ with "http://" (e.g.: http://freedb.musicbrainz.org:80/~cddb/cddb.cgi).
+ Set to an empty string to disable CDDB lookup completely.
+
+dsp.oss.device
+ PCM device for OSS plugin, usually /dev/dsp.
+
+mixer.oss.channel
+ Mixer channel for OSS Plugin, "pcm" or "master".
+
+mixer.oss.device
+ Mixer device for OSS plugin, usually /dev/mixer.
+
+dsp.roar.server
+ Address of RoarAudio server. Defaults to internal defaults.
+ Can be UNIX, TCP/IP or DECnet address.
+
+dsp.roar.role [music, background_music, ...]
+ Role for stream. May be used by the server to apply additional
+ defaults.
+
+dsp.sun.device
+ PCM device for Sun plugin, usually /dev/audio.
+
+mixer.sun.channel
+ Mixer channel for Sun Plugin, usually "master".
+
+mixer.sun.device
+ Mixer device for Sun plugin, usually /dev/mixer.
+
+@h1 PLAYING AUDIO DISCS
+
+With the cdio input plugin enabled, it is possible to play Audio CDs and CD
+images. Just set the *device* option to either a device file (e.g. /dev/cdrom)
+or an image file (e.g. ~/cd.cue). Then add a new track using the CDDA URL
+scheme, e.g.:
+
+ :add cdda://2
+
+To add the whole disc, use cdda:// (without track number). This is currently
+only working for audio discs, not images. Adding track ranges is also possible
+(cdda://1-3). To add images without changing the device option, it is possible
+to include the image path in the URL, e.g.:
+
+ :add cdda:///path/to/cd.cue/2-5
+
+The metadata will be read from CD-Text, and if not available, looked up from
+a CDDB server (see *input.cdio.cddb_url*).
+
+
+@h1 FILTERS
+
+Filters are used mostly for filtering contents of library views (1 & 2).
+Filters do not change the actual library content, i.e. *:save* command will
+still save all tracks to playlist file whether they are visible or not.
+
+@h2 Types
+
+There are three types of filter expressions, each offering more
+expressiveness:
+
+ @li *simple*
+ `e.g.` beatles
+
+ @li *short*
+ `e.g.` ~a beatles (!~y1960-1965 | ~d>600)
+
+ @li *long*
+ `e.g.` artist="\*beatles\*"&album="R\*"
+
+Simple expressions are only available using *live-filter*. For other
+filter commands the type is auto-detected, so both short and long
+expressions can be used.
+
+Long expressions are lists of built-in filters or user defined filters
+separated with *&* (and) or *|* (or). Parenthesis can be used group
+subexpressions and *!* negates result of the expression following it.
+Same is true for short expressions, but they can only be made of
+built-in filters. Also (and)-grouping is done implicitly.
+
+@h2 Strings
+
+@li long
+*filename*, *artist*, *albumartist*, *album*, *title*, *genre*, *comment*,
+*codec*, *codec_profile*, *media*
+@br
+Comparators: *=* and *!=* (not equal)
+
+@li short
+*~f*, *~a*, *~A*, *~l*, *~t*, *~g*, *~c*
+@br
+Comparators: none
+
+@h2 Integers
+
+@li long
+*discnumber*, *tracknumber*, *date* (year), *originaldate* (year), *duration*
+(seconds), *bitrate*
+@br
+Comparators: *<*, *<=*, *=*, *>=*, *>*, *!=*
+
+@li short
+*~D*, *~n*, *~y*, *~d*
+@br
+Comparators: *<*, *>*
+@br
+Ranges: *a-b* (>=a&<=b), *-b* (<=b), *a-* (>=a)
+
+@h2 Booleans
+
+*tag* (true if track has tags), *stream* (true if track is a stream)
+@br
+For short expressions: *~T* and *~s*
+
+@h2 Defining Filters
+
+Filters can be defined with *fset* command. User defined filters appear in
+the filters view (6).
+
+Create a new filter which name is *ogg* and value *filename="\*.ogg"*
+ :fset ogg=filename="\*.ogg"
+
+Filter ogg and mp3 files from the 90s. Note the use of user defined filter
+*ogg*
+ :fset 90s-ogg-mp3=date>=1990&date<2000&(ogg|filename="\*.mp3")
+
+@h2 Activating Filters
+
+*factivate* changes visible contents of the library (views 1-2).
+
+Activate user defined filters *ogg* and *missing-tags*
+ :factivate ogg missing-tags
+
+Like above but negate value of *ogg* filter.
+ :factivate !ogg missing-tags
+
+Alternatively you can select the filters by pressing *space* in view 6 and
+then activate the selected filters by pressing *enter*.
+
+@h2 Throw-away Filters
+
+*live-filter* and *filter* commands are useful when you want to use a filter only
+once and not save it. It changes visible contents of the library (views 1-2).
+*filter* unactivates all filters in the filters view, while *live-filter* is
+applied in addition to all currently activated filters.
+
+Filter all rock (anything with *rock* in genre tag) music from 80s-
+ :filter date>=1980&genre="\*rock\*"
+ @br
+ :filter ~y1980-~grock
+
+Filter all artists/albums/titles containing "sleepwalking"
+ :live-filter sleepwalking
+
+@h2 Selecting Tracks Matching a Filter
+
+Mark (select) all tracks with duration less than 2 minutes
+ :mark duration<120
+
+This command works in views 2-4.
+
+
+@h1 FILES
+
+cmus reads its configuration from 3 different places.
+
+`~/.cmus/autosave`
+ This is the first file cmus loads. cmus saves its state on exit to
+ this file so you shouldn't edit it.
+
+`/usr/share/cmus/rc`
+ If the autosave file didn't exist, this file is read instead.
+
+`~/.cmus/rc`
+ Static config file. This file is read immediately after the autosave
+ file, and is never modified by cmus. You can override auto-saved
+ settings in this file. This file is not limited to options, it can
+ contain other commands too.
+
+@h2 Color Schemes
+
+There are some color schemes (\*.theme) in `/usr/share/cmus`. You can switch
+them using the *:colorscheme* command. You can put your own color schemes to
+~/.cmus.
+
+@h2 Examples
+
+Example status display script (See *status_display_program* option) can be
+found in `/usr/share/doc/cmus/examples`.
+
+
+@h1 ENVIRONMENT
+
+CMUS_CHARSET
+ Override cmus character set (default: \`locale charmap\`).
+
+CMUS_HOME
+ Override cmus config directory (default: *$HOME*/.cmus).
+
+HOME
+ Full path of the user's home directory.
+
+http_proxy
+ URI of the proxy to use for HTTP requests.
+
+USER
+ Name of the user running cmus.
+
+USERNAME
+ Fallback for *USER*.
+
+
+@h1 BUGS
+
+After a crash last lines of `~/cmus-debug.txt` might
+contain useful information. The file exists only if you configured cmus with
+maximum debug level (*./configure DEBUG=2*).
+
+Feature requests and bug reports should go to the cmus-devel mailing list:
+ http://lists.sourceforge.net/lists/listinfo/cmus-devel
+
+
+@h1 SEE ALSO
+
+*cmus-tutorial*(7), *cmus-remote*(1)
+
+
+@h1 AUTHORS
+
+cmus was mainly written by Timo Hirvonen <tihirvon\@gmail.com>. Other
+contributers are listed in the `AUTHORS` file.
+
+This man page was written by Frank Terbeck <ft\@bewatermyfriend.org>,
+Timo Hirvonen <tihirvon\@gmail.com>, and Clay Barnes <clay.barnes\@gmail.com>.
--- /dev/null
+/*
+ * ttman - text to man converter
+ *
+ * Copyright 2006 Timo Hirvonen <tihirvon@gmail.com>
+ *
+ * This file is licensed under the GPLv2.
+ */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+struct token {
+ struct token *next;
+ struct token *prev;
+ enum {
+ TOK_TEXT, // max one line w/o \n
+ TOK_NL, // \n
+ TOK_ITALIC, // `
+ TOK_BOLD, // *
+ TOK_INDENT, // \t
+
+ // keywords (@...)
+ TOK_H1,
+ TOK_H2,
+ TOK_LI,
+ TOK_BR,
+ TOK_PRE,
+ TOK_ENDPRE, // must be after TOK_PRE
+ TOK_RAW,
+ TOK_ENDRAW, // must be after TOK_RAW
+ TOK_TITLE, // WRITE 2 2001-12-13 "Linux 2.0.32" "Linux Programmer's Manual"
+ } type;
+ int line;
+
+ // not NUL-terminated
+ const char *text;
+ // length of text
+ int len;
+};
+
+static const char *program;
+static const char *filename;
+static char tmp_file[1024];
+static FILE *outfile;
+static int cur_line = 1;
+static struct token head = { &head, &head, TOK_TEXT, 0, NULL, 0 };
+
+#define CONST_STR(str) { str, sizeof(str) - 1 }
+static const struct {
+ const char *str;
+ int len;
+} token_names[] = {
+ CONST_STR("text"),
+ CONST_STR("nl"),
+ CONST_STR("italic"),
+ CONST_STR("bold"),
+ CONST_STR("indent"),
+
+ // keywords
+ CONST_STR("h1"),
+ CONST_STR("h2"),
+ CONST_STR("li"),
+ CONST_STR("br"),
+ CONST_STR("pre"),
+ CONST_STR("endpre"),
+ CONST_STR("raw"),
+ CONST_STR("endraw"),
+ CONST_STR("title")
+};
+#define NR_TOKEN_NAMES (sizeof(token_names) / sizeof(token_names[0]))
+#define BUG() die("BUG in %s\n", __FUNCTION__)
+
+#ifdef __GNUC__
+#define __NORETURN __attribute__((__noreturn__))
+#else
+#define __NORETURN
+#endif
+
+static __NORETURN void quit(void)
+{
+ if (tmp_file[0])
+ unlink(tmp_file);
+ exit(1);
+}
+
+static __NORETURN void die(const char *format, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s: ", program);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ quit();
+}
+
+static __NORETURN void syntax(int line, const char *format, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s:%d: error: ", filename, line);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ quit();
+}
+
+static inline const char *keyword_name(int type)
+{
+ if (type < TOK_H1 || type > TOK_TITLE)
+ die("BUG: no keyword name for type %d\n", type);
+ return token_names[type].str;
+}
+
+static void *xmalloc(size_t size)
+{
+ void *ret = malloc(size);
+
+ if (!ret)
+ die("OOM when allocating %ul bytes\n", size);
+ return ret;
+}
+
+static char *memdup(const char *str, int len)
+{
+ char *s = xmalloc(len + 1);
+ memcpy(s, str, len);
+ s[len] = 0;
+ return s;
+}
+
+static struct token *new_token(int type)
+{
+ struct token *tok = xmalloc(sizeof(struct token));
+
+ tok->prev = NULL;
+ tok->next = NULL;
+ tok->type = type;
+ tok->line = cur_line;
+ return tok;
+}
+
+static void free_token(struct token *tok)
+{
+ struct token *prev = tok->prev;
+ struct token *next = tok->next;
+
+ if (tok == &head)
+ BUG();
+
+ prev->next = next;
+ next->prev = prev;
+ free(tok);
+}
+
+static void emit_token(struct token *tok)
+{
+ tok->prev = head.prev;
+ tok->next = &head;
+ head.prev->next = tok;
+ head.prev = tok;
+}
+
+static void emit(int type)
+{
+ struct token *tok = new_token(type);
+ tok->len = 0;
+ tok->text = NULL;
+ emit_token(tok);
+}
+
+static int emit_keyword(const char *buf, int size)
+{
+ int i, len;
+
+ for (len = 0; len < size; len++) {
+ if (!isalnum((unsigned char)buf[len]))
+ break;
+ }
+
+ if (!len)
+ syntax(cur_line, "keyword expected\n");
+
+ for (i = TOK_H1; i < NR_TOKEN_NAMES; i++) {
+ if (len != token_names[i].len)
+ continue;
+ if (!strncmp(buf, token_names[i].str, len)) {
+ emit(i);
+ return len;
+ }
+ }
+ syntax(cur_line, "invalid keyword '@%s'\n", memdup(buf, len));
+}
+
+static int emit_text(const char *buf, int size)
+{
+ struct token *tok;
+ int i;
+
+ for (i = 0; i < size; i++) {
+ int c = buf[i];
+ if (c == '@' || c == '`' || c == '*' || c == '\n' || c == '\\' || c == '\t')
+ break;
+ }
+ tok = new_token(TOK_TEXT);
+ tok->text = buf;
+ tok->len = i;
+ emit_token(tok);
+ return i;
+}
+
+static void tokenize(const char *buf, int size)
+{
+ int pos = 0;
+
+ while (pos < size) {
+ struct token *tok;
+ int ch;
+
+ ch = buf[pos++];
+ switch (ch) {
+ case '@':
+ pos += emit_keyword(buf + pos, size - pos);
+ break;
+ case '`':
+ emit(TOK_ITALIC);
+ break;
+ case '*':
+ emit(TOK_BOLD);
+ break;
+ case '\n':
+ emit(TOK_NL);
+ cur_line++;
+ break;
+ case '\t':
+ emit(TOK_INDENT);
+ break;
+ case '\\':
+ tok = new_token(TOK_TEXT);
+ tok->text = buf + pos;
+ tok->len = 1;
+ pos++;
+ if (pos == size || buf[pos] == '\n') {
+ // just one '\\'
+ tok->text--;
+ }
+
+ if (tok->text[0] == '\\') {
+ tok->text = "\\\\";
+ tok->len = 2;
+ }
+
+ emit_token(tok);
+ break;
+ default:
+ pos--;
+ pos += emit_text(buf + pos, size - pos);
+ break;
+ }
+ }
+}
+
+static int is_empty_line(const struct token *tok)
+{
+ while (tok != &head) {
+ int i;
+
+ switch (tok->type) {
+ case TOK_TEXT:
+ for (i = 0; i < tok->len; i++) {
+ if (tok->text[i] != ' ')
+ return 0;
+ }
+ break;
+ case TOK_INDENT:
+ break;
+ case TOK_NL:
+ return 1;
+ default:
+ return 0;
+ }
+ tok = tok->next;
+ }
+ return 1;
+}
+
+static struct token *remove_line(struct token *tok)
+{
+ while (tok != &head) {
+ struct token *next = tok->next;
+ int type = tok->type;
+
+ free_token(tok);
+ tok = next;
+ if (type == TOK_NL)
+ break;
+ }
+ return tok;
+}
+
+static struct token *skip_after(struct token *tok, int type)
+{
+ struct token *save = tok;
+
+ while (tok != &head) {
+ if (tok->type == type) {
+ tok = tok->next;
+ if (tok->type != TOK_NL)
+ syntax(tok->line, "newline expected after @%s\n",
+ keyword_name(type));
+ return tok->next;
+ }
+ if (tok->type >= TOK_H1)
+ syntax(tok->line, "keywords not allowed betweed @%s and @%s\n",
+ keyword_name(type-1), keyword_name(type));
+ tok = tok->next;
+ }
+ syntax(save->prev->line, "missing @%s\n", keyword_name(type));
+}
+
+static struct token *get_next_line(struct token *tok)
+{
+ while (tok != &head) {
+ int type = tok->type;
+
+ tok = tok->next;
+ if (type == TOK_NL)
+ break;
+ }
+ return tok;
+}
+
+static struct token *get_indent(struct token *tok, int *ip)
+{
+ int i = 0;
+
+ while (tok != &head && tok->type == TOK_INDENT) {
+ tok = tok->next;
+ i++;
+ }
+ *ip = i;
+ return tok;
+}
+
+// line must be non-empty
+static struct token *check_line(struct token *tok, int *ip)
+{
+ struct token *start;
+ int tok_type;
+
+ start = tok = get_indent(tok, ip);
+
+ tok_type = tok->type;
+ switch (tok_type) {
+ case TOK_TEXT:
+ case TOK_BOLD:
+ case TOK_ITALIC:
+ case TOK_BR:
+ tok = tok->next;
+ while (tok != &head) {
+ switch (tok->type) {
+ case TOK_TEXT:
+ case TOK_BOLD:
+ case TOK_ITALIC:
+ case TOK_BR:
+ case TOK_INDENT:
+ break;
+ case TOK_NL:
+ return start;
+ default:
+ syntax(tok->line, "@%s not allowed inside paragraph\n",
+ keyword_name(tok->type));
+ }
+ tok = tok->next;
+ }
+ break;
+ case TOK_H1:
+ case TOK_H2:
+ case TOK_TITLE:
+ if (*ip)
+ goto indentation;
+
+ // check arguments
+ tok = tok->next;
+ while (tok != &head) {
+ switch (tok->type) {
+ case TOK_TEXT:
+ case TOK_INDENT:
+ break;
+ case TOK_NL:
+ return start;
+ default:
+ syntax(tok->line, "@%s can contain only text\n",
+ keyword_name(tok_type));
+ }
+ tok = tok->next;
+ }
+ break;
+ case TOK_LI:
+ // check arguments
+ tok = tok->next;
+ while (tok != &head) {
+ switch (tok->type) {
+ case TOK_TEXT:
+ case TOK_BOLD:
+ case TOK_ITALIC:
+ case TOK_INDENT:
+ break;
+ case TOK_NL:
+ return start;
+ default:
+ syntax(tok->line, "@%s not allowed inside @li\n",
+ keyword_name(tok->type));
+ }
+ tok = tok->next;
+ }
+ break;
+ case TOK_PRE:
+ // checked later
+ break;
+ case TOK_RAW:
+ if (*ip)
+ goto indentation;
+ // checked later
+ break;
+ case TOK_ENDPRE:
+ case TOK_ENDRAW:
+ syntax(tok->line, "@%s not expected\n", keyword_name(tok->type));
+ break;
+ case TOK_NL:
+ case TOK_INDENT:
+ BUG();
+ break;
+ }
+ return start;
+indentation:
+ syntax(tok->line, "indentation before @%s\n", keyword_name(tok->type));
+}
+
+static void insert_nl_before(struct token *next)
+{
+ struct token *prev = next->prev;
+ struct token *new = new_token(TOK_NL);
+
+ new->prev = prev;
+ new->next = next;
+ prev->next = new;
+ next->prev = new;
+}
+
+static void normalize(void)
+{
+ struct token *tok = head.next;
+ /*
+ * >= 0 if previous line was text (== amount of indent)
+ * -1 if previous block was @pre (amount of indent doesn't matter)
+ * -2 otherwise (@h1 etc., indent was 0)
+ */
+ int prev_indent = -2;
+
+ while (tok != &head) {
+ struct token *start;
+ int i, new_para = 0;
+
+ // remove empty lines
+ while (is_empty_line(tok)) {
+ tok = remove_line(tok);
+ new_para = 1;
+ if (tok == &head)
+ return;
+ }
+
+ // skips indent
+ start = tok;
+ tok = check_line(tok, &i);
+
+ switch (tok->type) {
+ case TOK_TEXT:
+ case TOK_ITALIC:
+ case TOK_BOLD:
+ case TOK_BR:
+ // normal text
+ if (new_para && prev_indent >= -1) {
+ // previous line/block was text or @pre
+ // and there was a empty line after it
+ insert_nl_before(start);
+ }
+
+ if (!new_para && prev_indent == i) {
+ // join with previous line
+ struct token *nl = start->prev;
+
+ if (nl->type != TOK_NL)
+ BUG();
+
+ if ((nl->prev != &head && nl->prev->type == TOK_BR) ||
+ tok->type == TOK_BR) {
+ // don't convert \n after/before @br to ' '
+ free_token(nl);
+ } else {
+ // convert "\n" to " "
+ nl->type = TOK_TEXT;
+ nl->text = " ";
+ nl->len = 1;
+ }
+
+ // remove indent
+ while (start->type == TOK_INDENT) {
+ struct token *next = start->next;
+ free_token(start);
+ start = next;
+ }
+ }
+
+ prev_indent = i;
+ tok = get_next_line(tok);
+ break;
+ case TOK_PRE:
+ case TOK_RAW:
+ // these can be directly after normal text
+ // but not joined with the previous line
+ if (new_para && prev_indent >= -1) {
+ // previous line/block was text or @pre
+ // and there was a empty line after it
+ insert_nl_before(start);
+ }
+ tok = skip_after(tok->next, tok->type + 1);
+ prev_indent = -1;
+ break;
+ case TOK_H1:
+ case TOK_H2:
+ case TOK_LI:
+ case TOK_TITLE:
+ // remove white space after H1, H2, L1 and TITLE
+ tok = tok->next;
+ while (tok != &head) {
+ int type = tok->type;
+ struct token *next;
+
+ if (type == TOK_TEXT) {
+ while (tok->len && *tok->text == ' ') {
+ tok->text++;
+ tok->len--;
+ }
+ if (tok->len)
+ break;
+ }
+ if (type != TOK_INDENT)
+ break;
+
+ // empty TOK_TEXT or TOK_INDENT
+ next = tok->next;
+ free_token(tok);
+ tok = next;
+ }
+ // not normal text. can't be joined
+ prev_indent = -2;
+ tok = get_next_line(tok);
+ break;
+ case TOK_NL:
+ case TOK_INDENT:
+ case TOK_ENDPRE:
+ case TOK_ENDRAW:
+ BUG();
+ break;
+ }
+ }
+}
+
+#define output(...) fprintf(outfile, __VA_ARGS__)
+
+static void output_buf(const char *buf, int len)
+{
+ fwrite(buf, 1, len, outfile);
+}
+
+static void output_text(struct token *tok)
+{
+ char buf[1024];
+ const char *str = tok->text;
+ int len = tok->len;
+ int pos = 0;
+
+ while (len) {
+ int c = *str++;
+
+ if (pos >= sizeof(buf) - 1) {
+ output_buf(buf, pos);
+ pos = 0;
+ }
+ if (c == '-')
+ buf[pos++] = '\\';
+ buf[pos++] = c;
+ len--;
+ }
+
+ if (pos)
+ output_buf(buf, pos);
+}
+
+static int bold = 0;
+static int italic = 0;
+static int indent = 0;
+
+static struct token *output_pre(struct token *tok)
+{
+ int bol = 1;
+
+ if (tok->type != TOK_NL)
+ syntax(tok->line, "newline expected after @pre\n");
+
+ output(".nf\n");
+ tok = tok->next;
+ while (tok != &head) {
+ if (bol) {
+ int i;
+
+ tok = get_indent(tok, &i);
+ if (i != indent && tok->type != TOK_NL)
+ syntax(tok->line, "indent changed in @pre\n");
+ }
+
+ switch (tok->type) {
+ case TOK_TEXT:
+ if (bol && tok->len && tok->text[0] == '.')
+ output("\\&");
+ output_text(tok);
+ break;
+ case TOK_NL:
+ output("\n");
+ bol = 1;
+ tok = tok->next;
+ continue;
+ case TOK_ITALIC:
+ output("`");
+ break;
+ case TOK_BOLD:
+ output("*");
+ break;
+ case TOK_INDENT:
+ // FIXME: warn
+ output(" ");
+ break;
+ case TOK_ENDPRE:
+ output(".fi\n");
+ tok = tok->next;
+ if (tok != &head && tok->type == TOK_NL)
+ tok = tok->next;
+ return tok;
+ default:
+ BUG();
+ break;
+ }
+ bol = 0;
+ tok = tok->next;
+ }
+ return tok;
+}
+
+static struct token *output_raw(struct token *tok)
+{
+ if (tok->type != TOK_NL)
+ syntax(tok->line, "newline expected after @raw\n");
+
+ tok = tok->next;
+ while (tok != &head) {
+ switch (tok->type) {
+ case TOK_TEXT:
+ if (tok->len == 2 && !strncmp(tok->text, "\\\\", 2)) {
+ /* ugly special case
+ * "\\" (\) was converted to "\\\\" (\\) because
+ * nroff does escaping too.
+ */
+ output("\\");
+ } else {
+ output_buf(tok->text, tok->len);
+ }
+ break;
+ case TOK_NL:
+ output("\n");
+ break;
+ case TOK_ITALIC:
+ output("`");
+ break;
+ case TOK_BOLD:
+ output("*");
+ break;
+ case TOK_INDENT:
+ output("\t");
+ break;
+ case TOK_ENDRAW:
+ tok = tok->next;
+ if (tok != &head && tok->type == TOK_NL)
+ tok = tok->next;
+ return tok;
+ default:
+ BUG();
+ break;
+ }
+ tok = tok->next;
+ }
+ return tok;
+}
+
+static struct token *output_para(struct token *tok)
+{
+ int bol = 1;
+
+ while (tok != &head) {
+ switch (tok->type) {
+ case TOK_TEXT:
+ output_text(tok);
+ break;
+ case TOK_ITALIC:
+ italic ^= 1;
+ if (italic) {
+ output("\\fI");
+ } else {
+ output("\\fR");
+ }
+ break;
+ case TOK_BOLD:
+ bold ^= 1;
+ if (bold) {
+ output("\\fB");
+ } else {
+ output("\\fR");
+ }
+ break;
+ case TOK_BR:
+ if (bol) {
+ output(".br\n");
+ } else {
+ output("\n.br\n");
+ }
+ bol = 1;
+ tok = tok->next;
+ continue;
+ case TOK_NL:
+ output("\n");
+ return tok->next;
+ case TOK_INDENT:
+ output(" ");
+ break;
+ default:
+ BUG();
+ break;
+ }
+ bol = 0;
+ tok = tok->next;
+ }
+ return tok;
+}
+
+static struct token *title(struct token *tok, const char *cmd)
+{
+ output("%s", cmd);
+ return output_para(tok->next);
+}
+
+static struct token *dump_one(struct token *tok)
+{
+ int i;
+
+ tok = get_indent(tok, &i);
+ if (tok->type != TOK_RAW) {
+ while (indent < i) {
+ output(".RS\n");
+ indent++;
+ }
+ while (indent > i) {
+ output(".RE\n");
+ indent--;
+ }
+ }
+
+ switch (tok->type) {
+ case TOK_TEXT:
+ case TOK_ITALIC:
+ case TOK_BOLD:
+ case TOK_BR:
+ if (tok->type == TOK_TEXT && tok->len && tok->text[0] == '.')
+ output("\\&");
+ tok = output_para(tok);
+ break;
+ case TOK_H1:
+ tok = title(tok, ".SH ");
+ break;
+ case TOK_H2:
+ tok = title(tok, ".SS ");
+ break;
+ case TOK_LI:
+ tok = title(tok, ".TP\n");
+ break;
+ case TOK_PRE:
+ tok = output_pre(tok->next);
+ break;
+ case TOK_RAW:
+ tok = output_raw(tok->next);
+ break;
+ case TOK_TITLE:
+ tok = title(tok, ".TH ");
+ // must be after .TH
+ // no hyphenation, adjust left
+ output(".nh\n.ad l\n");
+ break;
+ case TOK_NL:
+ output("\n");
+ tok = tok->next;
+ break;
+ case TOK_ENDPRE:
+ case TOK_ENDRAW:
+ case TOK_INDENT:
+ BUG();
+ break;
+ }
+ return tok;
+}
+
+static void dump(void)
+{
+ struct token *tok = head.next;
+
+ while (tok != &head)
+ tok = dump_one(tok);
+}
+
+static void process(void)
+{
+ struct stat s;
+ const char *buf;
+ int fd;
+
+ fd = open(filename, O_RDONLY);
+ if (fd == -1)
+ die("opening `%s' for reading: %s\n", filename, strerror(errno));
+ fstat(fd, &s);
+ if (s.st_size) {
+ buf = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (buf == MAP_FAILED)
+ die("mmap: %s\n", strerror(errno));
+
+ tokenize(buf, s.st_size);
+ normalize();
+ }
+ dump();
+}
+
+int main(int argc, char *argv[])
+{
+ const char *dest;
+ int fd;
+
+ program = argv[0];
+ if (argc != 3) {
+ fprintf(stderr, "Usage: %s <in> <out>\n", program);
+ return 1;
+ }
+ filename = argv[1];
+ dest = argv[2];
+
+ snprintf(tmp_file, sizeof(tmp_file), "%s.XXXXXX", dest);
+ fd = mkstemp(tmp_file);
+ if (fd < 0)
+ die("creating %s: %s\n", tmp_file, strerror(errno));
+ outfile = fdopen(fd, "w");
+ if (!outfile)
+ die("opening %s: %s\n", tmp_file, strerror(errno));
+
+ process();
+ if (rename(tmp_file, dest))
+ die("renaming %s to %s: %s\n", tmp_file, dest, strerror(errno));
+ return 0;
+}
--- /dev/null
+REV = HEAD
+
+# version from an annotated tag
+_ver0 = $(shell git describe $(REV) 2>/dev/null)
+# version from a plain tag
+_ver1 = $(shell git describe --tags $(REV) 2>/dev/null)
+# SHA1
+_ver2 = $(shell git rev-parse --verify --short $(REV) 2>/dev/null)
+# hand-made
+_ver3 = v2.5.0
+
+VERSION = $(or $(_ver0),$(_ver1),$(_ver2),$(_ver3))
+
+all: main plugins man
+
+include config.mk
+include scripts/lib.mk
+
+CFLAGS += -D_FILE_OFFSET_BITS=64
+
+CMUS_LIBS = $(PTHREAD_LIBS) $(NCURSES_LIBS) $(ICONV_LIBS) $(DL_LIBS) $(DISCID_LIBS) $(CUE_LIBS) -lm $(COMPAT_LIBS)
+
+input.o main.o ui_curses.o pulse.lo: .version
+input.o main.o ui_curses.o pulse.lo: CFLAGS += -DVERSION=\"$(VERSION)\"
+main.o server.o: CFLAGS += -DDEFAULT_PORT=3000
+discid.o: CFLAGS += $(DISCID_CFLAGS)
+job.o cue_utils.o: CFLAGS += $(CUE_CFLAGS)
+
+.version: Makefile
+ @test "`cat $@ 2> /dev/null`" = "$(VERSION)" && exit 0; \
+ echo " GEN $@"; echo $(VERSION) > $@
+
+# programs {{{
+cmus-y := \
+ ape.o browser.o buffer.o cache.o cmdline.o cmus.o command_mode.o comment.o \
+ channelmap.o convert.lo debug.o discid.o editable.o expr.o filters.o \
+ format_print.o gbuf.o glob.o help.o history.o http.o id3.o input.o job.o \
+ keys.o keyval.o lib.o load_dir.o locking.o mergesort.o misc.o options.o \
+ output.o pcm.o pl.o play_queue.o player.o \
+ rbtree.o read_wrapper.o server.o search.o \
+ search_mode.o spawn.o tabexp.o tabexp_file.o \
+ track.o track_info.o tree.o u_collate.o uchar.o ui_curses.o \
+ window.o worker.o xstrjoin.o
+
+cmus-$(CONFIG_CUE) += cue_utils.o
+
+$(cmus-y): CFLAGS += $(PTHREAD_CFLAGS) $(NCURSES_CFLAGS) $(ICONV_CFLAGS) $(DL_CFLAGS)
+
+cmus: $(cmus-y) file.o path.o prog.o xmalloc.o
+ $(call cmd,ld,$(CMUS_LIBS))
+
+cmus-remote: main.o file.o path.o prog.o xmalloc.o
+ $(call cmd,ld,$(COMPAT_LIBS))
+
+# cygwin compat
+DLLTOOL=dlltool
+
+libcmus-$(CONFIG_CYGWIN) := libcmus.a
+
+libcmus.a: $(cmus-y) file.o path.o prog.o xmalloc.o
+ $(LD) -shared -o cmus.exe -Wl,--out-implib=libcmus.a -Wl,--base-file,cmus.base \
+ -Wl,--export-all-symbols -Wl,--no-whole-archive $^ $(CMUS_LIBS)
+ $(DLLTOOL) --output-def cmus.def --dllname cmus.exe --export-all-symbols $^
+ $(DLLTOOL) --base-file cmus.base --dllname cmus.exe --input-def cmus.def --output-exp cmus.exp
+ $(LD) -o cmus.exe -Wl,cmus.exp $^ $(CMUS_LIBS)
+
+# }}}
+
+# input plugins {{{
+cdio-objs := cdio.lo
+flac-objs := flac.lo
+mad-objs := mad.lo nomad.lo
+mikmod-objs := mikmod.lo
+modplug-objs := modplug.lo
+mpc-objs := mpc.lo
+vorbis-objs := vorbis.lo
+wavpack-objs := wavpack.lo
+wav-objs := wav.lo
+mp4-objs := mp4.lo
+aac-objs := aac.lo
+ffmpeg-objs := ffmpeg.lo
+cue-objs := cue.lo
+
+ip-$(CONFIG_CDIO) += cdio.so
+ip-$(CONFIG_FLAC) += flac.so
+ip-$(CONFIG_MAD) += mad.so
+ip-$(CONFIG_MIKMOD) += mikmod.so
+ip-$(CONFIG_MODPLUG) += modplug.so
+ip-$(CONFIG_MPC) += mpc.so
+ip-$(CONFIG_VORBIS) += vorbis.so
+ip-$(CONFIG_WAVPACK) += wavpack.so
+ip-$(CONFIG_WAV) += wav.so
+ip-$(CONFIG_MP4) += mp4.so
+ip-$(CONFIG_AAC) += aac.so
+ip-$(CONFIG_FFMPEG) += ffmpeg.so
+ip-$(CONFIG_CUE) += cue.so
+
+$(cdio-objs): CFLAGS += $(CDIO_CFLAGS) $(CDDB_CFLAGS)
+$(flac-objs): CFLAGS += $(FLAC_CFLAGS)
+$(mad-objs): CFLAGS += $(MAD_CFLAGS)
+$(mikmod-objs): CFLAGS += $(MIKMOD_CFLAGS)
+$(modplug-objs): CFLAGS += $(MODPLUG_CFLAGS)
+$(mpc-objs): CFLAGS += $(MPC_CFLAGS)
+$(vorbis-objs): CFLAGS += $(VORBIS_CFLAGS)
+$(wavpack-objs): CFLAGS += $(WAVPACK_CFLAGS)
+$(mp4-objs): CFLAGS += $(MP4_CFLAGS)
+$(aac-objs): CFLAGS += $(AAC_CFLAGS)
+$(ffmpeg-objs): CFLAGS += $(FFMPEG_CFLAGS)
+$(cue-objs): CFLAGS += $(CUE_CFLAGS)
+
+cdio.so: $(cdio-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(CDIO_LIBS) $(CDDB_LIBS))
+
+flac.so: $(flac-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(FLAC_LIBS))
+
+mad.so: $(mad-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(MAD_LIBS) $(ICONV_LIBS))
+
+mikmod.so: $(mikmod-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(MIKMOD_LIBS))
+
+modplug.so: $(modplug-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(MODPLUG_LIBS))
+
+mpc.so: $(mpc-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(MPC_LIBS))
+
+vorbis.so: $(vorbis-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(VORBIS_LIBS))
+
+wavpack.so: $(wavpack-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(WAVPACK_LIBS))
+
+wav.so: $(wav-objs) $(libcmus-y)
+ $(call cmd,ld_dl,)
+
+mp4.so: $(mp4-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(MP4_LIBS))
+
+aac.so: $(aac-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(AAC_LIBS))
+
+ffmpeg.so: $(ffmpeg-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(FFMPEG_LIBS))
+
+cue.so: $(cue-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(CUE_LIBS))
+
+# }}}
+
+# output plugins {{{
+pulse-objs := pulse.lo
+alsa-objs := alsa.lo mixer_alsa.lo
+arts-objs := arts.lo
+oss-objs := oss.lo mixer_oss.lo
+sun-objs := sun.lo mixer_sun.lo
+ao-objs := ao.lo
+waveout-objs := waveout.lo
+roar-objs := roar.lo
+
+op-$(CONFIG_PULSE) += pulse.so
+op-$(CONFIG_ALSA) += alsa.so
+op-$(CONFIG_ARTS) += arts.so
+op-$(CONFIG_OSS) += oss.so
+op-$(CONFIG_SUN) += sun.so
+op-$(CONFIG_AO) += ao.so
+op-$(CONFIG_WAVEOUT) += waveout.so
+op-$(CONFIG_ROAR) += roar.so
+
+$(pulse-objs): CFLAGS += $(PULSE_CFLAGS)
+$(alsa-objs): CFLAGS += $(ALSA_CFLAGS)
+$(arts-objs): CFLAGS += $(ARTS_CFLAGS)
+$(oss-objs): CFLAGS += $(OSS_CFLAGS)
+$(sun-objs): CFLAGS += $(SUN_CFLAGS)
+$(ao-objs): CFLAGS += $(AO_CFLAGS)
+$(waveout-objs): CFLAGS += $(WAVEOUT_CFLAGS)
+$(roar-objs): CFLAGS += $(ROAR_CFLAGS)
+
+pulse.so: $(pulse-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(PULSE_LIBS))
+
+alsa.so: $(alsa-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(ALSA_LIBS))
+
+arts.so: $(arts-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(ARTS_LIBS))
+
+oss.so: $(oss-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(OSS_LIBS))
+
+sun.so: $(sun-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(SUN_LIBS))
+
+ao.so: $(ao-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(AO_LIBS))
+
+waveout.so: $(waveout-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(WAVEOUT_LIBS))
+
+roar.so: $(roar-objs) $(libcmus-y)
+ $(call cmd,ld_dl,$(ROAR_LIBS))
+# }}}
+
+# man {{{
+man1 := Doc/cmus.1 Doc/cmus-remote.1
+man7 := Doc/cmus-tutorial.7
+
+$(man1): Doc/ttman
+$(man7): Doc/ttman
+
+%.1: %.txt
+ $(call cmd,ttman)
+
+%.7: %.txt
+ $(call cmd,ttman)
+
+Doc/ttman.o: Doc/ttman.c
+ $(call cmd,hostcc,)
+
+Doc/ttman: Doc/ttman.o
+ $(call cmd,hostld,)
+
+quiet_cmd_ttman = MAN $@
+ cmd_ttman = Doc/ttman $< $@
+# }}}
+
+data = $(wildcard data/*)
+
+clean += *.o *.lo *.so cmus libcmus.a cmus.def cmus.base cmus.exp cmus-remote Doc/*.o Doc/ttman Doc/*.1 Doc/*.7 .install.log
+distclean += .version config.mk config/*.h tags
+
+main: cmus cmus-remote
+plugins: $(ip-y) $(op-y)
+man: $(man1) $(man7)
+
+install-main: main
+ $(INSTALL) -m755 $(bindir) cmus cmus-remote
+
+install-plugins: plugins
+ $(INSTALL) -m755 $(libdir)/cmus/ip $(ip-y)
+ $(INSTALL) -m755 $(libdir)/cmus/op $(op-y)
+
+install-data: man
+ $(INSTALL) -m644 $(datadir)/cmus $(data)
+ $(INSTALL) -m644 $(mandir)/man1 $(man1)
+ $(INSTALL) -m644 $(mandir)/man7 $(man7)
+ $(INSTALL) -m755 $(exampledir) cmus-status-display
+
+install: all install-main install-plugins install-data
+
+tags:
+ exuberant-ctags *.[ch]
+
+# generating tarball using GIT {{{
+TARNAME = cmus-$(VERSION)
+
+dist:
+ @tarname=$(TARNAME); \
+ test "$(_ver2)" || { echo "No such revision $(REV)"; exit 1; }; \
+ echo " DIST $$tarname.tar.bz2"; \
+ git archive --format=tar --prefix=$$tarname/ $(REV)^{tree} | bzip2 -c -9 > $$tarname.tar.bz2
+
+# }}}
+
+.PHONY: all main plugins man dist tags
+.PHONY: install install-main install-plugins install-man
--- /dev/null
+
+ cmus - C* Music Player
+
+ http://cmus.sourceforge.net/
+
+ Copyright 2004-2008 Timo Hirvonen <tihirvon@gmail.com>
+ Copyright 2008-2011 Various Authors
+
+
+Configuration
+-------------
+
+List available optional features
+
+ $ ./configure --help
+
+Auto-detect everything
+
+ $ ./configure
+
+To disable some feature, arts for example, and install to $HOME run
+
+ $ ./configure prefix=$HOME CONFIG_ARTS=n
+
+After running configure you can see from the generated config.mk file
+what features have been configured in (see the CONFIG_* options).
+
+NOTE: For some distributions you need to install development versions
+of the dependencies. For example if you want to use 'mad' input plugin
+(mp3) you need to install libmad0-dev (Debian) or libmad-devel (RPM)
+package. After installing dependencies you need to run ./configure
+again, of course.
+
+If you want to use the Tremor library as alternative for decoding
+Ogg/Vorbis files you have to pass CONFIG_TREMOR=y to the configure
+script:
+
+ $ ./configure CONFIG_VORBIS=y CONFIG_TREMOR=y
+
+The Tremor library is supposed to be used on hardware that has no FPU.
+
+
+Building
+--------
+
+ $ make
+
+Or on some BSD systems you need to explicitly use GNU make:
+
+ $ gmake
+
+
+Installation
+------------
+
+ $ make install
+
+Or to install to a temporary directory:
+
+ $ make install DESTDIR=~/tmp/cmus
+
+This is useful when creating binary packages.
+
+Remember to replace 'make' with 'gmake' if needed.
+
+
+Manuals
+-------
+
+ $ man cmus-tutorial
+
+And
+
+ $ man cmus
+
+
+Mailing List
+------------
+
+To subscribe to cmus-devel@lists.sourceforge.net visit
+
+ http://lists.sourceforge.net/lists/listinfo/cmus-devel
+
+The list is open but moderated (you can post to the list without
+subscribing but it's not recommended because I have to accept each email
+form non-subscribed users). Traffic of the list is low.
+
+
+Reporting Bugs
+--------------
+
+After a crash send bug report with last lines of ~/cmus-debug.txt to
+cmus-devel@lists.sourceforge.net. The file exists only if you
+configured cmus with maximum debug level (./configure DEBUG=2).
+
+
+Git Repository
+--------------
+
+gitweb: http://gitorious.org/cmus
+clone: git://gitorious.org/cmus/cmus.git
+
+
+Hacking
+-------
+
+cmus uses the Linux kernel coding style. Use hard tabs. Tabs are
+_always_ 8 characters wide. Keep the style consistent with rest of the
+code.
+
+Use git format-patch to generate patches from your commits.
+Alternatively you can use "diff -up" if you don't want to use git.
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 dnk <dnk@bjum.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "id3.h"
+#include "comment.h"
+#include "read_wrapper.h"
+#include "aac.h"
+
+#include <neaacdec.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+/* FAAD_MIN_STREAMSIZE == 768, 6 == # of channels */
+#define BUFFER_SIZE (FAAD_MIN_STREAMSIZE * 6 * 4)
+
+struct aac_private {
+ char rbuf[BUFFER_SIZE];
+ int rbuf_len;
+ int rbuf_pos;
+
+ unsigned char channels;
+ unsigned long sample_rate;
+ long bitrate;
+ int object_type;
+
+ struct {
+ unsigned long samples;
+ unsigned long bytes;
+ } current;
+
+ char *overflow_buf;
+ int overflow_buf_len;
+
+ NeAACDecHandle decoder; /* typedef void * */
+};
+
+static inline int buffer_length(const struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+
+ return priv->rbuf_len - priv->rbuf_pos;
+}
+
+static inline void *buffer_data(const struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+
+ return priv->rbuf + priv->rbuf_pos;
+}
+
+static int buffer_fill(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+ int32_t n;
+
+ if (priv->rbuf_pos > 0) {
+ priv->rbuf_len = buffer_length(ip_data);
+ memmove(priv->rbuf, priv->rbuf + priv->rbuf_pos, priv->rbuf_len);
+ priv->rbuf_pos = 0;
+ }
+
+ if (priv->rbuf_len == BUFFER_SIZE)
+ return 1;
+
+ n = read_wrapper(ip_data, priv->rbuf + priv->rbuf_len, BUFFER_SIZE - priv->rbuf_len);
+ if (n == -1)
+ return -1;
+ if (n == 0)
+ return 0;
+
+ priv->rbuf_len += n;
+ return 1;
+}
+
+static inline void buffer_consume(struct input_plugin_data *ip_data, int n)
+{
+ struct aac_private *priv = ip_data->private;
+
+ BUG_ON(n > buffer_length(ip_data));
+
+ priv->rbuf_pos += n;
+}
+
+static int buffer_fill_min(struct input_plugin_data *ip_data, int len)
+{
+ int rc;
+
+ BUG_ON(len > BUFFER_SIZE);
+
+ while (buffer_length(ip_data) < len) {
+ rc = buffer_fill(ip_data);
+ if (rc <= 0)
+ return rc;
+ }
+ return 1;
+}
+
+/* 'data' must point to at least 6 bytes of data */
+static inline int parse_frame(const unsigned char data[6])
+{
+ int len;
+
+ /* http://www.audiocoding.com/modules/wiki/?page=ADTS */
+
+ /* first 12 bits must be set */
+ if (data[0] != 0xFF)
+ return 0;
+ if ((data[1] & 0xF0) != 0xF0)
+ return 0;
+
+ /* layer is always '00' */
+ if ((data[1] & 0x06) != 0x00)
+ return 0;
+
+ /* frame length is stored in 13 bits */
+ len = data[3] << 11; /* ..1100000000000 */
+ len |= data[4] << 3; /* ..xx11111111xxx */
+ len |= data[5] >> 5; /* ..xxxxxxxxxx111 */
+ len &= 0x1FFF; /* 13 bits */
+ return len;
+}
+
+/* scans forward to the next aac frame and makes sure
+ * the entire frame is in the buffer.
+ */
+static int buffer_fill_frame(struct input_plugin_data *ip_data)
+{
+ unsigned char *data;
+ int rc, n, len;
+ int max = 32768;
+
+ while (1) {
+ /* need at least 6 bytes of data */
+ rc = buffer_fill_min(ip_data, 6);
+ if (rc <= 0)
+ return rc;
+
+ len = buffer_length(ip_data);
+ data = buffer_data(ip_data);
+
+ /* scan for a frame */
+ for (n = 0; n < len - 5; n++) {
+ /* give up after 32KB */
+ if (max-- == 0) {
+ d_print("no frame found!\n");
+ /* FIXME: set errno? */
+ return -1;
+ }
+
+ /* see if there's a frame at this location */
+ rc = parse_frame(data + n);
+ if (rc == 0)
+ continue;
+
+ /* found a frame, consume all data up to the frame */
+ buffer_consume(ip_data, n);
+
+ /* rc == frame length */
+ rc = buffer_fill_min(ip_data, rc);
+ if (rc <= 0)
+ return rc;
+
+ return 1;
+ }
+
+ /* consume what we used */
+ buffer_consume(ip_data, n);
+ }
+ /* not reached */
+}
+
+static void aac_get_channel_map(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+ NeAACDecFrameInfo frame_info;
+ void *buf;
+ int i;
+
+ ip_data->channel_map[0] = CHANNEL_POSITION_INVALID;
+
+ if (buffer_fill_frame(ip_data) <= 0)
+ return;
+
+ buf = NeAACDecDecode(priv->decoder, &frame_info, buffer_data(ip_data), buffer_length(ip_data));
+ if (!buf || frame_info.error != 0 || frame_info.bytesconsumed <= 0
+ || frame_info.channels > CHANNELS_MAX)
+ return;
+
+ for (i = 0; i < frame_info.channels; i++)
+ ip_data->channel_map[i] = channel_position_aac(frame_info.channel_position[i]);
+}
+
+static int aac_open(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv;
+ NeAACDecConfigurationPtr neaac_cfg;
+ int ret, n;
+
+ /* init private struct */
+ const struct aac_private priv_init = {
+ .decoder = NeAACDecOpen(),
+ .bitrate = -1,
+ .object_type = -1
+ };
+ priv = xnew(struct aac_private, 1);
+ *priv = priv_init;
+ ip_data->private = priv;
+
+ /* set decoder config */
+ neaac_cfg = NeAACDecGetCurrentConfiguration(priv->decoder);
+ neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */
+ neaac_cfg->downMatrix = 0; /* NOT 5.1 -> stereo */
+ neaac_cfg->dontUpSampleImplicitSBR = 0; /* upsample, please! */
+ NeAACDecSetConfiguration(priv->decoder, neaac_cfg);
+
+ /* find a frame */
+ if (buffer_fill_frame(ip_data) <= 0) {
+ ret = -IP_ERROR_FILE_FORMAT;
+ goto out;
+ }
+
+ /* in case of a bug, make sure there is at least some data
+ * in the buffer for NeAACDecInit() to work with.
+ */
+ if (buffer_fill_min(ip_data, 256) <= 0) {
+ d_print("not enough data\n");
+ ret = -IP_ERROR_FILE_FORMAT;
+ goto out;
+ }
+
+ /* init decoder, returns the length of the header (if any) */
+ n = NeAACDecInit(priv->decoder, buffer_data(ip_data), buffer_length(ip_data),
+ &priv->sample_rate, &priv->channels);
+ if (n < 0) {
+ d_print("NeAACDecInit failed\n");
+ ret = -IP_ERROR_FILE_FORMAT;
+ goto out;
+ }
+
+ d_print("sample rate %luhz, channels %u\n", priv->sample_rate, priv->channels);
+ if (!priv->sample_rate || !priv->channels) {
+ ret = -IP_ERROR_FILE_FORMAT;
+ goto out;
+ }
+
+ /* skip the header */
+ d_print("skipping header (%d bytes)\n", n);
+
+ buffer_consume(ip_data, n);
+
+ /*NeAACDecInitDRM(priv->decoder, priv->sample_rate, priv->channels);*/
+
+ ip_data->sf = sf_rate(priv->sample_rate) | sf_channels(priv->channels) | sf_bits(16) | sf_signed(1);
+#if defined(WORDS_BIGENDIAN)
+ ip_data->sf |= sf_bigendian(1);
+#endif
+ aac_get_channel_map(ip_data);
+
+ return 0;
+out:
+ NeAACDecClose(priv->decoder);
+ free(priv);
+ return ret;
+}
+
+static int aac_close(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+
+ NeAACDecClose(priv->decoder);
+ free(priv);
+ ip_data->private = NULL;
+ return 0;
+}
+
+/* returns -1 on fatal errors
+ * returns -2 on non-fatal errors
+ * 0 on eof
+ * number of bytes put in 'buffer' on success */
+static int decode_one_frame(struct input_plugin_data *ip_data, void *buffer, int count)
+{
+ struct aac_private *priv = ip_data->private;
+ unsigned char *aac_data;
+ unsigned int aac_data_size;
+ NeAACDecFrameInfo frame_info;
+ char *sample_buf;
+ int bytes, rc;
+
+ rc = buffer_fill_frame(ip_data);
+ if (rc <= 0)
+ return rc;
+
+ aac_data = buffer_data(ip_data);
+ aac_data_size = buffer_length(ip_data);
+
+ /* aac data -> raw pcm */
+ sample_buf = NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_size);
+ if (frame_info.error == 0 && frame_info.samples > 0) {
+ priv->current.samples += frame_info.samples;
+ priv->current.bytes += frame_info.bytesconsumed;
+ }
+
+ buffer_consume(ip_data, frame_info.bytesconsumed);
+
+ if (!sample_buf || frame_info.bytesconsumed <= 0) {
+ d_print("fatal error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (frame_info.error != 0) {
+ d_print("frame error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
+ return -2;
+ }
+
+ if (frame_info.samples <= 0)
+ return -2;
+
+ if (frame_info.channels != priv->channels || frame_info.samplerate != priv->sample_rate) {
+ d_print("invalid channel or sample_rate count\n");
+ return -2;
+ }
+
+ /* 16-bit samples */
+ bytes = frame_info.samples * 2;
+
+ if (bytes > count) {
+ /* decoded too much, keep overflow */
+ priv->overflow_buf = sample_buf + count;
+ priv->overflow_buf_len = bytes - count;
+ memcpy(buffer, sample_buf, count);
+ return count;
+ } else {
+ memcpy(buffer, sample_buf, bytes);
+ }
+ return bytes;
+}
+
+static int aac_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct aac_private *priv = ip_data->private;
+ int rc;
+
+ /* use overflow from previous call (if any) */
+ if (priv->overflow_buf_len) {
+ int len = priv->overflow_buf_len;
+
+ if (len > count)
+ len = count;
+
+ memcpy(buffer, priv->overflow_buf, len);
+ priv->overflow_buf += len;
+ priv->overflow_buf_len -= len;
+ return len;
+ }
+
+ do {
+ rc = decode_one_frame(ip_data, buffer, count);
+ } while (rc == -2);
+ return rc;
+}
+
+static int aac_seek(struct input_plugin_data *ip_data, double offset)
+{
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+static int aac_read_comments(struct input_plugin_data *ip_data,
+ struct keyval **comments)
+{
+ GROWING_KEYVALS(c);
+ struct id3tag id3;
+ int rc, fd, i;
+
+ fd = open(ip_data->filename, O_RDONLY);
+ if (fd == -1)
+ return -1;
+
+ id3_init(&id3);
+ rc = id3_read_tags(&id3, fd, ID3_V1 | ID3_V2);
+ if (rc == -1) {
+ d_print("error: %s\n", strerror(errno));
+ goto out;
+ }
+
+ for (i = 0; i < NUM_ID3_KEYS; i++) {
+ char *val = id3_get_comment(&id3, i);
+
+ if (val)
+ comments_add(&c, id3_key_names[i], val);
+ }
+out:
+ close(fd);
+ id3_free(&id3);
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+ return 0;
+}
+
+static int aac_duration(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+ NeAACDecFrameInfo frame_info;
+ int samples = 0, bytes = 0, frames = 0;
+ off_t file_size;
+
+ file_size = lseek(ip_data->fd, 0, SEEK_END);
+ if (file_size == -1)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+
+ /* Seek to the middle of the file. There is almost always silence at
+ * the beginning, which gives wrong results. */
+ if (lseek(ip_data->fd, file_size/2, SEEK_SET) == -1)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+
+ priv->rbuf_pos = 0;
+ priv->rbuf_len = 0;
+
+ /* guess track length by decoding the first 10 frames */
+ while (frames < 10) {
+ if (buffer_fill_frame(ip_data) <= 0)
+ break;
+
+ NeAACDecDecode(priv->decoder, &frame_info,
+ buffer_data(ip_data), buffer_length(ip_data));
+ if (frame_info.error == 0 && frame_info.samples > 0) {
+ samples += frame_info.samples;
+ bytes += frame_info.bytesconsumed;
+ frames++;
+ }
+ if (frame_info.bytesconsumed == 0)
+ break;
+
+ buffer_consume(ip_data, frame_info.bytesconsumed);
+ }
+
+ if (frames == 0)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+
+ samples /= frames;
+ samples /= priv->channels;
+ bytes /= frames;
+
+ /* 8 * file_size / duration */
+ priv->bitrate = (8 * bytes * priv->sample_rate) / samples;
+
+ priv->object_type = frame_info.object_type;
+
+ return ((file_size / bytes) * samples) / priv->sample_rate;
+}
+
+static long aac_bitrate(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+ return priv->bitrate != -1 ? priv->bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+static long aac_current_bitrate(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+ long bitrate = -1;
+ if (priv->current.samples > 0) {
+ priv->current.samples /= priv->channels;
+ bitrate = (8 * priv->current.bytes * priv->sample_rate) / priv->current.samples;
+ priv->current.samples = 0;
+ priv->current.bytes = 0;
+ }
+ return bitrate;
+}
+
+static char *aac_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("aac");
+}
+
+static const char *object_type_to_str(int object_type)
+{
+ switch (object_type) {
+ case MAIN: return "Main";
+ case LC: return "LC";
+ case SSR: return "SSR";
+ case LTP: return "LTP";
+ case HE_AAC: return "HE";
+ case ER_LC: return "ER-LD";
+ case ER_LTP: return "ER-LTP";
+ case LD: return "LD";
+ case DRM_ER_LC: return "DRM-ER-LC";
+ }
+ return NULL;
+}
+
+static char *aac_codec_profile(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+ const char *profile = object_type_to_str(priv->object_type);
+
+ return profile ? xstrdup(profile) : NULL;
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = aac_open,
+ .close = aac_close,
+ .read = aac_read,
+ .seek = aac_seek,
+ .read_comments = aac_read_comments,
+ .duration = aac_duration,
+ .bitrate = aac_bitrate,
+ .bitrate_current = aac_current_bitrate,
+ .codec = aac_codec,
+ .codec_profile = aac_codec_profile
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = { "aac", NULL };
+const char * const ip_mime_types[] = { "audio/aac", "audio/aacp", NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2011 Various Authors
+ * Copyright 2011 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _AAC_H
+#define _AAC_H
+
+#include "channelmap.h"
+#include <neaacdec.h>
+
+static inline channel_position_t channel_position_aac(unsigned char c)
+{
+ switch (c) {
+ case FRONT_CHANNEL_CENTER: return CHANNEL_POSITION_FRONT_CENTER;
+ case FRONT_CHANNEL_LEFT: return CHANNEL_POSITION_FRONT_LEFT;
+ case FRONT_CHANNEL_RIGHT: return CHANNEL_POSITION_FRONT_RIGHT;
+ case SIDE_CHANNEL_LEFT: return CHANNEL_POSITION_SIDE_LEFT;
+ case SIDE_CHANNEL_RIGHT: return CHANNEL_POSITION_SIDE_RIGHT;
+ case BACK_CHANNEL_LEFT: return CHANNEL_POSITION_REAR_LEFT;
+ case BACK_CHANNEL_RIGHT: return CHANNEL_POSITION_REAR_RIGHT;
+ case BACK_CHANNEL_CENTER: return CHANNEL_POSITION_REAR_CENTER;
+ case LFE_CHANNEL: return CHANNEL_POSITION_LFE;
+ default: return CHANNEL_POSITION_INVALID;
+ }
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2008 Jonathan Kleinehellefort
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * snd_pcm_state_t:
+ *
+ * Open
+ * SND_PCM_STATE_OPEN = 0,
+ *
+ * Setup installed
+ * SND_PCM_STATE_SETUP = 1,
+ *
+ * Ready to start
+ * SND_PCM_STATE_PREPARED = 2,
+ *
+ * Running
+ * SND_PCM_STATE_RUNNING = 3,
+ *
+ * Stopped: underrun (playback) or overrun (capture) detected
+ * SND_PCM_STATE_XRUN = 4,
+ *
+ * Draining: running (playback) or stopped (capture)
+ * SND_PCM_STATE_DRAINING = 5,
+ *
+ * Paused
+ * SND_PCM_STATE_PAUSED = 6,
+ *
+ * Hardware is suspended
+ * SND_PCM_STATE_SUSPENDED = 7,
+ *
+ * Hardware is disconnected
+ * SND_PCM_STATE_DISCONNECTED = 8,
+ */
+
+#include "op.h"
+#include "utils.h"
+#include "xmalloc.h"
+#include "sf.h"
+#include "debug.h"
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#define ALSA_PCM_NEW_SW_PARAMS_API
+
+#include <alsa/asoundlib.h>
+
+static sample_format_t alsa_sf;
+static snd_pcm_t *alsa_handle;
+static snd_pcm_format_t alsa_fmt;
+static int alsa_can_pause;
+static snd_pcm_status_t *status;
+
+/* bytes (bits * channels / 8) */
+static int alsa_frame_size;
+
+/* configuration */
+static char *alsa_dsp_device = NULL;
+
+#if 0
+#define debug_ret(func, ret) \
+ d_print("%s returned %d %s\n", func, ret, ret < 0 ? snd_strerror(ret) : "")
+#else
+#define debug_ret(func, ret) do { } while (0)
+#endif
+
+static int alsa_error_to_op_error(int err)
+{
+ if (!err)
+ return OP_ERROR_SUCCESS;
+ err = -err;
+ if (err < SND_ERROR_BEGIN) {
+ errno = err;
+ return -OP_ERROR_ERRNO;
+ }
+ return -OP_ERROR_INTERNAL;
+}
+
+/* we don't want error messages to stderr */
+static void error_handler(const char *file, int line, const char *function, int err, const char *fmt, ...)
+{
+}
+
+static int op_alsa_init(void)
+{
+ int rc;
+
+ snd_lib_error_set_handler(error_handler);
+
+ if (alsa_dsp_device == NULL)
+ alsa_dsp_device = xstrdup("default");
+ rc = snd_pcm_status_malloc(&status);
+ if (rc < 0) {
+ free(alsa_dsp_device);
+ alsa_dsp_device = NULL;
+ errno = ENOMEM;
+ return -OP_ERROR_ERRNO;
+ }
+ return OP_ERROR_SUCCESS;
+}
+
+static int op_alsa_exit(void)
+{
+ snd_pcm_status_free(status);
+ free(alsa_dsp_device);
+ alsa_dsp_device = NULL;
+ return OP_ERROR_SUCCESS;
+}
+
+/* randomize hw params */
+static int alsa_set_hw_params(void)
+{
+ snd_pcm_hw_params_t *hwparams = NULL;
+ const char *cmd;
+ unsigned int rate;
+ int rc, dir;
+
+ snd_pcm_hw_params_malloc(&hwparams);
+
+ cmd = "snd_pcm_hw_params_any";
+ rc = snd_pcm_hw_params_any(alsa_handle, hwparams);
+ if (rc < 0)
+ goto error;
+
+ alsa_can_pause = snd_pcm_hw_params_can_pause(hwparams);
+ d_print("can pause = %d\n", alsa_can_pause);
+
+ cmd = "snd_pcm_hw_params_set_access";
+ rc = snd_pcm_hw_params_set_access(alsa_handle, hwparams,
+ SND_PCM_ACCESS_RW_INTERLEAVED);
+ if (rc < 0)
+ goto error;
+
+ alsa_fmt = snd_pcm_build_linear_format(sf_get_bits(alsa_sf), sf_get_bits(alsa_sf),
+ sf_get_signed(alsa_sf) ? 0 : 1,
+ sf_get_bigendian(alsa_sf));
+ cmd = "snd_pcm_hw_params_set_format";
+ rc = snd_pcm_hw_params_set_format(alsa_handle, hwparams, alsa_fmt);
+ if (rc < 0)
+ goto error;
+
+ cmd = "snd_pcm_hw_params_set_channels";
+ rc = snd_pcm_hw_params_set_channels(alsa_handle, hwparams, sf_get_channels(alsa_sf));
+ if (rc < 0)
+ goto error;
+
+ cmd = "snd_pcm_hw_params_set_rate";
+ rate = sf_get_rate(alsa_sf);
+ dir = 0;
+ rc = snd_pcm_hw_params_set_rate_near(alsa_handle, hwparams, &rate, &dir);
+ if (rc < 0)
+ goto error;
+ d_print("rate=%d\n", rate);
+
+ cmd = "snd_pcm_hw_params";
+ rc = snd_pcm_hw_params(alsa_handle, hwparams);
+ if (rc < 0)
+ goto error;
+ goto out;
+error:
+ d_print("%s: error: %s\n", cmd, snd_strerror(rc));
+out:
+ snd_pcm_hw_params_free(hwparams);
+ return rc;
+}
+
+static int op_alsa_open(sample_format_t sf, const channel_position_t *channel_map)
+{
+ int rc;
+
+ alsa_sf = sf;
+ alsa_frame_size = sf_get_frame_size(alsa_sf);
+
+ rc = snd_pcm_open(&alsa_handle, alsa_dsp_device, SND_PCM_STREAM_PLAYBACK, 0);
+ if (rc < 0)
+ goto error;
+
+ rc = alsa_set_hw_params();
+ if (rc)
+ goto close_error;
+
+ rc = snd_pcm_prepare(alsa_handle);
+ if (rc < 0)
+ goto close_error;
+ return OP_ERROR_SUCCESS;
+close_error:
+ snd_pcm_close(alsa_handle);
+error:
+ return alsa_error_to_op_error(rc);
+}
+
+static int op_alsa_write(const char *buffer, int count);
+
+static int op_alsa_close(void)
+{
+ int rc;
+
+ rc = snd_pcm_drain(alsa_handle);
+ debug_ret("snd_pcm_drain", rc);
+
+ rc = snd_pcm_close(alsa_handle);
+ debug_ret("snd_pcm_close", rc);
+ return alsa_error_to_op_error(rc);
+}
+
+static int op_alsa_drop(void)
+{
+ int rc;
+
+ rc = snd_pcm_drop(alsa_handle);
+ debug_ret("snd_pcm_drop", rc);
+
+ rc = snd_pcm_prepare(alsa_handle);
+ debug_ret("snd_pcm_prepare", rc);
+
+ /* drop set state to SETUP
+ * prepare set state to PREPARED
+ *
+ * so if old state was PAUSED we can't UNPAUSE (see op_alsa_unpause)
+ */
+ return alsa_error_to_op_error(rc);
+}
+
+static int op_alsa_write(const char *buffer, int count)
+{
+ int rc, len;
+ int recovered = 0;
+
+ len = count / alsa_frame_size;
+again:
+ rc = snd_pcm_writei(alsa_handle, buffer, len);
+ if (rc < 0) {
+ // rc _should_ be either -EBADFD, -EPIPE or -ESTRPIPE
+ if (!recovered && (rc == -EINTR || rc == -EPIPE || rc == -ESTRPIPE)) {
+ d_print("snd_pcm_writei failed: %s, trying to recover\n",
+ snd_strerror(rc));
+ recovered++;
+ // this handles -EINTR, -EPIPE and -ESTRPIPE
+ // for other errors it just returns the error code
+ rc = snd_pcm_recover(alsa_handle, rc, 1);
+ if (!rc)
+ goto again;
+ }
+
+ /* this handles EAGAIN too which is not critical error */
+ return alsa_error_to_op_error(rc);
+ }
+
+ rc *= alsa_frame_size;
+ return rc;
+}
+
+static int op_alsa_buffer_space(void)
+{
+ int rc;
+ snd_pcm_sframes_t f;
+
+ f = snd_pcm_avail_update(alsa_handle);
+ while (f < 0) {
+ d_print("snd_pcm_avail_update failed: %s, trying to recover\n",
+ snd_strerror(f));
+ rc = snd_pcm_recover(alsa_handle, f, 1);
+ if (rc < 0) {
+ d_print("recovery failed: %s\n", snd_strerror(rc));
+ return alsa_error_to_op_error(rc);
+ }
+ f = snd_pcm_avail_update(alsa_handle);
+ }
+
+ return f * alsa_frame_size;
+}
+
+static int op_alsa_pause(void)
+{
+ int rc = 0;
+ if (alsa_can_pause) {
+ snd_pcm_state_t state = snd_pcm_state(alsa_handle);
+ if (state == SND_PCM_STATE_PREPARED) {
+ // state is PREPARED -> no need to pause
+ } else if (state == SND_PCM_STATE_RUNNING) {
+ // state is RUNNING - > pause
+
+ // infinite timeout
+ rc = snd_pcm_wait(alsa_handle, -1);
+ debug_ret("snd_pcm_wait", rc);
+
+ rc = snd_pcm_pause(alsa_handle, 1);
+ debug_ret("snd_pcm_pause", rc);
+ } else {
+ d_print("error: state is not RUNNING or PREPARED\n");
+ rc = -OP_ERROR_INTERNAL;
+ }
+ } else {
+ rc = snd_pcm_drop(alsa_handle);
+ debug_ret("snd_pcm_drop", rc);
+ }
+ return alsa_error_to_op_error(rc);
+}
+
+static int op_alsa_unpause(void)
+{
+ int rc = 0;
+ if (alsa_can_pause) {
+ snd_pcm_state_t state = snd_pcm_state(alsa_handle);
+ if (state == SND_PCM_STATE_PREPARED) {
+ // state is PREPARED -> no need to unpause
+ } else if (state == SND_PCM_STATE_PAUSED) {
+ // state is PAUSED -> unpause
+
+ // infinite timeout
+ rc = snd_pcm_wait(alsa_handle, -1);
+ debug_ret("snd_pcm_wait", rc);
+
+ rc = snd_pcm_pause(alsa_handle, 0);
+ debug_ret("snd_pcm_pause", rc);
+ } else {
+ d_print("error: state is not PAUSED nor PREPARED\n");
+ rc = -OP_ERROR_INTERNAL;
+ }
+ } else {
+ rc = snd_pcm_prepare(alsa_handle);
+ debug_ret("snd_pcm_prepare", rc);
+ }
+ return alsa_error_to_op_error(rc);
+}
+
+static int op_alsa_set_option(int key, const char *val)
+{
+ switch (key) {
+ case 0:
+ free(alsa_dsp_device);
+ alsa_dsp_device = xstrdup(val);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return OP_ERROR_SUCCESS;
+}
+
+static int op_alsa_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ if (alsa_dsp_device)
+ *val = xstrdup(alsa_dsp_device);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return OP_ERROR_SUCCESS;
+}
+
+const struct output_plugin_ops op_pcm_ops = {
+ .init = op_alsa_init,
+ .exit = op_alsa_exit,
+ .open = op_alsa_open,
+ .close = op_alsa_close,
+ .drop = op_alsa_drop,
+ .write = op_alsa_write,
+ .buffer_space = op_alsa_buffer_space,
+ .pause = op_alsa_pause,
+ .unpause = op_alsa_unpause,
+ .set_option = op_alsa_set_option,
+ .get_option = op_alsa_get_option
+};
+
+const char * const op_pcm_options[] = {
+ "device",
+ NULL
+};
+
+const int op_priority = 0;
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "op.h"
+#include "xmalloc.h"
+#include "utils.h"
+#include "misc.h"
+#include "debug.h"
+
+/*
+ * <ao/ao.h> uses FILE but doesn't include stdio.h.
+ * Also we use snprintf().
+ */
+#include <stdio.h>
+#include <strings.h>
+#include <ao/ao.h>
+
+#ifdef AO_EBADFORMAT
+#define AO_API_1
+#endif
+
+static ao_device *libao_device;
+static char *wav_dir = NULL;
+static int wav_counter = 1;
+static int is_wav = 0;
+
+/* configuration */
+static char *libao_driver = NULL;
+static int libao_buffer_space = 16384;
+
+
+static int op_ao_init(void)
+{
+ /* ignore config value */
+ wav_counter = 1;
+
+ ao_initialize();
+ return 0;
+}
+
+static int op_ao_exit(void)
+{
+ free(libao_driver);
+ ao_shutdown();
+ return 0;
+}
+
+/* http://www.xiph.org/ao/doc/ao_sample_format.html */
+static const struct {
+ channel_position_t pos;
+ const char *str;
+} ao_channel_mapping[] = {
+ { CHANNEL_POSITION_LEFT, "L" },
+ { CHANNEL_POSITION_RIGHT, "R" },
+ { CHANNEL_POSITION_CENTER, "C" },
+ { CHANNEL_POSITION_MONO, "M" },
+ { CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, "CL" },
+ { CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, "CR" },
+ { CHANNEL_POSITION_REAR_LEFT, "BL" },
+ { CHANNEL_POSITION_REAR_RIGHT, "BR" },
+ { CHANNEL_POSITION_REAR_CENTER, "BC" },
+ { CHANNEL_POSITION_SIDE_LEFT, "SL" },
+ { CHANNEL_POSITION_SIDE_RIGHT, "SR" },
+ { CHANNEL_POSITION_LFE, "LFE" },
+ { CHANNEL_POSITION_INVALID, "X" },
+};
+
+#ifdef AO_API_1
+static char *ao_channel_matrix(int channels, const channel_position_t *map)
+{
+ int i, j;
+ char buf[256] = "";
+
+ if (!map || !channel_map_valid(map))
+ return NULL;
+
+ for (i = 0; i < channels; i++) {
+ const channel_position_t pos = map[i];
+ int found = 0;
+ for (j = 0; j < N_ELEMENTS(ao_channel_mapping); j++) {
+ if (pos == ao_channel_mapping[j].pos) {
+ strcat(buf, ao_channel_mapping[j].str);
+ strcat(buf, ",");
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ strcat(buf, "M,");
+ }
+ buf[strlen(buf)-1] = '\0';
+
+ return xstrdup(buf);
+}
+#endif
+
+static int op_ao_open(sample_format_t sf, const channel_position_t *channel_map)
+{
+ ao_sample_format format = {
+ .bits = sf_get_bits(sf),
+ .rate = sf_get_rate(sf),
+ .channels = sf_get_channels(sf),
+ .byte_format = sf_get_bigendian(sf) ? AO_FMT_BIG : AO_FMT_LITTLE,
+#ifdef AO_API_1
+ .matrix = ao_channel_matrix(sf_get_channels(sf), channel_map)
+#endif
+ };
+ int driver;
+
+ if (libao_driver == NULL) {
+ driver = ao_default_driver_id();
+ } else {
+ driver = ao_driver_id(libao_driver);
+ is_wav = strcasecmp(libao_driver, "wav") == 0;
+ }
+ if (driver == -1) {
+ errno = ENODEV;
+ return -OP_ERROR_ERRNO;
+ }
+
+ if (is_wav) {
+ char file[512];
+
+ if (wav_dir == NULL)
+ wav_dir = xstrdup(home_dir);
+ snprintf(file, sizeof(file), "%s/%02d.wav", wav_dir, wav_counter);
+ libao_device = ao_open_file(driver, file, 0, &format, NULL);
+ } else {
+ libao_device = ao_open_live(driver, &format, NULL);
+ }
+
+ if (libao_device == NULL) {
+ switch (errno) {
+ case AO_ENODRIVER:
+ case AO_ENOTFILE:
+ case AO_ENOTLIVE:
+ case AO_EOPENDEVICE:
+ errno = ENODEV;
+ return -OP_ERROR_ERRNO;
+ case AO_EBADOPTION:
+ errno = EINVAL;
+ return -OP_ERROR_ERRNO;
+ case AO_EOPENFILE:
+ errno = EACCES;
+ return -OP_ERROR_ERRNO;
+ case AO_EFILEEXISTS:
+ errno = EEXIST;
+ return -OP_ERROR_ERRNO;
+ case AO_EFAIL:
+ default:
+ return -OP_ERROR_INTERNAL;
+ }
+ }
+
+#ifdef AO_API_1
+ d_print("channel matrix: %s\n", format.matrix ? format.matrix : "default");
+#endif
+ return 0;
+}
+
+static int op_ao_close(void)
+{
+ ao_close(libao_device);
+ if (is_wav)
+ wav_counter++;
+ return 0;
+}
+
+static int op_ao_write(const char *buffer, int count)
+{
+ if (ao_play(libao_device, (void *)buffer, count) == 0)
+ return -1;
+ return count;
+}
+
+static int op_ao_buffer_space(void)
+{
+ if (is_wav)
+ return 128 * 1024;
+ return libao_buffer_space;
+}
+
+static int op_ao_set_option(int key, const char *val)
+{
+ long int ival;
+
+ switch (key) {
+ case 0:
+ if (str_to_int(val, &ival) || ival < 4096) {
+ errno = EINVAL;
+ return -OP_ERROR_ERRNO;
+ }
+ libao_buffer_space = ival;
+ break;
+ case 1:
+ free(libao_driver);
+ libao_driver = NULL;
+ if (val[0])
+ libao_driver = xstrdup(val);
+ break;
+ case 2:
+ if (str_to_int(val, &ival)) {
+ errno = EINVAL;
+ return -OP_ERROR_ERRNO;
+ }
+ wav_counter = ival;
+ break;
+ case 3:
+ free(wav_dir);
+ wav_dir = xstrdup(val);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+static int op_ao_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ *val = xnew(char, 22);
+ snprintf(*val, 22, "%d", libao_buffer_space);
+ break;
+ case 1:
+ if (libao_driver)
+ *val = xstrdup(libao_driver);
+ break;
+ case 2:
+ *val = xnew(char, 22);
+ snprintf(*val, 22, "%d", wav_counter);
+ break;
+ case 3:
+ if (wav_dir == NULL)
+ wav_dir = xstrdup(home_dir);
+ *val = expand_filename(wav_dir);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+const struct output_plugin_ops op_pcm_ops = {
+ .init = op_ao_init,
+ .exit = op_ao_exit,
+ .open = op_ao_open,
+ .close = op_ao_close,
+ .write = op_ao_write,
+ .buffer_space = op_ao_buffer_space,
+ .set_option = op_ao_set_option,
+ .get_option = op_ao_get_option
+};
+
+const char * const op_pcm_options[] = {
+ "buffer_size",
+ "driver",
+ "wav_counter",
+ "wav_dir",
+ NULL
+};
+
+const int op_priority = 3;
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
+ *
+ * Cleaned up by Timo Hirvonen <tihirvon@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ape.h"
+#include "file.h"
+#include "xmalloc.h"
+#include "utils.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <strings.h>
+
+/* http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html */
+
+#define PREAMBLE_SIZE (8)
+static const char preamble[PREAMBLE_SIZE] = { 'A', 'P', 'E', 'T', 'A', 'G', 'E', 'X' };
+
+/* NOTE: not sizeof(struct ape_header)! */
+#define HEADER_SIZE (32)
+
+/* returns position of APE header or -1 if not found */
+static int find_ape_tag_slow(int fd)
+{
+ char buf[4096];
+ int match = 0;
+ int pos = 0;
+
+ /* seek to start of file */
+ if (lseek(fd, pos, SEEK_SET) == -1)
+ return -1;
+
+ while (1) {
+ int i, got = read(fd, buf, sizeof(buf));
+
+ if (got == -1) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+ break;
+ }
+ if (got == 0)
+ break;
+
+ for (i = 0; i < got; i++) {
+ if (buf[i] != preamble[match]) {
+ match = 0;
+ continue;
+ }
+
+ match++;
+ if (match == PREAMBLE_SIZE)
+ return pos + i + 1 - PREAMBLE_SIZE;
+ }
+ pos += got;
+ }
+ return -1;
+}
+
+static int ape_parse_header(const char *buf, struct ape_header *h)
+{
+ if (memcmp(buf, preamble, PREAMBLE_SIZE))
+ return 0;
+
+ h->version = read_le32(buf + 8);
+ h->size = read_le32(buf + 12);
+ h->count = read_le32(buf + 16);
+ h->flags = read_le32(buf + 20);
+ return 1;
+}
+
+static int read_header(int fd, struct ape_header *h)
+{
+ char buf[HEADER_SIZE];
+
+ if (read_all(fd, buf, sizeof(buf)) != sizeof(buf))
+ return 0;
+ return ape_parse_header(buf, h);
+}
+
+/* sets fd right after the header and returns 1 if found,
+ * otherwise returns 0
+ */
+static int find_ape_tag(int fd, struct ape_header *h, int slow)
+{
+ int pos;
+
+ if (lseek(fd, -HEADER_SIZE, SEEK_END) == -1)
+ return 0;
+ if (read_header(fd, h))
+ return 1;
+
+ if (!slow)
+ return 0;
+
+ pos = find_ape_tag_slow(fd);
+ if (pos == -1)
+ return 0;
+ if (lseek(fd, pos, SEEK_SET) == -1)
+ return 0;
+ return read_header(fd, h);
+}
+
+/*
+ * All keys are ASCII and length is 2..255
+ *
+ * UTF-8: Artist, Album, Title, Genre
+ * Integer: Track (N or N/M)
+ * Date: Year (release), "Record Date"
+ *
+ * UTF-8 strings are NOT zero terminated.
+ *
+ * Also support "discnumber" (vorbis) and "disc" (non-standard)
+ */
+static int ape_parse_one(const char *buf, int size, char **keyp, char **valp)
+{
+ int pos = 0;
+
+ while (size - pos > 8) {
+ uint32_t val_len, flags;
+ char *key, *val;
+ int max_key_len, key_len;
+
+ val_len = read_le32(buf + pos); pos += 4;
+ flags = read_le32(buf + pos); pos += 4;
+
+ max_key_len = size - pos - val_len - 1;
+ if (max_key_len < 0) {
+ /* corrupt */
+ break;
+ }
+
+ for (key_len = 0; key_len < max_key_len && buf[pos + key_len]; key_len++)
+ ; /* nothing */
+ if (buf[pos + key_len]) {
+ /* corrupt */
+ break;
+ }
+
+ if (!AF_IS_UTF8(flags)) {
+ /* ignore binary data */
+ pos += key_len + 1 + val_len;
+ continue;
+ }
+
+ key = xstrdup(buf + pos);
+ pos += key_len + 1;
+
+ /* should not be NUL-terminated */
+ val = xstrndup(buf + pos, val_len);
+ pos += val_len;
+
+ /* could be moved to comment.c but I don't think anyone else would use it */
+ if (!strcasecmp(key, "record date") || !strcasecmp(key, "year")) {
+ free(key);
+ key = xstrdup("date");
+ }
+
+ if (!strcasecmp(key, "date")) {
+ /* Date format
+ *
+ * 1999-08-11 12:34:56
+ * 1999-08-11 12:34
+ * 1999-08-11
+ * 1999-08
+ * 1999
+ * 1999-W34 (week 34, totally crazy)
+ *
+ * convert to year, pl.c supports only years anyways
+ *
+ * FIXME: which one is the most common tag (year or record date)?
+ */
+ if (strlen(val) > 4)
+ val[4] = 0;
+ }
+
+ *keyp = key;
+ *valp = val;
+ return pos;
+ }
+ return -1;
+}
+
+static off_t get_size(int fd)
+{
+ struct stat statbuf;
+
+ if (fstat(fd, &statbuf) || !S_ISREG(statbuf.st_mode))
+ return 0;
+ return statbuf.st_size;
+}
+
+/* return the number of comments, or -1 */
+int ape_read_tags(struct apetag *ape, int fd, int slow)
+{
+ struct ape_header *h = &ape->header;
+ int rc = -1;
+ off_t old_pos;
+
+ /* save position */
+ old_pos = lseek(fd, 0, SEEK_CUR);
+
+ if (!find_ape_tag(fd, h, slow))
+ goto fail;
+
+ if (AF_IS_FOOTER(h->flags)) {
+ /* seek back right after the header */
+ if (lseek(fd, get_size(fd) - h->size, SEEK_SET) == -1)
+ goto fail;
+ }
+
+ /* ignore insane tags */
+ if (h->size > 1024 * 1024)
+ goto fail;
+
+ ape->buf = xnew(char, h->size);
+ if (read_all(fd, ape->buf, h->size) != h->size)
+ goto fail;
+
+ rc = h->count;
+
+fail:
+ lseek(fd, old_pos, SEEK_SET);
+ return rc;
+}
+
+/* returned key-name must be free'd */
+char *ape_get_comment(struct apetag *ape, char **val)
+{
+ struct ape_header *h = &ape->header;
+ char *key;
+ int rc;
+
+ if (ape->pos >= h->size)
+ return NULL;
+
+ rc = ape_parse_one(ape->buf + ape->pos, h->size - ape->pos, &key, val);
+ if (rc < 0)
+ return NULL;
+ ape->pos += rc;
+
+ return key;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2007 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _APE_H
+#define _APE_H
+
+#include <stdint.h>
+#include <stdlib.h>
+
+struct ape_header {
+ /* 1000 or 2000 (1.0, 2.0) */
+ uint32_t version;
+
+ /* tag size (header + tags, excluding footer) */
+ uint32_t size;
+
+ /* number of items */
+ uint32_t count;
+
+ /* global flags for each tag
+ * there are also private flags for every tag
+ * NOTE: 0 for version 1.0 (1000)
+ */
+ uint32_t flags;
+};
+
+/* ape flags */
+#define AF_IS_UTF8(f) (((f) & 6) == 0)
+#define AF_IS_FOOTER(f) (((f) & (1 << 29)) == 0)
+
+struct apetag {
+ char *buf;
+ int pos;
+ struct ape_header header;
+};
+
+#define APETAG(name) struct apetag name = { .buf = NULL, .pos = 0, }
+
+int ape_read_tags(struct apetag *ape, int fd, int slow);
+char *ape_get_comment(struct apetag *ape, char **val);
+
+static inline void ape_free(struct apetag *ape)
+{
+ free(ape->buf);
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "op.h"
+#include "xmalloc.h"
+#include "debug.h"
+
+#include <artsc.h>
+
+static arts_stream_t arts_stream;
+static sample_format_t arts_sf;
+static int arts_buffer_size;
+
+static int op_arts_init(void)
+{
+ int rc;
+
+ rc = arts_init();
+ if (rc < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int op_arts_exit(void)
+{
+ arts_free();
+ return 0;
+}
+
+static int op_arts_open(sample_format_t sf, const channel_position_t *channel_map)
+{
+ int buffer_time, server_latency, total_latency;
+ int blocking;
+
+ arts_sf = sf;
+ arts_stream = arts_play_stream(sf_get_rate(arts_sf), sf_get_bits(arts_sf),
+ sf_get_channels(arts_sf), "cmus");
+ blocking = arts_stream_set(arts_stream, ARTS_P_BLOCKING, 0);
+ if (blocking) {
+ }
+ arts_buffer_size = arts_stream_get(arts_stream, ARTS_P_BUFFER_SIZE);
+ if (arts_buffer_size < 0) {
+ }
+ buffer_time = arts_stream_get(arts_stream, ARTS_P_BUFFER_TIME);
+ server_latency = arts_stream_get(arts_stream, ARTS_P_SERVER_LATENCY);
+ total_latency = arts_stream_get(arts_stream, ARTS_P_TOTAL_LATENCY);
+ d_print("buffer_time: %d\n", buffer_time);
+ d_print("server_latency: %d\n", server_latency);
+ d_print("total_latency: %d\n", total_latency);
+ return 0;
+}
+
+static int op_arts_close(void)
+{
+ arts_close_stream(arts_stream);
+ return 0;
+}
+
+static int op_arts_write(const char *buffer, int count)
+{
+ int rc;
+
+ rc = arts_write(arts_stream, buffer, count);
+ if (rc < 0) {
+ d_print("rc = %d, count = %d\n", rc, count);
+ return -1;
+ }
+ return rc;
+}
+
+static int op_arts_pause(void)
+{
+ return 0;
+}
+
+static int op_arts_unpause(void)
+{
+ return 0;
+}
+
+static int op_arts_buffer_space(void)
+{
+ int space;
+
+ space = arts_stream_get(arts_stream, ARTS_P_BUFFER_SPACE);
+ if (space < 0)
+ return -1;
+ return space;
+}
+
+static int op_arts_set_option(int key, const char *val)
+{
+ return -OP_ERROR_NOT_OPTION;
+}
+
+static int op_arts_get_option(int key, char **val)
+{
+ return -OP_ERROR_NOT_OPTION;
+}
+
+const struct output_plugin_ops op_pcm_ops = {
+ .init = op_arts_init,
+ .exit = op_arts_exit,
+ .open = op_arts_open,
+ .close = op_arts_close,
+ .write = op_arts_write,
+ .pause = op_arts_pause,
+ .unpause = op_arts_unpause,
+ .buffer_space = op_arts_buffer_space,
+ .set_option = op_arts_set_option,
+ .get_option = op_arts_get_option
+};
+
+const char * const op_pcm_options[] = {
+ NULL
+};
+
+const int op_priority = 2;
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "browser.h"
+#include "load_dir.h"
+#include "cmus.h"
+#include "xmalloc.h"
+#include "ui_curses.h"
+#include "file.h"
+#include "misc.h"
+#include "options.h"
+#include "uchar.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+struct window *browser_win;
+struct searchable *browser_searchable;
+char *browser_dir;
+
+static LIST_HEAD(browser_head);
+
+static inline void browser_entry_to_iter(struct browser_entry *e, struct iter *iter)
+{
+ iter->data0 = &browser_head;
+ iter->data1 = e;
+ iter->data2 = NULL;
+}
+
+/* filter out names starting with '.' except '..' */
+static int normal_filter(const char *name, const struct stat *s)
+{
+ if (name[0] == '.') {
+ if (name[1] == '.' && name[2] == 0)
+ return 1;
+ return 0;
+ }
+ if (S_ISDIR(s->st_mode))
+ return 1;
+ return cmus_is_supported(name);
+}
+
+/* filter out '.' */
+static int hidden_filter(const char *name, const struct stat *s)
+{
+ if (name[0] == '.' && name[1] == 0)
+ return 0;
+ return 1;
+}
+
+/* only works for BROWSER_ENTRY_DIR and BROWSER_ENTRY_FILE */
+static int entry_cmp(const struct browser_entry *a, const struct browser_entry *b)
+{
+ if (a->type == BROWSER_ENTRY_DIR) {
+ if (b->type == BROWSER_ENTRY_FILE)
+ return -1;
+ if (!strcmp(a->name, "../"))
+ return -1;
+ if (!strcmp(b->name, "../"))
+ return 1;
+ return strcmp(a->name, b->name);
+ }
+ if (b->type == BROWSER_ENTRY_DIR)
+ return 1;
+ return strcmp(a->name, b->name);
+}
+
+static char *fullname(const char *path, const char *name)
+{
+ int l1, l2;
+ char *full;
+
+ l1 = strlen(path);
+ l2 = strlen(name);
+ if (path[l1 - 1] == '/')
+ l1--;
+ full = xnew(char, l1 + 1 + l2 + 1);
+ memcpy(full, path, l1);
+ full[l1] = '/';
+ memcpy(full + l1 + 1, name, l2 + 1);
+ return full;
+}
+
+static void free_browser_list(void)
+{
+ struct list_head *item;
+
+ item = browser_head.next;
+ while (item != &browser_head) {
+ struct list_head *next = item->next;
+ struct browser_entry *entry;
+
+ entry = list_entry(item, struct browser_entry, node);
+ free(entry);
+ item = next;
+ }
+ list_init(&browser_head);
+}
+
+static int add_pl_line(void *data, const char *line)
+{
+ struct browser_entry *e;
+ int name_size = strlen(line) + 1;
+
+ e = xmalloc(sizeof(struct browser_entry) + name_size);
+ memcpy(e->name, line, name_size);
+ e->type = BROWSER_ENTRY_PLLINE;
+ list_add_tail(&e->node, &browser_head);
+ return 0;
+}
+
+static int do_browser_load(const char *name)
+{
+ struct stat st;
+
+ if (stat(name, &st))
+ return -1;
+
+ if (S_ISREG(st.st_mode) && cmus_is_playlist(name)) {
+ char *buf;
+ int size;
+
+ buf = mmap_file(name, &size);
+ if (size == -1)
+ return -1;
+
+ free_browser_list();
+
+ if (buf) {
+ struct browser_entry *parent_dir_e = xmalloc(sizeof(struct browser_entry) + 4);
+ strcpy(parent_dir_e->name, "../");
+ parent_dir_e->type = BROWSER_ENTRY_DIR;
+ list_add_tail(&parent_dir_e->node, &browser_head);
+
+ cmus_playlist_for_each(buf, size, 0, add_pl_line, NULL);
+ munmap(buf, size);
+ }
+ } else if (S_ISDIR(st.st_mode)) {
+ int (*filter)(const char *, const struct stat *) = normal_filter;
+ struct directory dir;
+ const char *str;
+ int root = !strcmp(name, "/");
+
+ if (show_hidden)
+ filter = hidden_filter;
+
+ if (dir_open(&dir, name))
+ return -1;
+
+ free_browser_list();
+ while ((str = dir_read(&dir))) {
+ struct browser_entry *e;
+ struct list_head *item;
+ int len;
+
+ if (!filter(str, &dir.st))
+ continue;
+
+ /* ignore .. if we are in the root dir */
+ if (root && !strcmp(str, ".."))
+ continue;
+
+ len = strlen(str);
+ e = xmalloc(sizeof(struct browser_entry) + len + 2);
+ e->type = BROWSER_ENTRY_FILE;
+ memcpy(e->name, str, len);
+ if (S_ISDIR(dir.st.st_mode)) {
+ e->type = BROWSER_ENTRY_DIR;
+ e->name[len++] = '/';
+ }
+ e->name[len] = 0;
+
+ item = browser_head.prev;
+ while (item != &browser_head) {
+ struct browser_entry *other;
+
+ other = container_of(item, struct browser_entry, node);
+ if (entry_cmp(e, other) >= 0)
+ break;
+ item = item->prev;
+ }
+ /* add after item */
+ list_add(&e->node, item);
+ }
+ dir_close(&dir);
+
+ /* try to update currect working directory */
+ if (chdir(name))
+ return -1;
+ } else {
+ errno = ENOTDIR;
+ return -1;
+ }
+ return 0;
+}
+
+static int browser_load(const char *name)
+{
+ int rc;
+
+ rc = do_browser_load(name);
+ if (rc)
+ return rc;
+
+ window_set_contents(browser_win, &browser_head);
+ free(browser_dir);
+ browser_dir = xstrdup(name);
+ return 0;
+}
+
+static GENERIC_ITER_PREV(browser_get_prev, struct browser_entry, node)
+static GENERIC_ITER_NEXT(browser_get_next, struct browser_entry, node)
+
+static int browser_search_get_current(void *data, struct iter *iter)
+{
+ return window_get_sel(browser_win, iter);
+}
+
+static int browser_search_matches(void *data, struct iter *iter, const char *text)
+{
+ char **words = get_words(text);
+ int matched = 0;
+
+ if (words[0] != NULL) {
+ struct browser_entry *e;
+ int i;
+
+ e = iter_to_browser_entry(iter);
+ for (i = 0; ; i++) {
+ if (words[i] == NULL) {
+ window_set_sel(browser_win, iter);
+ matched = 1;
+ break;
+ }
+ if (u_strcasestr_filename(e->name, words[i]) == NULL)
+ break;
+ }
+ }
+ free_str_array(words);
+ return matched;
+}
+
+static const struct searchable_ops browser_search_ops = {
+ .get_prev = browser_get_prev,
+ .get_next = browser_get_next,
+ .get_current = browser_search_get_current,
+ .matches = browser_search_matches
+};
+
+void browser_init(void)
+{
+ struct iter iter;
+ char cwd[1024];
+ char *dir;
+
+ if (getcwd(cwd, sizeof(cwd)) == NULL) {
+ dir = xstrdup("/");
+ } else {
+ dir = xstrdup(cwd);
+ }
+ if (do_browser_load(dir)) {
+ free(dir);
+ do_browser_load("/");
+ browser_dir = xstrdup("/");
+ } else {
+ browser_dir = dir;
+ }
+
+ browser_win = window_new(browser_get_prev, browser_get_next);
+ window_set_contents(browser_win, &browser_head);
+ window_changed(browser_win);
+
+ iter.data0 = &browser_head;
+ iter.data1 = NULL;
+ iter.data2 = NULL;
+ browser_searchable = searchable_new(NULL, &iter, &browser_search_ops);
+}
+
+void browser_exit(void)
+{
+ searchable_free(browser_searchable);
+ free_browser_list();
+ window_free(browser_win);
+ free(browser_dir);
+}
+
+int browser_chdir(const char *dir)
+{
+ if (browser_load(dir)) {
+ }
+ return 0;
+}
+
+void browser_up(void)
+{
+ char *new, *ptr, *pos;
+ struct browser_entry *e;
+ int len;
+
+ if (strcmp(browser_dir, "/") == 0)
+ return;
+
+ ptr = strrchr(browser_dir, '/');
+ if (ptr == browser_dir) {
+ new = xstrdup("/");
+ } else {
+ new = xstrndup(browser_dir, ptr - browser_dir);
+ }
+
+ /* remember last position */
+ ptr++;
+ len = strlen(ptr);
+ pos = xstrdup(ptr);
+
+ if (browser_load(new)) {
+ error_msg("could not open directory '%s': %s\n", new, strerror(errno));
+ free(new);
+ return;
+ }
+ free(new);
+
+ /* select */
+ list_for_each_entry(e, &browser_head, node) {
+ if (strncmp(e->name, pos, len) == 0 &&
+ (e->name[len] == '/' || e->name[len] == '\0')) {
+ struct iter iter;
+
+ browser_entry_to_iter(e, &iter);
+ window_set_sel(browser_win, &iter);
+ break;
+ }
+ }
+ free(pos);
+}
+
+static void browser_cd(const char *dir)
+{
+ char *new;
+ int len;
+
+ if (strcmp(dir, "../") == 0) {
+ browser_up();
+ return;
+ }
+
+ new = fullname(browser_dir, dir);
+ len = strlen(new);
+ if (new[len - 1] == '/')
+ new[len - 1] = 0;
+ if (browser_load(new))
+ error_msg("could not open directory '%s': %s\n", dir, strerror(errno));
+ free(new);
+}
+
+static void browser_cd_playlist(const char *filename)
+{
+ if (browser_load(filename))
+ error_msg("could not read playlist '%s': %s\n", filename, strerror(errno));
+}
+
+void browser_enter(void)
+{
+ struct browser_entry *e;
+ struct iter sel;
+ int len;
+
+ if (!window_get_sel(browser_win, &sel))
+ return;
+ e = iter_to_browser_entry(&sel);
+ len = strlen(e->name);
+ if (len == 0)
+ return;
+ if (e->type == BROWSER_ENTRY_DIR) {
+ browser_cd(e->name);
+ } else {
+ if (e->type == BROWSER_ENTRY_PLLINE) {
+ cmus_play_file(e->name);
+ } else {
+ char *filename;
+
+ filename = fullname(browser_dir, e->name);
+ if (cmus_is_playlist(filename)) {
+ browser_cd_playlist(filename);
+ } else {
+ cmus_play_file(filename);
+ }
+ free(filename);
+ }
+ }
+}
+
+char *browser_get_sel(void)
+{
+ struct browser_entry *e;
+ struct iter sel;
+
+ if (!window_get_sel(browser_win, &sel))
+ return NULL;
+
+ e = iter_to_browser_entry(&sel);
+ if (e->type == BROWSER_ENTRY_PLLINE)
+ return xstrdup(e->name);
+
+ return fullname(browser_dir, e->name);
+}
+
+void browser_delete(void)
+{
+ struct browser_entry *e;
+ struct iter sel;
+ int len;
+
+ if (!window_get_sel(browser_win, &sel))
+ return;
+ e = iter_to_browser_entry(&sel);
+ len = strlen(e->name);
+ if (len == 0)
+ return;
+ if (e->type == BROWSER_ENTRY_FILE) {
+ char *name;
+
+ name = fullname(browser_dir, e->name);
+ if (yes_no_query("Delete file '%s'? [y/N]", e->name)) {
+ if (unlink(name) == -1) {
+ error_msg("deleting '%s': %s", e->name, strerror(errno));
+ } else {
+ window_row_vanishes(browser_win, &sel);
+ list_del(&e->node);
+ free(e);
+ }
+ }
+ free(name);
+ }
+}
+
+void browser_reload(void)
+{
+ char *tmp = xstrdup(browser_dir);
+ char *sel = NULL;
+ struct iter iter;
+ struct browser_entry *e;
+
+ /* remember selection */
+ if (window_get_sel(browser_win, &iter)) {
+ e = iter_to_browser_entry(&iter);
+ sel = xstrdup(e->name);
+ }
+
+ /* have to use tmp */
+ if (browser_load(tmp)) {
+ error_msg("could not update contents '%s': %s\n", tmp, strerror(errno));
+ free(tmp);
+ free(sel);
+ return;
+ }
+
+ if (sel) {
+ /* set selection */
+ list_for_each_entry(e, &browser_head, node) {
+ if (strcmp(e->name, sel) == 0) {
+ browser_entry_to_iter(e, &iter);
+ window_set_sel(browser_win, &iter);
+ break;
+ }
+ }
+ }
+
+ free(tmp);
+ free(sel);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _BROWSER_H
+#define _BROWSER_H
+
+#include "list.h"
+#include "window.h"
+#include "search.h"
+
+struct browser_entry {
+ struct list_head node;
+
+ enum { BROWSER_ENTRY_DIR, BROWSER_ENTRY_FILE, BROWSER_ENTRY_PLLINE } type;
+ char name[];
+};
+
+static inline struct browser_entry *iter_to_browser_entry(struct iter *iter)
+{
+ return iter->data1;
+}
+
+extern struct window *browser_win;
+extern char *browser_dir;
+extern struct searchable *browser_searchable;
+
+void browser_init(void);
+void browser_exit(void);
+int browser_chdir(const char *dir);
+char *browser_get_sel(void);
+void browser_up(void);
+void browser_enter(void);
+void browser_delete(void);
+void browser_reload(void);
+void browser_toggle_show_hidden(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "buffer.h"
+#include "xmalloc.h"
+#include "locking.h"
+#include "debug.h"
+
+/*
+ * chunk can be accessed by either consumer OR producer, not both at same time
+ * -> no need to lock
+ */
+struct chunk {
+ char data[CHUNK_SIZE];
+
+ /* index to data, first filled byte */
+ unsigned int l;
+
+ /* index to data, last filled byte + 1
+ *
+ * there are h - l bytes available (filled)
+ */
+ unsigned int h : 31;
+
+ /* if chunk is marked filled it can only be accessed by consumer
+ * otherwise only producer is allowed to access the chunk
+ */
+ unsigned int filled : 1;
+};
+
+unsigned int buffer_nr_chunks;
+
+static pthread_mutex_t buffer_mutex = CMUS_MUTEX_INITIALIZER;
+static struct chunk *buffer_chunks = NULL;
+static unsigned int buffer_ridx;
+static unsigned int buffer_widx;
+
+void buffer_init(void)
+{
+ free(buffer_chunks);
+ buffer_chunks = xnew(struct chunk, buffer_nr_chunks);
+ buffer_reset();
+}
+
+void buffer_free(void)
+{
+ free(buffer_chunks);
+}
+
+/*
+ * @pos: returned pointer to available data
+ *
+ * Returns number of bytes available at @pos
+ *
+ * After reading bytes mark them consumed calling buffer_consume().
+ */
+int buffer_get_rpos(char **pos)
+{
+ struct chunk *c;
+ int size = 0;
+
+ cmus_mutex_lock(&buffer_mutex);
+ c = &buffer_chunks[buffer_ridx];
+ if (c->filled) {
+ size = c->h - c->l;
+ *pos = c->data + c->l;
+ }
+ cmus_mutex_unlock(&buffer_mutex);
+
+ return size;
+}
+
+/*
+ * @pos: pointer to buffer position where data can be written
+ *
+ * Returns number of bytes can be written to @pos. If the return value is
+ * non-zero it is guaranteed to be >= 1024.
+ *
+ * After writing bytes mark them filled calling buffer_fill().
+ */
+int buffer_get_wpos(char **pos)
+{
+ struct chunk *c;
+ int size = 0;
+
+ cmus_mutex_lock(&buffer_mutex);
+ c = &buffer_chunks[buffer_widx];
+ if (!c->filled) {
+ size = CHUNK_SIZE - c->h;
+ *pos = c->data + c->h;
+ }
+ cmus_mutex_unlock(&buffer_mutex);
+
+ return size;
+}
+
+void buffer_consume(int count)
+{
+ struct chunk *c;
+
+ BUG_ON(count < 0);
+ cmus_mutex_lock(&buffer_mutex);
+ c = &buffer_chunks[buffer_ridx];
+ BUG_ON(!c->filled);
+ c->l += count;
+ if (c->l == c->h) {
+ c->l = 0;
+ c->h = 0;
+ c->filled = 0;
+ buffer_ridx++;
+ buffer_ridx %= buffer_nr_chunks;
+ }
+ cmus_mutex_unlock(&buffer_mutex);
+}
+
+/* chunk is marked filled if free bytes < 1024 or count == 0 */
+int buffer_fill(int count)
+{
+ struct chunk *c;
+ int filled = 0;
+
+ cmus_mutex_lock(&buffer_mutex);
+ c = &buffer_chunks[buffer_widx];
+ BUG_ON(c->filled);
+ c->h += count;
+
+ if (CHUNK_SIZE - c->h < 1024 || (count == 0 && c->h > 0)) {
+ c->filled = 1;
+ buffer_widx++;
+ buffer_widx %= buffer_nr_chunks;
+ filled = 1;
+ }
+
+ cmus_mutex_unlock(&buffer_mutex);
+ return filled;
+}
+
+void buffer_reset(void)
+{
+ int i;
+
+ cmus_mutex_lock(&buffer_mutex);
+ buffer_ridx = 0;
+ buffer_widx = 0;
+ for (i = 0; i < buffer_nr_chunks; i++) {
+ buffer_chunks[i].l = 0;
+ buffer_chunks[i].h = 0;
+ buffer_chunks[i].filled = 0;
+ }
+ cmus_mutex_unlock(&buffer_mutex);
+}
+
+int buffer_get_filled_chunks(void)
+{
+ int c;
+
+ cmus_mutex_lock(&buffer_mutex);
+ if (buffer_ridx < buffer_widx) {
+ /*
+ * |__##########____|
+ * r w
+ *
+ * |############____|
+ * r w
+ */
+ c = buffer_widx - buffer_ridx;
+ } else if (buffer_ridx > buffer_widx) {
+ /*
+ * |#######______###|
+ * w r
+ *
+ * |_____________###|
+ * w r
+ */
+ c = buffer_nr_chunks - buffer_ridx + buffer_widx;
+ } else {
+ /*
+ * |################|
+ * r
+ * w
+ *
+ * |________________|
+ * r
+ * w
+ */
+ if (buffer_chunks[buffer_ridx].filled) {
+ c = buffer_nr_chunks;
+ } else {
+ c = 0;
+ }
+ }
+ cmus_mutex_unlock(&buffer_mutex);
+ return c;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _BUFFER_H
+#define _BUFFER_H
+
+/* must be a multiple of any supported frame size */
+#define CHUNK_SIZE (60 * 1024)
+
+extern unsigned int buffer_nr_chunks;
+
+void buffer_init(void);
+void buffer_free(void);
+int buffer_get_rpos(char **pos);
+int buffer_get_wpos(char **pos);
+void buffer_consume(int count);
+int buffer_fill(int count);
+void buffer_reset(void);
+int buffer_get_filled_chunks(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "cache.h"
+#include "misc.h"
+#include "file.h"
+#include "input.h"
+#include "track_info.h"
+#include "utils.h"
+#include "xmalloc.h"
+#include "xstrjoin.h"
+#include "gbuf.h"
+#include "options.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+#define CACHE_64_BIT 0x01
+#define CACHE_BE 0x02
+
+// Cmus Track Cache version X + 4 bytes flags
+static char cache_header[8] = "CTC\0\0\0\0\0";
+
+// host byte order
+// mtime is either 32 or 64 bits
+struct cache_entry {
+ // size of this struct including size itself
+ // NOTE: size does not include padding bytes
+ unsigned int size;
+ int duration;
+ long bitrate;
+ time_t mtime;
+
+ // filename, codec, codec_profile and N * (key, val)
+ char strings[];
+};
+
+#define ALIGN(size) (((size) + sizeof(long) - 1) & ~(sizeof(long) - 1))
+#define HASH_SIZE 1023
+
+static struct track_info *hash_table[HASH_SIZE];
+static char *cache_filename;
+static int total;
+static int removed;
+static int new;
+
+pthread_mutex_t cache_mutex = CMUS_MUTEX_INITIALIZER;
+
+static void add_ti(struct track_info *ti, unsigned int hash)
+{
+ unsigned int pos = hash % HASH_SIZE;
+ struct track_info *next = hash_table[pos];
+
+ ti->next = next;
+ hash_table[pos] = ti;
+ total++;
+}
+
+static int valid_cache_entry(const struct cache_entry *e, unsigned int avail)
+{
+ unsigned int min_size = sizeof(*e);
+ unsigned int str_size;
+ int i, count;
+
+ if (avail < min_size)
+ return 0;
+
+ if (e->size < min_size || e->size > avail)
+ return 0;
+
+ str_size = e->size - min_size;
+ count = 0;
+ for (i = 0; i < str_size; i++) {
+ if (!e->strings[i])
+ count++;
+ }
+ if (count % 2 == 0)
+ return 0;
+ if (e->strings[str_size - 1])
+ return 0;
+ return 1;
+}
+
+static struct track_info *cache_entry_to_ti(struct cache_entry *e)
+{
+ const char *strings = e->strings;
+ struct track_info *ti = track_info_new(strings);
+ struct keyval *kv;
+ int str_size = e->size - sizeof(*e);
+ int pos, i, count;
+
+ ti->duration = e->duration;
+ ti->bitrate = e->bitrate;
+ ti->mtime = e->mtime;
+
+ // count strings (filename + codec + codec_profile + key/val pairs)
+ count = 0;
+ for (i = 0; i < str_size; i++) {
+ if (!strings[i])
+ count++;
+ }
+ count = (count - 3) / 2;
+
+ // NOTE: filename already copied by track_info_new()
+ pos = strlen(strings) + 1;
+ ti->codec = strings[pos] ? xstrdup(strings + pos) : NULL;
+ pos += strlen(strings + pos) + 1;
+ ti->codec_profile = strings[pos] ? xstrdup(strings + pos) : NULL;
+ pos += strlen(strings + pos) + 1;
+ kv = xnew(struct keyval, count + 1);
+ for (i = 0; i < count; i++) {
+ int size;
+
+ size = strlen(strings + pos) + 1;
+ kv[i].key = xstrdup(strings + pos);
+ pos += size;
+
+ size = strlen(strings + pos) + 1;
+ kv[i].val = xstrdup(strings + pos);
+ pos += size;
+ }
+ kv[i].key = NULL;
+ kv[i].val = NULL;
+ track_info_set_comments(ti, kv);
+ return ti;
+}
+
+struct track_info *lookup_cache_entry(const char *filename, unsigned int hash)
+{
+ struct track_info *ti = hash_table[hash % HASH_SIZE];
+
+ while (ti) {
+ if (!strcmp(filename, ti->filename))
+ return ti;
+ ti = ti->next;
+ }
+ return NULL;
+}
+
+static void do_cache_remove_ti(struct track_info *ti, unsigned int hash)
+{
+ unsigned int pos = hash % HASH_SIZE;
+ struct track_info *t = hash_table[pos];
+ struct track_info *next, *prev = NULL;
+
+ while (t) {
+ next = t->next;
+ if (t == ti) {
+ if (prev) {
+ prev->next = next;
+ } else {
+ hash_table[pos] = next;
+ }
+ total--;
+ removed++;
+ track_info_unref(ti);
+ return;
+ }
+ prev = t;
+ t = next;
+ }
+}
+
+void cache_remove_ti(struct track_info *ti)
+{
+ do_cache_remove_ti(ti, hash_str(ti->filename));
+}
+
+static int read_cache(void)
+{
+ unsigned int size, offset = 0;
+ struct stat st;
+ char *buf;
+ int fd;
+
+ fd = open(cache_filename, O_RDONLY);
+ if (fd < 0) {
+ if (errno == ENOENT)
+ return 0;
+ return -1;
+ }
+ fstat(fd, &st);
+ if (st.st_size < sizeof(cache_header))
+ goto close;
+ size = st.st_size;
+
+ buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (buf == MAP_FAILED) {
+ close(fd);
+ return -1;
+ }
+
+ if (memcmp(buf, cache_header, sizeof(cache_header)))
+ goto corrupt;
+
+ offset = sizeof(cache_header);
+ while (offset < size) {
+ struct cache_entry *e = (struct cache_entry *)(buf + offset);
+ struct track_info *ti;
+
+ if (!valid_cache_entry(e, size - offset))
+ goto corrupt;
+
+ ti = cache_entry_to_ti(e);
+ add_ti(ti, hash_str(ti->filename));
+ offset += ALIGN(e->size);
+ }
+ munmap(buf, size);
+ close(fd);
+ return 0;
+corrupt:
+ munmap(buf, size);
+close:
+ close(fd);
+ // corrupt
+ return -2;
+}
+
+int cache_init(void)
+{
+ unsigned int flags = 0;
+
+#ifdef WORDS_BIGENDIAN
+ flags |= CACHE_BE;
+#endif
+ if (sizeof(long) == 8)
+ flags |= CACHE_64_BIT;
+
+ cache_header[7] = flags & 0xff; flags >>= 8;
+ cache_header[6] = flags & 0xff; flags >>= 8;
+ cache_header[5] = flags & 0xff; flags >>= 8;
+ cache_header[4] = flags & 0xff;
+
+ /* assumed version */
+ cache_header[3] = 0x07;
+
+ cache_filename = xstrjoin(cmus_config_dir, "/cache");
+ return read_cache();
+}
+
+static int ti_filename_cmp(const void *a, const void *b)
+{
+ const struct track_info *ai = *(const struct track_info **)a;
+ const struct track_info *bi = *(const struct track_info **)b;
+
+ return strcmp(ai->filename, bi->filename);
+}
+
+static struct track_info **get_track_infos(void)
+{
+ struct track_info **tis;
+ int i, c;
+
+ tis = xnew(struct track_info *, total);
+ c = 0;
+ for (i = 0; i < HASH_SIZE; i++) {
+ struct track_info *ti = hash_table[i];
+
+ while (ti) {
+ tis[c++] = ti;
+ ti = ti->next;
+ }
+ }
+ qsort(tis, total, sizeof(struct track_info *), ti_filename_cmp);
+ return tis;
+}
+
+static void flush_buffer(int fd, struct gbuf *buf)
+{
+ if (buf->len) {
+ write_all(fd, buf->buffer, buf->len);
+ gbuf_clear(buf);
+ }
+}
+
+static void write_ti(int fd, struct gbuf *buf, struct track_info *ti, unsigned int *offsetp)
+{
+ const struct keyval *kv = ti->comments;
+ unsigned int offset = *offsetp;
+ unsigned int pad;
+ struct cache_entry e;
+ int *len, alloc = 64, count, i;
+
+ count = 0;
+ len = xnew(int, alloc);
+ e.size = sizeof(e);
+ e.duration = ti->duration;
+ e.bitrate = ti->bitrate;
+ e.mtime = ti->mtime;
+ len[count] = strlen(ti->filename) + 1;
+ e.size += len[count++];
+ len[count] = (ti->codec ? strlen(ti->codec) : 0) + 1;
+ e.size += len[count++];
+ len[count] = (ti->codec_profile ? strlen(ti->codec_profile) : 0) + 1;
+ e.size += len[count++];
+ for (i = 0; kv[i].key; i++) {
+ if (count + 2 > alloc) {
+ alloc *= 2;
+ len = xrenew(int, len, alloc);
+ }
+ len[count] = strlen(kv[i].key) + 1;
+ e.size += len[count++];
+ len[count] = strlen(kv[i].val) + 1;
+ e.size += len[count++];
+ }
+
+ pad = ALIGN(offset) - offset;
+ if (gbuf_avail(buf) < pad + e.size)
+ flush_buffer(fd, buf);
+
+ count = 0;
+ if (pad)
+ gbuf_set(buf, 0, pad);
+ gbuf_add_bytes(buf, &e, sizeof(e));
+ gbuf_add_bytes(buf, ti->filename, len[count++]);
+ gbuf_add_bytes(buf, ti->codec ? ti->codec : "", len[count++]);
+ gbuf_add_bytes(buf, ti->codec_profile ? ti->codec_profile : "", len[count++]);
+ for (i = 0; kv[i].key; i++) {
+ gbuf_add_bytes(buf, kv[i].key, len[count++]);
+ gbuf_add_bytes(buf, kv[i].val, len[count++]);
+ }
+
+ free(len);
+ *offsetp = offset + pad + e.size;
+}
+
+int cache_close(void)
+{
+ GBUF(buf);
+ struct track_info **tis;
+ unsigned int offset;
+ int i, fd, rc;
+ char *tmp;
+
+ if (!new && !removed)
+ return 0;
+
+ tmp = xstrjoin(cmus_config_dir, "/cache.tmp");
+ fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (fd < 0) {
+ free(tmp);
+ return -1;
+ }
+
+ tis = get_track_infos();
+
+ gbuf_grow(&buf, 64 * 1024 - 1);
+ gbuf_add_bytes(&buf, cache_header, sizeof(cache_header));
+ offset = sizeof(cache_header);
+ for (i = 0; i < total; i++)
+ write_ti(fd, &buf, tis[i], &offset);
+ flush_buffer(fd, &buf);
+ gbuf_free(&buf);
+ free(tis);
+
+ close(fd);
+ rc = rename(tmp, cache_filename);
+ free(tmp);
+ return rc;
+}
+
+static struct track_info *ip_get_ti(const char *filename)
+{
+ struct track_info *ti = NULL;
+ struct input_plugin *ip;
+ struct keyval *comments;
+ int rc;
+
+ ip = ip_new(filename);
+ rc = ip_open(ip);
+ if (rc) {
+ ip_delete(ip);
+ return NULL;
+ }
+
+ rc = ip_read_comments(ip, &comments);
+ if (!rc) {
+ ti = track_info_new(filename);
+ track_info_set_comments(ti, comments);
+ ti->duration = ip_duration(ip);
+ ti->bitrate = ip_bitrate(ip);
+ ti->codec = ip_codec(ip);
+ ti->codec_profile = ip_codec_profile(ip);
+ ti->mtime = ip_is_remote(ip) ? -1 : file_get_mtime(filename);
+ }
+ ip_delete(ip);
+ return ti;
+}
+
+struct track_info *cache_get_ti(const char *filename, int force)
+{
+ unsigned int hash = hash_str(filename);
+ struct track_info *ti;
+ int reload = 0;
+
+ ti = lookup_cache_entry(filename, hash);
+ if (ti) {
+ if ((!skip_track_info && ti->duration == 0 && !is_http_url(filename)) || force){
+ do_cache_remove_ti(ti, hash);
+ ti = NULL;
+ reload = 1;
+ }
+ }
+ if (!ti) {
+ if (skip_track_info && !reload && !force) {
+ struct growing_keyvals c = {NULL, 0, 0};
+
+ ti = track_info_new(filename);
+
+ keyvals_terminate(&c);
+ track_info_set_comments(ti, c.keyvals);
+
+ ti->duration = 0;
+ } else {
+ ti = ip_get_ti(filename);
+ }
+ if (!ti)
+ return NULL;
+ add_ti(ti, hash);
+ new++;
+ }
+ track_info_ref(ti);
+ return ti;
+}
+
+struct track_info **cache_refresh(int *count, int force)
+{
+ struct track_info **tis = get_track_infos();
+ int i, n = total;
+
+ for (i = 0; i < n; i++) {
+ unsigned int hash;
+ struct track_info *ti = tis[i];
+ struct stat st;
+ int rc = 0;
+
+ /*
+ * If no-one else has reference to tis[i] then it is set to NULL
+ * otherwise:
+ *
+ * unchanged: tis[i] = NULL
+ * deleted: tis[i]->next = NULL
+ * changed: tis[i]->next = new
+ */
+
+ if (!is_url(ti->filename)) {
+ rc = stat(ti->filename, &st);
+ if (!rc && !force && ti->mtime == st.st_mtime) {
+ // unchanged
+ tis[i] = NULL;
+ continue;
+ }
+ }
+
+ hash = hash_str(ti->filename);
+ track_info_ref(ti);
+ do_cache_remove_ti(ti, hash);
+
+ if (!rc) {
+ // changed
+ struct track_info *new_ti;
+
+ // clear cache-only entries
+ if (force && ti->ref == 1) {
+ track_info_unref(ti);
+ tis[i] = NULL;
+ continue;
+ }
+
+ new_ti = ip_get_ti(ti->filename);
+ if (new_ti) {
+ add_ti(new_ti, hash);
+ new++;
+
+ if (ti->ref == 1) {
+ track_info_unref(ti);
+ tis[i] = NULL;
+ } else {
+ track_info_ref(new_ti);
+ ti->next = new_ti;
+ }
+ continue;
+ }
+ // treat as deleted
+ }
+
+ // deleted
+ if (ti->ref == 1) {
+ track_info_unref(ti);
+ tis[i] = NULL;
+ } else {
+ ti->next = NULL;
+ }
+ }
+ *count = n;
+ return tis;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CACHE_H
+#define CACHE_H
+
+#include "track_info.h"
+#include "locking.h"
+
+extern pthread_mutex_t cache_mutex;
+
+#define cache_lock() cmus_mutex_lock(&cache_mutex)
+#define cache_unlock() cmus_mutex_unlock(&cache_mutex)
+
+int cache_init(void);
+int cache_close(void);
+struct track_info *cache_get_ti(const char *filename, int force);
+void cache_remove_ti(struct track_info *ti);
+struct track_info **cache_refresh(int *count, int force);
+struct track_info *lookup_cache_entry(const char *filename, unsigned int hash);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2011 Various Authors
+ * Copyright 2011 Johannes Weißl
+ *
+ * Based on cdda.c from XMMS2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "file.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "utils.h"
+#include "options.h"
+#include "comment.h"
+#include "discid.h"
+
+#include <cdio/cdda.h>
+#include <cdio/cdio.h>
+#include <cdio/logging.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#undef HAVE_CDDB
+
+#ifdef HAVE_CONFIG
+#include "config/cdio.h"
+#endif
+
+#ifdef HAVE_CDDB
+#include "http.h"
+#include "xstrjoin.h"
+#include <cddb/cddb.h>
+#endif
+
+#ifdef HAVE_CDDB
+static char *cddb_url = NULL;
+#endif
+
+static struct {
+ CdIo_t *cdio;
+ cdrom_drive_t *drive;
+ const char *disc_id;
+ const char *device;
+} cached;
+
+struct cdda_private {
+ CdIo_t *cdio;
+ cdrom_drive_t *drive;
+ char *disc_id;
+ char *device;
+ track_t track;
+ lsn_t first_lsn;
+ lsn_t last_lsn;
+ lsn_t current_lsn;
+ int first_read;
+
+ char read_buf[CDIO_CD_FRAMESIZE_RAW];
+ unsigned long buf_used;
+};
+
+static void libcdio_log(cdio_log_level_t level, const char *message)
+{
+ const char *level_names[] = { "DEBUG", "INFO", "WARN", "ERROR", "ASSERT" };
+ int len = strlen(message);
+ if (len > 0 && message[len-1] == '\n')
+ len--;
+ if (len > 0) {
+ level = clamp(level, 1, N_ELEMENTS(level_names));
+ d_print("%s: %.*s\n", level_names[level-1], len, message);
+ }
+}
+
+static int libcdio_open(struct input_plugin_data *ip_data)
+{
+ struct cdda_private *priv, priv_init = {
+ .first_read = 1,
+ .buf_used = CDIO_CD_FRAMESIZE_RAW
+ };
+ CdIo_t *cdio = NULL;
+ cdrom_drive_t *drive = NULL;
+ const char *device = cdda_device;
+ lsn_t first_lsn;
+ int track = -1;
+ char *disc_id = NULL;
+ char *msg = NULL;
+ int rc = 0, save = 0;
+
+ if (!parse_cdda_url(ip_data->filename, &disc_id, &track, NULL)) {
+ rc = -IP_ERROR_INVALID_URI;
+ goto end;
+ }
+
+ if (track == -1) {
+ d_print("invalid or missing track number, aborting!\n");
+ rc = -IP_ERROR_INVALID_URI;
+ goto end;
+ }
+
+ /* In case of cue/toc/nrg, take filename (= disc_id) as device.
+ * A real disc_id is base64 encoded and never contains a slash */
+ if (strchr(disc_id, '/'))
+ device = disc_id;
+
+ ip_data->fd = open(device, O_RDONLY);
+ if (ip_data->fd == -1) {
+ save = errno;
+ d_print("could not open device %s\n", device);
+ rc = -IP_ERROR_ERRNO;
+ goto end;
+ }
+
+ if (cached.cdio && strcmp(disc_id, cached.disc_id) == 0 && strcmp(device, cached.device) == 0) {
+ cdio = cached.cdio;
+ drive = cached.drive;
+ } else {
+ cdio_log_set_handler(libcdio_log);
+ cdio = cdio_open(device, DRIVER_UNKNOWN);
+ if (!cdio) {
+ d_print("failed to open device %s\n", device);
+ rc = -IP_ERROR_NO_DISC;
+ goto end;
+ }
+ cdio_set_speed(cdio, 1);
+
+ drive = cdio_cddap_identify_cdio(cdio, CDDA_MESSAGE_LOGIT, &msg);
+ if (!drive) {
+ d_print("failed to identify drive, aborting!\n");
+ rc = -IP_ERROR_NO_DISC;
+ goto end;
+ }
+ d_print("%s", msg);
+ cdio_cddap_verbose_set(drive, CDDA_MESSAGE_LOGIT, CDDA_MESSAGE_LOGIT);
+ drive->b_swap_bytes = 1;
+
+ if (cdio_cddap_open(drive)) {
+ d_print("unable to open disc, aborting!\n");
+ rc = -IP_ERROR_NO_DISC;
+ goto end;
+ }
+ }
+
+ first_lsn = cdio_cddap_track_firstsector(drive, track);
+ if (first_lsn == -1) {
+ d_print("no such track: %d, aborting!\n", track);
+ rc = -IP_ERROR_INVALID_URI;
+ goto end;
+ }
+
+ priv = xnew(struct cdda_private, 1);
+ *priv = priv_init;
+ priv->cdio = cdio;
+ priv->drive = drive;
+ priv->disc_id = xstrdup(disc_id);
+ priv->device = xstrdup(device);
+ priv->track = track;
+ priv->first_lsn = first_lsn;
+ priv->last_lsn = cdio_cddap_track_lastsector(drive, priv->track);
+ priv->current_lsn = first_lsn;
+
+ cached.cdio = priv->cdio;
+ cached.drive = priv->drive;
+ cached.disc_id = priv->disc_id;
+ cached.device = priv->device;
+
+ ip_data->private = priv;
+ ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
+#ifdef WORDS_BIGENDIAN
+ ip_data->sf |= sf_bigendian(1);
+#endif
+
+end:
+ free(disc_id);
+
+ if (rc < 0) {
+ if (ip_data->fd != -1)
+ close(ip_data->fd);
+ ip_data->fd = -1;
+ }
+
+ if (rc == -IP_ERROR_ERRNO)
+ errno = save;
+ return rc;
+}
+
+static int libcdio_close(struct input_plugin_data *ip_data)
+{
+ struct cdda_private *priv = ip_data->private;
+
+ if (ip_data->fd != -1)
+ close(ip_data->fd);
+ ip_data->fd = -1;
+
+ if (strcmp(priv->disc_id, cached.disc_id) != 0 || strcmp(priv->device, cached.device) != 0) {
+ cdio_cddap_close_no_free_cdio(priv->drive);
+ cdio_destroy(priv->cdio);
+ free(priv->disc_id);
+ free(priv->device);
+ }
+
+ free(priv);
+ ip_data->private = NULL;
+ return 0;
+}
+
+static int libcdio_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct cdda_private *priv = ip_data->private;
+ int rc = 0;
+
+ if (priv->first_read || cdio_get_media_changed(priv->cdio)) {
+ char *disc_id;
+ priv->first_read = 0;
+ if (!get_disc_id(priv->device, &disc_id, NULL))
+ return -IP_ERROR_NO_DISC;
+ if (strcmp(disc_id, priv->disc_id) != 0) {
+ free(disc_id);
+ return -IP_ERROR_WRONG_DISC;
+ }
+ free(disc_id);
+ }
+
+ if (priv->current_lsn >= priv->last_lsn)
+ return 0;
+
+ if (priv->buf_used == CDIO_CD_FRAMESIZE_RAW) {
+ cdio_cddap_read(priv->drive, priv->read_buf, priv->current_lsn, 1);
+ priv->current_lsn++;
+ priv->buf_used = 0;
+ }
+
+ if (count >= CDIO_CD_FRAMESIZE_RAW) {
+ rc = CDIO_CD_FRAMESIZE_RAW - priv->buf_used;
+ memcpy(buffer, priv->read_buf + priv->buf_used, rc);
+ } else {
+ unsigned long buf_left = CDIO_CD_FRAMESIZE_RAW - priv->buf_used;
+
+ if (buf_left < count) {
+ memcpy(buffer, priv->read_buf + priv->buf_used, buf_left);
+ rc = buf_left;
+ } else {
+ memcpy(buffer, priv->read_buf + priv->buf_used, count);
+ rc = count;
+ }
+ }
+ priv->buf_used += rc;
+
+ return rc;
+}
+
+static int libcdio_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct cdda_private *priv = ip_data->private;
+ lsn_t new_lsn;
+ int64_t samples = offset * 44100;
+
+ /* Magic number 42... really should think of a better way to do this but
+ * it seemed that the lsn is off by about 42 everytime...
+ */
+ new_lsn = samples / 441.0 * CDIO_CD_FRAMES_PER_SEC / 100 + 42;
+
+ if ((priv->first_lsn + new_lsn) > priv->last_lsn) {
+ d_print("trying to seek past the end of track.\n");
+ return -1;
+ }
+
+ priv->current_lsn = priv->first_lsn + new_lsn;
+
+ return 0;
+}
+
+#ifdef HAVE_CDDB
+static int parse_cddb_url(const char *url, struct http_uri *http_uri, int *use_http)
+{
+ char *full_url;
+ int rc;
+
+ if (is_http_url(url)) {
+ *use_http = 1;
+ full_url = xstrdup(url);
+ } else {
+ *use_http = 0;
+ full_url = xstrjoin("http://", url);
+ }
+
+ rc = http_parse_uri(full_url, http_uri);
+ free(full_url);
+ return rc == 0;
+}
+
+static void setup_cddb_conn(cddb_conn_t *cddb_conn)
+{
+ struct http_uri http_uri, http_proxy_uri;
+ const char *proxy;
+ int use_http;
+
+ parse_cddb_url(cddb_url, &http_uri, &use_http);
+
+ proxy = getenv("http_proxy");
+ if (proxy && http_parse_uri(proxy, &http_proxy_uri) == 0) {
+ cddb_http_proxy_enable(cddb_conn);
+ cddb_set_http_proxy_server_name(cddb_conn, http_proxy_uri.host);
+ cddb_set_http_proxy_server_port(cddb_conn, http_proxy_uri.port);
+ if (http_proxy_uri.user)
+ cddb_set_http_proxy_username(cddb_conn, http_proxy_uri.user);
+ if (http_proxy_uri.pass)
+ cddb_set_http_proxy_password(cddb_conn, http_proxy_uri.pass);
+ http_free_uri(&http_proxy_uri);
+ } else
+ cddb_http_proxy_disable(cddb_conn);
+
+ if (use_http)
+ cddb_http_enable(cddb_conn);
+ else
+ cddb_http_disable(cddb_conn);
+
+ cddb_set_server_name(cddb_conn, http_uri.host);
+ cddb_set_email_address(cddb_conn, "me@home");
+ cddb_set_server_port(cddb_conn, http_uri.port);
+ if (strcmp(http_uri.path, "/") != 0)
+ cddb_set_http_path_query(cddb_conn, http_uri.path);
+#ifdef DEBUG_CDDB
+ cddb_cache_disable(cddb_conn);
+#endif
+
+ http_free_uri(&http_uri);
+}
+#endif
+
+
+#define add_comment(c, x) do { if (x) comments_add_const(c, #x, x); } while (0)
+
+static int libcdio_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
+{
+ struct cdda_private *priv = ip_data->private;
+ GROWING_KEYVALS(c);
+ const char *artist = NULL, *albumartist = NULL, *album = NULL,
+ *title = NULL, *genre = NULL, *comment = NULL;
+ const cdtext_t *cdtext_track, *cdtext_album;
+#ifdef HAVE_CDDB
+ cddb_conn_t *cddb_conn = NULL;
+ cddb_disc_t *cddb_disc = NULL;
+#endif
+ char buf[64];
+
+ cdtext_track = cdio_get_cdtext(priv->cdio, priv->track);
+ if (cdtext_track) {
+ char * const *field = cdtext_track->field;
+ artist = field[CDTEXT_PERFORMER];
+ title = field[CDTEXT_TITLE];
+ genre = field[CDTEXT_GENRE];
+ comment = field[CDTEXT_MESSAGE];
+ }
+ cdtext_album = cdio_get_cdtext(priv->cdio, 0);
+ if (cdtext_album) {
+ char * const *field = cdtext_album->field;
+ album = field[CDTEXT_TITLE];
+ albumartist = field[CDTEXT_PERFORMER];
+ if (!artist)
+ artist = field[CDTEXT_PERFORMER];
+ if (!genre)
+ genre = field[CDTEXT_GENRE];
+ if (!comment)
+ comment = field[CDTEXT_MESSAGE];
+ }
+
+#ifdef HAVE_CDDB
+ if (!cdtext_track && cddb_url && cddb_url[0]) {
+ cddb_track_t *cddb_track;
+ track_t i_tracks = cdio_get_num_tracks(priv->cdio);
+ track_t i_first_track = cdio_get_first_track_num(priv->cdio);
+ unsigned int year;
+ int i;
+
+ cddb_conn = cddb_new();
+ if (!cddb_conn)
+ malloc_fail();
+
+ setup_cddb_conn(cddb_conn);
+
+ cddb_disc = cddb_disc_new();
+ if (!cddb_disc)
+ malloc_fail();
+ for (i = 0; i < i_tracks; i++) {
+ cddb_track = cddb_track_new();
+ if (!cddb_track)
+ malloc_fail();
+ cddb_track_set_frame_offset(cddb_track,
+ cdio_get_track_lba(priv->cdio, i+i_first_track));
+ cddb_disc_add_track(cddb_disc, cddb_track);
+ }
+
+ cddb_disc_set_length(cddb_disc, cdio_get_track_lba(priv->cdio,
+ CDIO_CDROM_LEADOUT_TRACK) / CDIO_CD_FRAMES_PER_SEC);
+ if (cddb_query(cddb_conn, cddb_disc) == 1 && cddb_read(cddb_conn, cddb_disc)) {
+ albumartist = cddb_disc_get_artist(cddb_disc);
+ album = cddb_disc_get_title(cddb_disc);
+ genre = cddb_disc_get_genre(cddb_disc);
+ year = cddb_disc_get_year(cddb_disc);
+ if (year) {
+ sprintf(buf, "%u", year);
+ comments_add_const(&c, "date", buf);
+ }
+ cddb_track = cddb_disc_get_track(cddb_disc, priv->track - 1);
+ artist = cddb_track_get_artist(cddb_track);
+ if (!artist)
+ artist = albumartist;
+ title = cddb_track_get_title(cddb_track);
+ }
+ }
+#endif
+
+ add_comment(&c, artist);
+ add_comment(&c, albumartist);
+ add_comment(&c, album);
+ add_comment(&c, title);
+ add_comment(&c, genre);
+ add_comment(&c, comment);
+
+ sprintf(buf, "%02d", priv->track);
+ comments_add_const(&c, "tracknumber", buf);
+
+#ifdef HAVE_CDDB
+ if (cddb_disc)
+ cddb_disc_destroy(cddb_disc);
+ if (cddb_conn)
+ cddb_destroy(cddb_conn);
+#endif
+
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+ return 0;
+}
+
+static int libcdio_duration(struct input_plugin_data *ip_data)
+{
+ struct cdda_private *priv = ip_data->private;
+
+ return (priv->last_lsn - priv->first_lsn) / CDIO_CD_FRAMES_PER_SEC;
+}
+
+static long libcdio_bitrate(struct input_plugin_data *ip_data)
+{
+ return 44100 * 16 * 2;
+}
+
+static char *libcdio_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("cdda");
+}
+
+static char *libcdio_codec_profile(struct input_plugin_data *ip_data)
+{
+ struct cdda_private *priv = ip_data->private;
+ discmode_t cd_discmode = cdio_get_discmode(priv->cdio);
+
+ return xstrdup(discmode2str[cd_discmode]);
+}
+
+static int libcdio_set_option(int key, const char *val)
+{
+#ifdef HAVE_CDDB
+ struct http_uri http_uri;
+ int use_http;
+#endif
+ switch (key) {
+#ifdef HAVE_CDDB
+ case 0:
+ if (parse_cddb_url(val, &http_uri, &use_http)) {
+ http_free_uri(&http_uri);
+ free(cddb_url);
+ cddb_url = xstrdup(val);
+ } else
+ return -IP_ERROR_INVALID_URI;
+ break;
+#endif
+ default:
+ return -IP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+static int libcdio_get_option(int key, char **val)
+{
+ switch (key) {
+#ifdef HAVE_CDDB
+ case 0:
+ if (!cddb_url)
+ cddb_url = xstrdup("freedb.freedb.org:8880");
+ *val = xstrdup(cddb_url);
+ break;
+#endif
+ default:
+ return -IP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = libcdio_open,
+ .close = libcdio_close,
+ .read = libcdio_read,
+ .seek = libcdio_seek,
+ .read_comments = libcdio_read_comments,
+ .duration = libcdio_duration,
+ .bitrate = libcdio_bitrate,
+ .codec = libcdio_codec,
+ .codec_profile = libcdio_codec_profile,
+ .set_option = libcdio_set_option,
+ .get_option = libcdio_get_option
+};
+
+const char * const ip_options[] = {
+#ifdef HAVE_CDDB
+ "cddb_url",
+#endif
+ NULL
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = { NULL };
+const char * const ip_mime_types[] = { "x-content/audio-cdda", NULL };
--- /dev/null
+/*
+ * Copyright 2011 Various Authors
+ * Copyright 2011 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "channelmap.h"
+#include "utils.h"
+
+void channel_map_init_waveex(int channels, unsigned int mask, channel_position_t *map)
+{
+ /* http://www.microsoft.com/whdc/device/audio/multichaud.mspx#EMLAC */
+ const channel_position_t channel_map_waveex[] = {
+ CHANNEL_POSITION_FRONT_LEFT,
+ CHANNEL_POSITION_FRONT_RIGHT,
+ CHANNEL_POSITION_FRONT_CENTER,
+ CHANNEL_POSITION_LFE,
+ CHANNEL_POSITION_REAR_LEFT,
+ CHANNEL_POSITION_REAR_RIGHT,
+ CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+ CHANNEL_POSITION_REAR_CENTER,
+ CHANNEL_POSITION_SIDE_LEFT,
+ CHANNEL_POSITION_SIDE_RIGHT,
+ CHANNEL_POSITION_TOP_CENTER,
+ CHANNEL_POSITION_TOP_FRONT_LEFT,
+ CHANNEL_POSITION_TOP_FRONT_CENTER,
+ CHANNEL_POSITION_TOP_FRONT_RIGHT,
+ CHANNEL_POSITION_TOP_REAR_LEFT,
+ CHANNEL_POSITION_TOP_REAR_CENTER,
+ CHANNEL_POSITION_TOP_REAR_RIGHT
+ };
+
+ if (channels == 1) {
+ map[0] = CHANNEL_POSITION_MONO;
+ } else if (channels > 1 && channels < N_ELEMENTS(channel_map_waveex)) {
+ int i, j = 0;
+
+ if (!mask)
+ mask = (1 << channels) - 1;
+
+ for (i = 0; i < N_ELEMENTS(channel_map_waveex); i++) {
+ if (mask & (1 << i))
+ map[j++] = channel_map_waveex[i];
+ }
+ if (j != channels)
+ map[0] = CHANNEL_POSITION_INVALID;
+ } else {
+ map[0] = CHANNEL_POSITION_INVALID;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2011 Various Authors
+ * Copyright 2011 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CHANNELMAP_H
+#define _CHANNELMAP_H
+
+#include <string.h>
+
+#define CHANNELS_MAX 32
+
+/* Modelled after PulseAudio */
+enum channel_position {
+ CHANNEL_POSITION_INVALID = -1,
+ CHANNEL_POSITION_MONO = 0,
+ CHANNEL_POSITION_FRONT_LEFT,
+ CHANNEL_POSITION_FRONT_RIGHT,
+ CHANNEL_POSITION_FRONT_CENTER,
+
+ CHANNEL_POSITION_LEFT = CHANNEL_POSITION_FRONT_LEFT,
+ CHANNEL_POSITION_RIGHT = CHANNEL_POSITION_FRONT_RIGHT,
+ CHANNEL_POSITION_CENTER = CHANNEL_POSITION_FRONT_CENTER,
+
+ CHANNEL_POSITION_REAR_CENTER,
+ CHANNEL_POSITION_REAR_LEFT,
+ CHANNEL_POSITION_REAR_RIGHT,
+
+ CHANNEL_POSITION_LFE,
+ CHANNEL_POSITION_SUBWOOFER = CHANNEL_POSITION_LFE,
+
+ CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+
+ CHANNEL_POSITION_SIDE_LEFT,
+ CHANNEL_POSITION_SIDE_RIGHT,
+
+ CHANNEL_POSITION_TOP_CENTER,
+
+ CHANNEL_POSITION_TOP_FRONT_LEFT,
+ CHANNEL_POSITION_TOP_FRONT_RIGHT,
+ CHANNEL_POSITION_TOP_FRONT_CENTER,
+
+ CHANNEL_POSITION_TOP_REAR_LEFT,
+ CHANNEL_POSITION_TOP_REAR_RIGHT,
+ CHANNEL_POSITION_TOP_REAR_CENTER,
+
+ CHANNEL_POSITION_MAX
+};
+
+typedef enum channel_position channel_position_t;
+
+#define CHANNEL_MAP_INIT { CHANNEL_POSITION_INVALID }
+
+#define CHANNEL_MAP(name) \
+ channel_position_t name[CHANNELS_MAX] = CHANNEL_MAP_INIT
+
+static inline int channel_map_valid(const channel_position_t *map)
+{
+ return map[0] != CHANNEL_POSITION_INVALID;
+}
+
+static inline int channel_map_equal(const channel_position_t *a, const channel_position_t *b, int channels)
+{
+ return memcmp(a, b, sizeof(*a) * channels) == 0;
+}
+
+static inline channel_position_t *channel_map_copy(channel_position_t *dst, const channel_position_t *src)
+{
+ return memcpy(dst, src, sizeof(*dst) * CHANNELS_MAX);
+}
+
+static inline void channel_map_init_stereo(channel_position_t *map)
+{
+ map[0] = CHANNEL_POSITION_LEFT;
+ map[1] = CHANNEL_POSITION_RIGHT;
+}
+
+void channel_map_init_waveex(int channels, unsigned int mask, channel_position_t *map);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "cmdline.h"
+#include "uchar.h"
+#include "xmalloc.h"
+
+struct cmdline cmdline;
+
+const char cmdline_word_delimiters[] = " ";
+const char cmdline_filename_delimiters[] = "/";
+
+void cmdline_init(void)
+{
+ cmdline.blen = 0;
+ cmdline.clen = 0;
+ cmdline.bpos = 0;
+ cmdline.cpos = 0;
+ cmdline.size = 128;
+ cmdline.line = xnew(char, cmdline.size);
+ cmdline.line[0] = 0;
+}
+
+void cmdline_insert_ch(uchar ch)
+{
+ int size;
+
+ size = u_char_size(ch);
+ if (cmdline.blen + size > cmdline.size) {
+ cmdline.size *= 2;
+ cmdline.line = xrenew(char, cmdline.line, cmdline.size);
+ }
+ memmove(cmdline.line + cmdline.bpos + size,
+ cmdline.line + cmdline.bpos,
+ cmdline.blen - cmdline.bpos + 1);
+ u_set_char_raw(cmdline.line, &cmdline.bpos, ch);
+ cmdline.cpos++;
+ cmdline.blen += size;
+ cmdline.clen++;
+}
+
+void cmdline_backspace(void)
+{
+ int bpos, size;
+
+ if (cmdline.bpos == 0)
+ return;
+
+ bpos = cmdline.bpos;
+ u_prev_char_pos(cmdline.line, &bpos);
+ size = cmdline.bpos - bpos;
+
+ memmove(cmdline.line + bpos,
+ cmdline.line + cmdline.bpos,
+ cmdline.blen - cmdline.bpos + 1);
+ cmdline.bpos -= size;
+ cmdline.cpos--;
+ cmdline.blen -= size;
+ cmdline.clen--;
+}
+
+void cmdline_backspace_to_bol(void)
+{
+ while (cmdline.bpos)
+ cmdline_backspace();
+}
+
+void cmdline_delete_ch(void)
+{
+ uchar ch;
+ int size, bpos;
+
+ if (cmdline.bpos == cmdline.blen)
+ return;
+ bpos = cmdline.bpos;
+ ch = u_get_char(cmdline.line, &bpos);
+ size = u_char_size(ch);
+ cmdline.blen -= size;
+ cmdline.clen--;
+ memmove(cmdline.line + cmdline.bpos,
+ cmdline.line + cmdline.bpos + size,
+ cmdline.blen - cmdline.bpos + 1);
+}
+
+void cmdline_set_text(const char *text)
+{
+ int len = strlen(text);
+
+ if (len >= cmdline.size) {
+ while (len >= cmdline.size)
+ cmdline.size *= 2;
+ cmdline.line = xrenew(char, cmdline.line, cmdline.size);
+ }
+ memcpy(cmdline.line, text, len + 1);
+ cmdline.cpos = u_strlen_safe(cmdline.line);
+ cmdline.bpos = len;
+ cmdline.clen = cmdline.cpos;
+ cmdline.blen = len;
+}
+
+void cmdline_clear(void)
+{
+ cmdline.blen = 0;
+ cmdline.clen = 0;
+ cmdline.bpos = 0;
+ cmdline.cpos = 0;
+ cmdline.line[0] = 0;
+}
+
+void cmdline_clear_end(void)
+{
+ cmdline.line[cmdline.bpos] = 0;
+
+ cmdline.clen = u_strlen_safe(cmdline.line);
+ cmdline.blen = strlen(cmdline.line);
+}
+
+void cmdline_move_left(void)
+{
+ if (cmdline.bpos > 0) {
+ cmdline.cpos--;
+ u_prev_char_pos(cmdline.line, &cmdline.bpos);
+ }
+}
+
+void cmdline_move_right(void)
+{
+ if (cmdline.bpos < cmdline.blen) {
+ u_get_char(cmdline.line, &cmdline.bpos);
+ cmdline.cpos++;
+ }
+}
+
+void cmdline_move_home(void)
+{
+ cmdline.cpos = 0;
+ cmdline.bpos = 0;
+}
+
+void cmdline_move_end(void)
+{
+ cmdline.cpos = cmdline.clen;
+ cmdline.bpos = cmdline.blen;
+}
+
+static int next_word(const char *str, int bpos, int *cdiff, const char *delim, int direction)
+{
+ int skip_delim = 1;
+ while ((direction > 0) ? str[bpos] : (bpos > 0)) {
+ uchar ch;
+ int oldp = bpos;
+
+ if (direction > 0) {
+ ch = u_get_char(str, &bpos);
+ } else {
+ u_prev_char_pos(str, &bpos);
+ oldp = bpos;
+ ch = u_get_char(str, &oldp);
+ }
+
+ if (u_strchr(delim, ch)) {
+ if (!skip_delim) {
+ bpos -= bpos - oldp;
+ break;
+ }
+ } else
+ skip_delim = 0;
+
+ *cdiff += direction;
+ }
+ return bpos;
+}
+
+void cmdline_forward_word(const char *delim)
+{
+ cmdline.bpos = next_word(cmdline.line, cmdline.bpos, &cmdline.cpos, delim, +1);
+}
+
+void cmdline_backward_word(const char *delim)
+{
+ cmdline.bpos = next_word(cmdline.line, cmdline.bpos, &cmdline.cpos, delim, -1);
+}
+
+void cmdline_delete_word(const char *delim)
+{
+ int bpos, cdiff = 0;
+
+ bpos = next_word(cmdline.line, cmdline.bpos, &cdiff, delim, +1);
+
+ memmove(cmdline.line + cmdline.bpos,
+ cmdline.line + bpos,
+ cmdline.blen - bpos + 1);
+ cmdline.blen -= bpos - cmdline.bpos;
+ cmdline.clen -= cdiff;
+}
+
+void cmdline_backward_delete_word(const char *delim)
+{
+ int bpos, cdiff = 0;
+
+ bpos = next_word(cmdline.line, cmdline.bpos, &cdiff, delim, -1);
+
+ cmdline.blen += bpos - cmdline.bpos;
+ memmove(cmdline.line + bpos,
+ cmdline.line + cmdline.bpos,
+ cmdline.blen - bpos + 1);
+ cmdline.bpos = bpos;
+ cmdline.clen += cdiff;
+ cmdline.cpos += cdiff;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CMDLINE_H
+#define CMDLINE_H
+
+#include "uchar.h"
+
+struct cmdline {
+ /* length in bytes */
+ int blen;
+
+ /* length in characters */
+ int clen;
+
+ /* pos in bytes */
+ int bpos;
+
+ /* pos in characters */
+ int cpos;
+
+ /* allocated size */
+ int size;
+
+ char *line;
+};
+
+extern struct cmdline cmdline;
+
+extern const char cmdline_word_delimiters[];
+extern const char cmdline_filename_delimiters[];
+
+void cmdline_init(void);
+void cmdline_insert_ch(uchar ch);
+void cmdline_backspace(void);
+void cmdline_backspace_to_bol(void);
+void cmdline_delete_ch(void);
+void cmdline_set_text(const char *text);
+void cmdline_clear(void);
+void cmdline_clear_end(void);
+void cmdline_move_left(void);
+void cmdline_move_right(void);
+void cmdline_move_home(void);
+void cmdline_move_end(void);
+
+void cmdline_forward_word(const char *delim);
+void cmdline_backward_word(const char *delim);
+void cmdline_delete_word(const char *delim);
+void cmdline_backward_delete_word(const char *delim);
+
+#endif
--- /dev/null
+#!/bin/sh
+#
+# cmus-status-display
+#
+# Usage:
+# in cmus command ":set status_display_program=cmus-status-display"
+#
+# This scripts is executed by cmus when status changes:
+# cmus-status-display key1 val1 key2 val2 ...
+#
+# All keys contain only chars a-z. Values are UTF-8 strings.
+#
+# Keys: status file url artist album discnumber tracknumber title date
+# - status (stopped, playing, paused) is always given
+# - file or url is given only if track is 'loaded' in cmus
+# - other keys/values are given only if they are available
+#
+
+output()
+{
+ # write status to ~/cmus-status.txt (not very useful though)
+ echo "$*" >> ~/cmus-status.txt 2>&1
+
+ # WMI (http://wmi.modprobe.de/)
+ #wmiremote -t "$*" &> /dev/null
+}
+
+while test $# -ge 2
+do
+ eval _$1='$2'
+ shift
+ shift
+done
+
+if test -n "$_file"
+then
+ h=$(($_duration / 3600))
+ m=$(($_duration % 3600))
+
+ duration=""
+ test $h -gt 0 && dur="$h:"
+ duration="$dur$(printf '%02d:%02d' $(($m / 60)) $(($m % 60)))"
+
+ output "[$_status] $_artist - $_album - $_title ($_date) $duration"
+elif test -n "$_url"
+then
+ output "[$_status] $_url - $_title"
+else
+ output "[$_status]"
+fi
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "cmus.h"
+#include "job.h"
+#include "lib.h"
+#include "pl.h"
+#include "player.h"
+#include "input.h"
+#include "play_queue.h"
+#include "worker.h"
+#include "cache.h"
+#include "misc.h"
+#include "file.h"
+#include "utils.h"
+#include "path.h"
+#include "options.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "load_dir.h"
+#include "ui_curses.h"
+#include "cache.h"
+#include "gbuf.h"
+#include "discid.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <strings.h>
+
+/* save_playlist_cb, save_ext_playlist_cb */
+typedef int (*save_tracks_cb)(void *data, struct track_info *ti);
+
+static char **playable_exts;
+static const char * const playlist_exts[] = { "m3u", "pl", "pls", NULL };
+
+int cmus_init(void)
+{
+ playable_exts = ip_get_supported_extensions();
+ cache_init();
+ worker_init();
+ play_queue_init();
+ return 0;
+}
+
+void cmus_exit(void)
+{
+ worker_remove_jobs(JOB_TYPE_ANY);
+ worker_exit();
+ if (cache_close())
+ d_print("error: %s\n", strerror(errno));
+}
+
+void cmus_next(void)
+{
+ struct track_info *info;
+
+ editable_lock();
+ info = play_queue_remove();
+ if (info == NULL) {
+ if (play_library) {
+ info = lib_set_next();
+ } else {
+ info = pl_set_next();
+ }
+ }
+ editable_unlock();
+
+ if (info)
+ player_set_file(info);
+}
+
+void cmus_prev(void)
+{
+ struct track_info *info;
+
+ editable_lock();
+ if (play_library) {
+ info = lib_set_prev();
+ } else {
+ info = pl_set_prev();
+ }
+ editable_unlock();
+
+ if (info)
+ player_set_file(info);
+}
+
+void cmus_play_file(const char *filename)
+{
+ struct track_info *ti;
+
+ cache_lock();
+ ti = cache_get_ti(filename, 0);
+ cache_unlock();
+ if (!ti) {
+ error_msg("Couldn't get file information for %s\n", filename);
+ return;
+ }
+
+ player_play_file(ti);
+}
+
+enum file_type cmus_detect_ft(const char *name, char **ret)
+{
+ char *absolute;
+ struct stat st;
+
+ if (is_http_url(name) || is_cue_url(name)) {
+ *ret = xstrdup(name);
+ return FILE_TYPE_URL;
+ }
+
+ if (is_cdda_url(name)) {
+ *ret = complete_cdda_url(cdda_device, name);
+ return FILE_TYPE_CDDA;
+ }
+
+ *ret = NULL;
+ absolute = path_absolute(name);
+ if (absolute == NULL)
+ return FILE_TYPE_INVALID;
+
+ /* stat follows symlinks, lstat does not */
+ if (stat(absolute, &st) == -1) {
+ free(absolute);
+ return FILE_TYPE_INVALID;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ *ret = absolute;
+ return FILE_TYPE_DIR;
+ }
+ if (!S_ISREG(st.st_mode)) {
+ free(absolute);
+ errno = EINVAL;
+ return FILE_TYPE_INVALID;
+ }
+
+ *ret = absolute;
+ if (cmus_is_playlist(absolute))
+ return FILE_TYPE_PL;
+
+ /* NOTE: it could be FILE_TYPE_PL too! */
+ return FILE_TYPE_FILE;
+}
+
+void cmus_add(add_ti_cb add, const char *name, enum file_type ft, int jt, int force)
+{
+ struct add_data *data = xnew(struct add_data, 1);
+
+ data->add = add;
+ data->name = xstrdup(name);
+ data->type = ft;
+ data->force = force;
+ worker_add_job(jt, do_add_job, free_add_job, data);
+}
+
+static int save_ext_playlist_cb(void *data, struct track_info *ti)
+{
+ GBUF(buf);
+ int fd = *(int *)data;
+ int i, rc;
+
+ gbuf_addf(&buf, "file %s\n", escape(ti->filename));
+ gbuf_addf(&buf, "duration %d\n", ti->duration);
+ gbuf_addf(&buf, "codec %s\n", ti->codec);
+ gbuf_addf(&buf, "bitrate %ld\n", ti->bitrate);
+ for (i = 0; ti->comments[i].key; i++)
+ gbuf_addf(&buf, "tag %s %s\n",
+ ti->comments[i].key,
+ escape(ti->comments[i].val));
+
+ rc = write_all(fd, buf.buffer, buf.len);
+ gbuf_free(&buf);
+
+ if (rc == -1)
+ return -1;
+ return 0;
+}
+
+static int save_playlist_cb(void *data, struct track_info *ti)
+{
+ int fd = *(int *)data;
+ const char nl = '\n';
+ int rc;
+
+ rc = write_all(fd, ti->filename, strlen(ti->filename));
+ if (rc == -1)
+ return -1;
+ rc = write_all(fd, &nl, 1);
+ if (rc == -1)
+ return -1;
+ return 0;
+}
+
+static int do_cmus_save(for_each_ti_cb for_each_ti, const char *filename, save_tracks_cb save_tracks)
+{
+ int fd, rc;
+
+ if (strcmp(filename, "-") == 0) {
+ if (get_client_fd() == -1) {
+ error_msg("saving to stdout works only remotely");
+ return 0;
+ }
+ fd = dup(get_client_fd());
+ } else
+ fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if (fd == -1)
+ return -1;
+ rc = for_each_ti(save_tracks, &fd);
+ close(fd);
+ return rc;
+}
+
+int cmus_save(for_each_ti_cb for_each_ti, const char *filename)
+{
+ return do_cmus_save(for_each_ti, filename, save_playlist_cb);
+}
+
+int cmus_save_ext(for_each_ti_cb for_each_ti, const char *filename)
+{
+ return do_cmus_save(for_each_ti, filename, save_ext_playlist_cb);
+}
+
+static int update_cb(void *data, struct track_info *ti)
+{
+ struct update_data *d = data;
+
+ if (d->size == d->used) {
+ if (d->size == 0)
+ d->size = 16;
+ d->size *= 2;
+ d->ti = xrealloc(d->ti, d->size * sizeof(struct track_info *));
+ }
+ track_info_ref(ti);
+ d->ti[d->used++] = ti;
+ return 0;
+}
+
+void cmus_update_cache(int force)
+{
+ struct update_cache_data *data;
+
+ data = xnew(struct update_cache_data, 1);
+ data->force = force;
+
+ worker_add_job(JOB_TYPE_LIB, do_update_cache_job, free_update_cache_job, data);
+}
+
+void cmus_update_lib(void)
+{
+ struct update_data *data;
+
+ data = xnew(struct update_data, 1);
+ data->size = 0;
+ data->used = 0;
+ data->ti = NULL;
+
+ editable_lock();
+ lib_for_each(update_cb, data);
+ editable_unlock();
+
+ worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
+}
+
+void cmus_update_tis(struct track_info **tis, int nr, int force)
+{
+ struct update_data *data;
+
+ data = xnew(struct update_data, 1);
+ data->size = nr;
+ data->used = nr;
+ data->ti = tis;
+ data->force = force;
+ worker_add_job(JOB_TYPE_LIB, do_update_job, free_update_job, data);
+}
+
+static const char *get_ext(const char *filename)
+{
+ const char *ext = strrchr(filename, '.');
+
+ if (ext)
+ ext++;
+ return ext;
+}
+
+static int str_in_array(const char *str, const char * const *array)
+{
+ int i;
+
+ for (i = 0; array[i]; i++) {
+ if (strcasecmp(str, array[i]) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+int cmus_is_playlist(const char *filename)
+{
+ const char *ext = get_ext(filename);
+
+ return ext && str_in_array(ext, playlist_exts);
+}
+
+int cmus_is_playable(const char *filename)
+{
+ const char *ext = get_ext(filename);
+
+ return ext && str_in_array(ext, (const char * const *)playable_exts);
+}
+
+int cmus_is_supported(const char *filename)
+{
+ const char *ext = get_ext(filename);
+
+ return ext && (str_in_array(ext, (const char * const *)playable_exts) ||
+ str_in_array(ext, playlist_exts));
+}
+
+struct pl_data {
+ int (*cb)(void *data, const char *line);
+ void *data;
+};
+
+static int pl_handle_line(void *data, const char *line)
+{
+ struct pl_data *d = data;
+ int i = 0;
+
+ while (isspace((unsigned char)line[i]))
+ i++;
+ if (line[i] == 0)
+ return 0;
+
+ if (line[i] == '#')
+ return 0;
+
+ return d->cb(d->data, line);
+}
+
+static int pls_handle_line(void *data, const char *line)
+{
+ struct pl_data *d = data;
+
+ if (strncasecmp(line, "file", 4))
+ return 0;
+ line = strchr(line, '=');
+ if (line == NULL)
+ return 0;
+ return d->cb(d->data, line + 1);
+}
+
+int cmus_playlist_for_each(const char *buf, int size, int reverse,
+ int (*cb)(void *data, const char *line),
+ void *data)
+{
+ struct pl_data d = { cb, data };
+ int (*handler)(void *, const char *);
+
+ handler = pl_handle_line;
+ if (size >= 10 && strncasecmp(buf, "[playlist]", 10) == 0)
+ handler = pls_handle_line;
+
+ if (reverse) {
+ buffer_for_each_line_reverse(buf, size, handler, &d);
+ } else {
+ buffer_for_each_line(buf, size, handler, &d);
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CMUS_H
+#define _CMUS_H
+
+#include "track_info.h"
+
+/*
+ * these types are only used to determine what jobs we should cancel.
+ * for example ":load" cancels jobs for the current view before loading
+ * new playlist.
+ */
+
+#define JOB_TYPE_LIB 1
+#define JOB_TYPE_PL 2
+#define JOB_TYPE_QUEUE 3
+
+enum file_type {
+ /* not found, device file... */
+ FILE_TYPE_INVALID,
+
+ FILE_TYPE_URL,
+ FILE_TYPE_PL,
+ FILE_TYPE_DIR,
+ FILE_TYPE_FILE,
+ FILE_TYPE_CDDA
+};
+
+typedef int (*track_info_cb)(void *data, struct track_info *ti);
+
+/* lib_for_each, lib_for_each_filtered, pl_for_each, play_queue_for_each */
+typedef int (*for_each_ti_cb)(track_info_cb cb, void *data);
+
+/* lib_for_each_sel, pl_for_each_sel, play_queue_for_each_sel */
+typedef int (*for_each_sel_ti_cb)(track_info_cb cb, void *data, int reverse);
+
+/* lib_add_track, pl_add_track, play_queue_append, play_queue_prepend */
+typedef void (*add_ti_cb)(struct track_info *);
+
+/* cmus_save, cmus_save_ext */
+typedef int (*save_ti_cb)(for_each_ti_cb for_each_ti, const char *filename);
+
+int cmus_init(void);
+void cmus_exit(void);
+void cmus_play_file(const char *filename);
+
+/* detect file type, returns absolute path or url in @ret */
+enum file_type cmus_detect_ft(const char *name, char **ret);
+
+/* add to library, playlist or queue view
+ *
+ * @add callback that does the actual adding
+ * @name playlist, directory, file, URL
+ * @ft detected FILE_TYPE_*
+ * @jt JOB_TYPE_{LIB,PL,QUEUE}
+ *
+ * returns immediately, actual work is done in the worker thread.
+ */
+void cmus_add(add_ti_cb, const char *name, enum file_type ft, int jt, int force);
+
+int cmus_save(for_each_ti_cb for_each_ti, const char *filename);
+int cmus_save_ext(for_each_ti_cb for_each_ti, const char *filename);
+
+void cmus_update_cache(int force);
+void cmus_update_lib(void);
+void cmus_update_tis(struct track_info **tis, int nr, int force);
+
+int cmus_is_playlist(const char *filename);
+int cmus_is_playable(const char *filename);
+int cmus_is_supported(const char *filename);
+
+int cmus_playlist_for_each(const char *buf, int size, int reverse,
+ int (*cb)(void *data, const char *line),
+ void *data);
+
+void cmus_next(void);
+void cmus_prev(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "command_mode.h"
+#include "search_mode.h"
+#include "cmdline.h"
+#include "options.h"
+#include "ui_curses.h"
+#include "history.h"
+#include "tabexp.h"
+#include "tabexp_file.h"
+#include "browser.h"
+#include "filters.h"
+#include "player.h"
+#include "output.h"
+#include "editable.h"
+#include "lib.h"
+#include "pl.h"
+#include "play_queue.h"
+#include "cmus.h"
+#include "worker.h"
+#include "keys.h"
+#include "xmalloc.h"
+#include "xstrjoin.h"
+#include "misc.h"
+#include "path.h"
+#include "spawn.h"
+#include "utils.h"
+#include "list.h"
+#include "debug.h"
+#include "load_dir.h"
+#ifdef HAVE_CONFIG
+#include "config/datadir.h"
+#endif
+#include "help.h"
+#include "op.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dirent.h>
+
+#if defined(__sun__)
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+static struct history cmd_history;
+static char *cmd_history_filename;
+static char *history_search_text = NULL;
+static int arg_expand_cmd = -1;
+static int prev_view = -1;
+
+/* view {{{ */
+
+void view_clear(int view)
+{
+ switch (view) {
+ case TREE_VIEW:
+ case SORTED_VIEW:
+ worker_remove_jobs(JOB_TYPE_LIB);
+ editable_lock();
+ editable_clear(&lib_editable);
+
+ /* FIXME: make this optional? */
+ lib_clear_store();
+
+ editable_unlock();
+ break;
+ case PLAYLIST_VIEW:
+ worker_remove_jobs(JOB_TYPE_PL);
+ editable_lock();
+ editable_clear(&pl_editable);
+ editable_unlock();
+ break;
+ case QUEUE_VIEW:
+ worker_remove_jobs(JOB_TYPE_QUEUE);
+ editable_lock();
+ editable_clear(&pq_editable);
+ editable_unlock();
+ break;
+ default:
+ info_msg(":clear only works in views 1-4");
+ }
+}
+
+void view_add(int view, char *arg, int prepend)
+{
+ char *tmp, *name;
+ enum file_type ft;
+
+ tmp = expand_filename(arg);
+ ft = cmus_detect_ft(tmp, &name);
+ if (ft == FILE_TYPE_INVALID) {
+ error_msg("adding '%s': %s", tmp, strerror(errno));
+ free(tmp);
+ return;
+ }
+ free(tmp);
+
+ switch (view) {
+ case TREE_VIEW:
+ case SORTED_VIEW:
+ cmus_add(lib_add_track, name, ft, JOB_TYPE_LIB, 0);
+ break;
+ case PLAYLIST_VIEW:
+ cmus_add(pl_add_track, name, ft, JOB_TYPE_PL, 0);
+ break;
+ case QUEUE_VIEW:
+ if (prepend) {
+ cmus_add(play_queue_prepend, name, ft, JOB_TYPE_QUEUE, 0);
+ } else {
+ cmus_add(play_queue_append, name, ft, JOB_TYPE_QUEUE, 0);
+ }
+ break;
+ default:
+ info_msg(":add only works in views 1-4");
+ }
+ free(name);
+}
+
+void view_load(int view, char *arg)
+{
+ char *tmp, *name;
+ enum file_type ft;
+
+ tmp = expand_filename(arg);
+ ft = cmus_detect_ft(tmp, &name);
+ if (ft == FILE_TYPE_INVALID) {
+ error_msg("loading '%s': %s", tmp, strerror(errno));
+ free(tmp);
+ return;
+ }
+ free(tmp);
+
+ if (ft == FILE_TYPE_FILE)
+ ft = FILE_TYPE_PL;
+ if (ft != FILE_TYPE_PL) {
+ error_msg("loading '%s': not a playlist file", name);
+ free(name);
+ return;
+ }
+
+ switch (view) {
+ case TREE_VIEW:
+ case SORTED_VIEW:
+ worker_remove_jobs(JOB_TYPE_LIB);
+ editable_lock();
+ editable_clear(&lib_editable);
+ editable_unlock();
+ cmus_add(lib_add_track, name, FILE_TYPE_PL, JOB_TYPE_LIB, 0);
+ free(lib_filename);
+ lib_filename = name;
+ break;
+ case PLAYLIST_VIEW:
+ worker_remove_jobs(JOB_TYPE_PL);
+ editable_lock();
+ editable_clear(&pl_editable);
+ editable_unlock();
+ cmus_add(pl_add_track, name, FILE_TYPE_PL, JOB_TYPE_PL, 0);
+ free(pl_filename);
+ pl_filename = name;
+ break;
+ default:
+ info_msg(":load only works in views 1-3");
+ free(name);
+ }
+}
+
+static void do_save(for_each_ti_cb for_each_ti, const char *arg, char **filenamep, save_ti_cb save_ti)
+{
+ char *filename = *filenamep;
+
+ if (arg) {
+ if (strcmp(arg, "-") == 0) {
+ filename = (char *) arg;
+ } else {
+ free(filename);
+ filename = xstrdup(arg);
+ *filenamep = filename;
+ }
+ } else if (!filename) {
+ error_msg("need a file as argument, no default stored yet");
+ return;
+ }
+
+ editable_lock();
+ if (save_ti(for_each_ti, filename) == -1)
+ error_msg("saving '%s': %s", filename, strerror(errno));
+ editable_unlock();
+}
+
+void view_save(int view, char *arg, int to_stdout, int filtered, int extended)
+{
+ char **dest;
+ save_ti_cb save_ti = extended ? cmus_save_ext : cmus_save;
+ for_each_ti_cb lib_for_each_ti = filtered ? lib_for_each_filtered : lib_for_each;
+
+ if (arg) {
+ if (to_stdout) {
+ arg = xstrdup(arg);
+ } else {
+ char *tmp = expand_filename(arg);
+ arg = path_absolute(tmp);
+ free(tmp);
+ }
+ }
+
+ switch (view) {
+ case TREE_VIEW:
+ case SORTED_VIEW:
+ if (worker_has_job(JOB_TYPE_LIB))
+ goto worker_running;
+ dest = extended ? &lib_ext_filename : &lib_filename;
+ do_save(lib_for_each_ti, arg, dest, save_ti);
+ break;
+ case PLAYLIST_VIEW:
+ if (worker_has_job(JOB_TYPE_PL))
+ goto worker_running;
+ dest = extended ? &pl_ext_filename : &pl_filename;
+ do_save(pl_for_each, arg, dest, save_ti);
+ break;
+ case QUEUE_VIEW:
+ if (worker_has_job(JOB_TYPE_QUEUE))
+ goto worker_running;
+ dest = extended ? &play_queue_ext_filename : &play_queue_filename;
+ do_save(play_queue_for_each, arg, dest, save_ti);
+ break;
+ default:
+ info_msg(":save only works in views 1 & 2 (library) and 3 (playlist)");
+ }
+ free(arg);
+ return;
+worker_running:
+ error_msg("can't save when tracks are being added");
+ free(arg);
+}
+
+/* }}} */
+
+/* if only_last != 0, only return the last flag */
+static int do_parse_flags(const char **strp, const char *flags, int only_last)
+{
+ const char *str = *strp;
+ int flag = 0;
+
+ if (str == NULL)
+ return flag;
+
+ while (*str && (only_last || !flag)) {
+ if (*str != '-')
+ break;
+
+ // "-"
+ if (str[1] == 0)
+ break;
+
+ // "--" or "-- "
+ if (str[1] == '-' && (str[2] == 0 || str[2] == ' ')) {
+ str += 2;
+ break;
+ }
+
+ // not "-?" or "-? "
+ if (str[2] && str[2] != ' ')
+ break;
+
+ flag = str[1];
+ if (!strchr(flags, flag)) {
+ error_msg("invalid option -%c", flag);
+ return -1;
+ }
+
+ str += 2;
+
+ while (*str == ' ')
+ str++;
+ }
+ while (*str == ' ')
+ str++;
+ if (*str == 0)
+ str = NULL;
+ *strp = str;
+ return flag;
+}
+
+static int parse_flags(const char **strp, const char *flags)
+{
+ return do_parse_flags(strp, flags, 1);
+}
+
+static int parse_one_flag(const char **strp, const char *flags)
+{
+ return do_parse_flags(strp, flags, 0);
+}
+
+/* is str == "...-", but not "...-- -" ? copied from do_parse_flags() */
+static int is_stdout_filename(const char *str)
+{
+ if (!str)
+ return 0;
+
+ while (*str) {
+ if (*str != '-')
+ return 0;
+ // "-"
+ if (str[1] == 0)
+ return 1;
+ // "--" or "-- "
+ if (str[1] == '-' && (str[2] == 0 || str[2] == ' '))
+ return 0;
+ // not "-?" or "-? "
+ if (str[2] && str[2] != ' ')
+ return 0;
+ str += 2;
+ while (*str == ' ')
+ str++;
+ }
+
+ return 0;
+}
+
+static int flag_to_view(int flag)
+{
+ switch (flag) {
+ case 'l':
+ case 'L':
+ return TREE_VIEW;
+ case 'p':
+ return PLAYLIST_VIEW;
+ case 'q':
+ case 'Q':
+ return QUEUE_VIEW;
+ default:
+ return cur_view;
+ }
+}
+
+static void cmd_add(char *arg)
+{
+ int flag = parse_flags((const char **)&arg, "lpqQ");
+
+ if (flag == -1)
+ return;
+ if (arg == NULL) {
+ error_msg("not enough arguments\n");
+ return;
+ }
+ view_add(flag_to_view(flag), arg, flag == 'Q');
+}
+
+static void cmd_clear(char *arg)
+{
+ int flag = parse_flags((const char **)&arg, "lpq");
+
+ if (flag == -1)
+ return;
+ if (arg) {
+ error_msg("too many arguments\n");
+ return;
+ }
+ view_clear(flag_to_view(flag));
+}
+
+static void cmd_load(char *arg)
+{
+ int flag = parse_flags((const char **)&arg, "lp");
+
+ if (flag == -1)
+ return;
+ if (arg == NULL) {
+ error_msg("not enough arguments\n");
+ return;
+ }
+ view_load(flag_to_view(flag), arg);
+}
+
+static void cmd_save(char *arg)
+{
+ int to_stdout = is_stdout_filename(arg);
+ int flag = 0, f, extended = 0;
+
+ do {
+ f = parse_one_flag((const char **)&arg, "eLlpq");
+ if (f == 'e')
+ extended = 1;
+ else if (f)
+ flag = f;
+ } while (f > 0);
+
+ if (flag == -1)
+ return;
+ view_save(flag_to_view(flag), arg, to_stdout, flag == 'L', extended);
+}
+
+static void cmd_set(char *arg)
+{
+ char *value = NULL;
+ int i;
+
+ for (i = 0; arg[i]; i++) {
+ if (arg[i] == '=') {
+ arg[i] = 0;
+ value = &arg[i + 1];
+ break;
+ }
+ }
+ if (value) {
+ option_set(arg, value);
+ help_win->changed = 1;
+ } else {
+ struct cmus_opt *opt;
+ char buf[OPTION_MAX_SIZE];
+
+ /* support "set <option>?" */
+ i--;
+ if (arg[i] == '?')
+ arg[i] = 0;
+
+ opt = option_find(arg);
+ if (opt) {
+ opt->get(opt->id, buf);
+ info_msg("setting: '%s=%s'", arg, buf);
+ }
+ }
+}
+
+static void cmd_toggle(char *arg)
+{
+ struct cmus_opt *opt = option_find(arg);
+
+ if (opt == NULL)
+ return;
+
+ if (opt->toggle == NULL) {
+ error_msg("%s is not toggle option", opt->name);
+ return;
+ }
+ opt->toggle(opt->id);
+ help_win->changed = 1;
+}
+
+static int get_number(char *str, char **end)
+{
+ int val = 0;
+
+ while (*str >= '0' && *str <= '9') {
+ val *= 10;
+ val += *str++ - '0';
+ }
+ *end = str;
+ return val;
+}
+
+static void cmd_seek(char *arg)
+{
+ int relative = 0;
+ int seek = 0, sign = 1, count;
+
+ switch (*arg) {
+ case '-':
+ sign = -1;
+ case '+':
+ relative = 1;
+ arg++;
+ break;
+ }
+
+ count = 0;
+ goto inside;
+
+ do {
+ int num;
+ char *end;
+
+ if (*arg != ':')
+ break;
+ arg++;
+inside:
+ num = get_number(arg, &end);
+ if (arg == end)
+ break;
+ arg = end;
+ seek = seek * 60 + num;
+ } while (++count < 3);
+
+ seek *= sign;
+ if (!count)
+ goto err;
+
+ if (count == 1) {
+ switch (tolower((unsigned char)*arg)) {
+ case 'h':
+ seek *= 60;
+ case 'm':
+ seek *= 60;
+ case 's':
+ arg++;
+ break;
+ }
+ }
+
+ if (!*arg) {
+ player_seek(seek, relative, 1);
+ return;
+ }
+err:
+ error_msg("expecting one argument: [+-]INTEGER[mh] or [+-]H:MM:SS");
+}
+
+static void cmd_factivate(char *arg)
+{
+ editable_lock();
+ filters_activate_names(arg);
+ editable_unlock();
+}
+
+static void cmd_live_filter(char *arg)
+{
+ editable_lock();
+ filters_set_live(arg);
+ editable_unlock();
+}
+
+static void cmd_filter(char *arg)
+{
+ editable_lock();
+ filters_set_anonymous(arg);
+ editable_unlock();
+}
+
+static void cmd_fset(char *arg)
+{
+ filters_set_filter(arg);
+}
+
+static void cmd_help(char *arg)
+{
+ info_msg("To get started with cmus, read cmus-tutorial(7) and cmus(1) man pages");
+}
+
+static void cmd_invert(char *arg)
+{
+ editable_lock();
+ switch (cur_view) {
+ case SORTED_VIEW:
+ editable_invert_marks(&lib_editable);
+ break;
+ case PLAYLIST_VIEW:
+ editable_invert_marks(&pl_editable);
+ break;
+ case QUEUE_VIEW:
+ editable_invert_marks(&pq_editable);
+ break;
+ default:
+ info_msg(":invert only works in views 2-4");
+ }
+ editable_unlock();
+}
+
+static void cmd_mark(char *arg)
+{
+ editable_lock();
+ switch (cur_view) {
+ case SORTED_VIEW:
+ editable_mark(&lib_editable, arg);
+ break;
+ case PLAYLIST_VIEW:
+ editable_mark(&pl_editable, arg);
+ break;
+ case QUEUE_VIEW:
+ editable_mark(&pq_editable, arg);
+ break;
+ default:
+ info_msg(":mark only works in views 2-4");
+ }
+ editable_unlock();
+}
+
+static void cmd_unmark(char *arg)
+{
+ editable_lock();
+ switch (cur_view) {
+ case SORTED_VIEW:
+ editable_unmark(&lib_editable);
+ break;
+ case PLAYLIST_VIEW:
+ editable_unmark(&pl_editable);
+ break;
+ case QUEUE_VIEW:
+ editable_unmark(&pq_editable);
+ break;
+ default:
+ info_msg(":unmark only works in views 2-4");
+ }
+ editable_unlock();
+}
+
+static void cmd_update_cache(char *arg)
+{
+ int flag = parse_flags((const char **)&arg, "f");
+ cmus_update_cache(flag == 'f');
+}
+
+static void cmd_cd(char *arg)
+{
+ if (arg) {
+ char *dir, *absolute;
+
+ dir = expand_filename(arg);
+ absolute = path_absolute(dir);
+ if (chdir(dir) == -1) {
+ error_msg("could not cd to '%s': %s", dir, strerror(errno));
+ } else {
+ browser_chdir(absolute);
+ }
+ free(absolute);
+ free(dir);
+ } else {
+ if (chdir(home_dir) == -1) {
+ error_msg("could not cd to '%s': %s", home_dir, strerror(errno));
+ } else {
+ browser_chdir(home_dir);
+ }
+ }
+}
+
+static void cmd_bind(char *arg)
+{
+ int flag = parse_flags((const char **)&arg, "f");
+ char *key, *func;
+
+ if (flag == -1)
+ return;
+
+ if (arg == NULL)
+ goto err;
+
+ key = strchr(arg, ' ');
+ if (key == NULL)
+ goto err;
+ *key++ = 0;
+ while (*key == ' ')
+ key++;
+
+ func = strchr(key, ' ');
+ if (func == NULL)
+ goto err;
+ *func++ = 0;
+ while (*func == ' ')
+ func++;
+ if (*func == 0)
+ goto err;
+
+ key_bind(arg, key, func, flag == 'f');
+ return;
+err:
+ error_msg("expecting 3 arguments (context, key and function)\n");
+}
+
+static void cmd_unbind(char *arg)
+{
+ int flag = parse_flags((const char **)&arg, "f");
+ char *key;
+
+ if (flag == -1)
+ return;
+
+ if (arg == NULL)
+ goto err;
+
+ key = strchr(arg, ' ');
+ if (key == NULL)
+ goto err;
+ *key++ = 0;
+ while (*key == ' ')
+ key++;
+ if (*key == 0)
+ goto err;
+
+ /* FIXME: remove spaces at end */
+
+ key_unbind(arg, key, flag == 'f');
+ return;
+err:
+ error_msg("expecting 2 arguments (context and key)\n");
+}
+
+static void cmd_showbind(char *arg)
+{
+ char *key;
+
+ key = strchr(arg, ' ');
+ if (key == NULL)
+ goto err;
+ *key++ = 0;
+ while (*key == ' ')
+ key++;
+ if (*key == 0)
+ goto err;
+
+ /* FIXME: remove spaces at end */
+
+ show_binding(arg, key);
+ return;
+err:
+ error_msg("expecting 2 arguments (context and key)\n");
+}
+
+static void cmd_quit(char *arg)
+{
+ int flag = parse_flags((const char **)&arg, "i");
+ if (!worker_has_job(JOB_TYPE_ANY)) {
+ if (flag != 'i' || yes_no_query("Quit cmus? [y/N]"))
+ cmus_running = 0;
+ } else {
+ if (yes_no_query("Tracks are being added. Quit and truncate playlist(s)? [y/N]"))
+ cmus_running = 0;
+ }
+}
+
+static void cmd_reshuffle(char *arg)
+{
+ editable_lock();
+ lib_reshuffle();
+ pl_reshuffle();
+ editable_unlock();
+}
+
+static void cmd_source(char *arg)
+{
+ char *filename = expand_filename(arg);
+
+ if (source_file(filename) == -1)
+ error_msg("sourcing %s: %s", filename, strerror(errno));
+ free(filename);
+}
+
+static void cmd_colorscheme(char *arg)
+{
+ char filename[512];
+
+ snprintf(filename, sizeof(filename), "%s/%s.theme", cmus_config_dir, arg);
+ if (source_file(filename) == -1) {
+ snprintf(filename, sizeof(filename), DATADIR "/cmus/%s.theme", arg);
+ if (source_file(filename) == -1)
+ error_msg("sourcing %s: %s", filename, strerror(errno));
+ }
+}
+
+/*
+ * \" inside double-quotes becomes "
+ * \\ inside double-quotes becomes \
+ */
+static char *parse_quoted(const char **strp)
+{
+ const char *str = *strp;
+ const char *start;
+ char *ret, *dst;
+
+ str++;
+ start = str;
+ while (1) {
+ int c = *str++;
+
+ if (c == 0)
+ goto error;
+ if (c == '"')
+ break;
+ if (c == '\\') {
+ if (*str++ == 0)
+ goto error;
+ }
+ }
+ *strp = str;
+ ret = xnew(char, str - start);
+ str = start;
+ dst = ret;
+ while (1) {
+ int c = *str++;
+
+ if (c == '"')
+ break;
+ if (c == '\\') {
+ c = *str++;
+ if (c != '"' && c != '\\')
+ *dst++ = '\\';
+ }
+ *dst++ = c;
+ }
+ *dst = 0;
+ return ret;
+error:
+ error_msg("`\"' expected");
+ return NULL;
+}
+
+static char *parse_escaped(const char **strp)
+{
+ const char *str = *strp;
+ const char *start;
+ char *ret, *dst;
+
+ start = str;
+ while (1) {
+ int c = *str;
+
+ if (c == 0 || c == ' ' || c == '\'' || c == '"')
+ break;
+
+ str++;
+ if (c == '\\') {
+ c = *str;
+ if (c == 0)
+ break;
+ str++;
+ }
+ }
+ *strp = str;
+ ret = xnew(char, str - start + 1);
+ str = start;
+ dst = ret;
+ while (1) {
+ int c = *str;
+
+ if (c == 0 || c == ' ' || c == '\'' || c == '"')
+ break;
+
+ str++;
+ if (c == '\\') {
+ c = *str;
+ if (c == 0) {
+ *dst++ = '\\';
+ break;
+ }
+ str++;
+ }
+ *dst++ = c;
+ }
+ *dst = 0;
+ return ret;
+}
+
+static char *parse_one(const char **strp)
+{
+ const char *str = *strp;
+ char *ret = NULL;
+
+ while (1) {
+ char *part;
+ int c = *str;
+
+ if (!c || c == ' ')
+ break;
+ if (c == '"') {
+ part = parse_quoted(&str);
+ if (part == NULL)
+ goto error;
+ } else if (c == '\'') {
+ /* backslashes are normal chars inside single-quotes */
+ const char *end;
+
+ str++;
+ end = strchr(str, '\'');
+ if (end == NULL)
+ goto sq_missing;
+ part = xstrndup(str, end - str);
+ str = end + 1;
+ } else {
+ part = parse_escaped(&str);
+ }
+
+ if (ret == NULL) {
+ ret = part;
+ } else {
+ char *tmp = xstrjoin(ret, part);
+ free(ret);
+ ret = tmp;
+ }
+ }
+ *strp = str;
+ return ret;
+sq_missing:
+ error_msg("`'' expected");
+error:
+ free(ret);
+ return NULL;
+}
+
+static char **parse_cmd(const char *cmd, int *args_idx, int *ac)
+{
+ char **av = NULL;
+ int nr = 0;
+ int alloc = 0;
+
+ while (*cmd) {
+ char *arg;
+
+ /* there can't be spaces at start of command
+ * and there is at least one argument */
+ if (cmd[0] == '{' && cmd[1] == '}' && (cmd[2] == ' ' || cmd[2] == 0)) {
+ /* {} is replaced with file arguments */
+ if (*args_idx != -1)
+ goto only_once_please;
+ *args_idx = nr;
+ cmd += 2;
+ goto skip_spaces;
+ } else {
+ arg = parse_one(&cmd);
+ if (arg == NULL)
+ goto error;
+ }
+
+ if (nr == alloc) {
+ alloc = alloc ? alloc * 2 : 4;
+ av = xrenew(char *, av, alloc + 1);
+ }
+ av[nr++] = arg;
+skip_spaces:
+ while (*cmd == ' ')
+ cmd++;
+ }
+ av[nr] = NULL;
+ *ac = nr;
+ return av;
+only_once_please:
+ error_msg("{} can be used only once");
+error:
+ while (nr > 0)
+ free(av[--nr]);
+ free(av);
+ return NULL;
+}
+
+struct track_info_selection {
+ struct track_info **tis;
+ int tis_alloc;
+ int tis_nr;
+};
+
+static int add_ti(void *data, struct track_info *ti)
+{
+ struct track_info_selection *sel = data;
+ if (sel->tis_nr == sel->tis_alloc) {
+ sel->tis_alloc = sel->tis_alloc ? sel->tis_alloc * 2 : 8;
+ sel->tis = xrenew(struct track_info *, sel->tis, sel->tis_alloc);
+ }
+ track_info_ref(ti);
+ sel->tis[sel->tis_nr++] = ti;
+ return 0;
+}
+
+static void cmd_run(char *arg)
+{
+ char **av, **argv;
+ int ac, argc, i, run, files_idx = -1;
+ struct track_info_selection sel = { .tis = NULL };
+
+ if (cur_view > QUEUE_VIEW) {
+ info_msg("Command execution is supported only in views 1-4");
+ return;
+ }
+
+ av = parse_cmd(arg, &files_idx, &ac);
+ if (av == NULL) {
+ return;
+ }
+
+ /* collect selected files (struct track_info) */
+ editable_lock();
+ switch (cur_view) {
+ case TREE_VIEW:
+ __tree_for_each_sel(add_ti, NULL, 0);
+ break;
+ case SORTED_VIEW:
+ __editable_for_each_sel(&lib_editable, add_ti, &sel, 0);
+ break;
+ case PLAYLIST_VIEW:
+ __editable_for_each_sel(&pl_editable, add_ti, &sel, 0);
+ break;
+ case QUEUE_VIEW:
+ __editable_for_each_sel(&pq_editable, add_ti, &sel, 0);
+ break;
+ }
+ editable_unlock();
+
+ if (sel.tis_nr == 0) {
+ /* no files selected, do nothing */
+ free_str_array(av);
+ return;
+ }
+ sel.tis[sel.tis_nr] = NULL;
+
+ /* build argv */
+ argv = xnew(char *, ac + sel.tis_nr + 1);
+ argc = 0;
+ if (files_idx == -1) {
+ /* add selected files after rest of the args */
+ for (i = 0; i < ac; i++)
+ argv[argc++] = av[i];
+ for (i = 0; i < sel.tis_nr; i++)
+ argv[argc++] = sel.tis[i]->filename;
+ } else {
+ for (i = 0; i < files_idx; i++)
+ argv[argc++] = av[i];
+ for (i = 0; i < sel.tis_nr; i++)
+ argv[argc++] = sel.tis[i]->filename;
+ for (i = files_idx; i < ac; i++)
+ argv[argc++] = av[i];
+ }
+ argv[argc] = NULL;
+
+ for (i = 0; argv[i]; i++)
+ d_print("ARG: '%s'\n", argv[i]);
+
+ run = 1;
+ if (confirm_run && (sel.tis_nr > 1 || strcmp(argv[0], "rm") == 0)) {
+ if (!yes_no_query("Execute %s for the %d selected files? [y/N]", arg, sel.tis_nr)) {
+ info_msg("Aborted");
+ run = 0;
+ }
+ }
+ if (run) {
+ int status;
+
+ if (spawn(argv, &status, 1)) {
+ error_msg("executing %s: %s", argv[0], strerror(errno));
+ } else {
+ if (WIFEXITED(status)) {
+ int rc = WEXITSTATUS(status);
+
+ if (rc)
+ error_msg("%s returned %d", argv[0], rc);
+ }
+ if (WIFSIGNALED(status))
+ error_msg("%s received signal %d", argv[0], WTERMSIG(status));
+
+ switch (cur_view) {
+ case TREE_VIEW:
+ case SORTED_VIEW:
+ /* this must be done before sel.tis are unreffed */
+ free_str_array(av);
+ free(argv);
+
+ /* remove non-existed files, update tags for changed files */
+ cmus_update_tis(sel.tis, sel.tis_nr, 0);
+
+ /* we don't own sel.tis anymore! */
+ return;
+ }
+ }
+ }
+ free_str_array(av);
+ free(argv);
+ for (i = 0; sel.tis[i]; i++)
+ track_info_unref(sel.tis[i]);
+ free(sel.tis);
+}
+
+static void cmd_shell(char *arg)
+{
+ const char * const argv[] = { "sh", "-c", arg, NULL };
+
+ if (spawn((char **) argv, NULL, 0))
+ error_msg("executing '%s': %s", arg, strerror(errno));
+}
+
+static int get_one_ti(void *data, struct track_info *ti)
+{
+ struct track_info **sel_ti = data;
+
+ track_info_ref(ti);
+ *sel_ti = ti;
+ /* stop the for each loop, we need only the first selected track */
+ return 1;
+}
+
+static void cmd_echo(char *arg)
+{
+ struct track_info *sel_ti;
+ char *ptr = arg;
+
+ while (1) {
+ ptr = strchr(ptr, '{');
+ if (ptr == NULL)
+ break;
+ if (ptr[1] == '}')
+ break;
+ ptr++;
+ }
+
+ if (ptr == NULL) {
+ info_msg("%s", arg);
+ return;
+ }
+
+ if (cur_view > QUEUE_VIEW) {
+ info_msg("echo with {} in its arguments is supported only in views 1-4");
+ return;
+ }
+
+ *ptr = 0;
+ ptr += 2;
+
+ /* get only the first selected track */
+ sel_ti = NULL;
+
+ editable_lock();
+ switch (cur_view) {
+ case TREE_VIEW:
+ __tree_for_each_sel(get_one_ti, &sel_ti, 0);
+ break;
+ case SORTED_VIEW:
+ __editable_for_each_sel(&lib_editable, get_one_ti, &sel_ti, 0);
+ break;
+ case PLAYLIST_VIEW:
+ __editable_for_each_sel(&pl_editable, get_one_ti, &sel_ti, 0);
+ break;
+ case QUEUE_VIEW:
+ __editable_for_each_sel(&pq_editable, get_one_ti, &sel_ti, 0);
+ break;
+ }
+ editable_unlock();
+
+ if (sel_ti == NULL)
+ return;
+
+ info_msg("%s%s%s", arg, sel_ti->filename, ptr);
+ track_info_unref(sel_ti);
+}
+
+#define VF_RELATIVE 0x01
+#define VF_PERCENTAGE 0x02
+
+static int parse_vol_arg(const char *arg, int *value, unsigned int *flags)
+{
+ unsigned int f = 0;
+ int ch, val = 0, digits = 0, sign = 1;
+
+ if (*arg == '-') {
+ arg++;
+ f |= VF_RELATIVE;
+ sign = -1;
+ } else if (*arg == '+') {
+ arg++;
+ f |= VF_RELATIVE;
+ }
+
+ while (1) {
+ ch = *arg++;
+ if (ch < '0' || ch > '9')
+ break;
+ val *= 10;
+ val += ch - '0';
+ digits++;
+ }
+ if (digits == 0)
+ goto err;
+
+ if (ch == '%') {
+ f |= VF_PERCENTAGE;
+ ch = *arg;
+ }
+ if (ch)
+ goto err;
+
+ *value = sign * val;
+ *flags = f;
+ return 0;
+err:
+ return -1;
+}
+
+static int calc_vol(int val, int old, int max_vol, unsigned int flags)
+{
+ if (flags & VF_RELATIVE) {
+ if (flags & VF_PERCENTAGE)
+ val = scale_from_percentage(val, max_vol);
+ val += old;
+ } else if (flags & VF_PERCENTAGE) {
+ val = scale_from_percentage(val, max_vol);
+ }
+ return clamp(val, 0, max_vol);
+}
+
+/*
+ * :vol value [value]
+ *
+ * where value is [-+]?[0-9]+%?
+ */
+static void cmd_vol(char *arg)
+{
+ char **values = get_words(arg);
+ unsigned int lf, rf;
+ int l, r;
+
+ if (values[1] && values[2])
+ goto err;
+
+ if (parse_vol_arg(values[0], &l, &lf))
+ goto err;
+
+ r = l;
+ rf = lf;
+ if (values[1] && parse_vol_arg(values[1], &r, &rf))
+ goto err;
+
+ free_str_array(values);
+
+ if (soft_vol) {
+ l = calc_vol(l, soft_vol_l, 100, lf);
+ r = calc_vol(r, soft_vol_r, 100, rf);
+ player_set_soft_volume(l, r);
+ } else {
+ int rc;
+ mixer_read_volume();
+ l = calc_vol(l, volume_l, volume_max, lf);
+ r = calc_vol(r, volume_r, volume_max, rf);
+ rc = mixer_set_volume(l, r);
+ if (rc != OP_ERROR_SUCCESS) {
+ char *msg = op_get_error_msg(rc, "can't change volume");
+ error_msg("%s", msg);
+ free(msg);
+ }
+ mixer_read_volume();
+ }
+ update_statusline();
+ return;
+err:
+ free_str_array(values);
+ error_msg("expecting 1 or 2 arguments (total or L and R volumes [+-]INTEGER[%%])\n");
+}
+
+static void cmd_prev_view(char *arg)
+{
+ int tmp;
+ if (prev_view >= 0) {
+ tmp = cur_view;
+ set_view(prev_view);
+ prev_view = tmp;
+ }
+}
+
+static void cmd_view(char *arg)
+{
+ int view;
+
+ if (parse_enum(arg, 1, NR_VIEWS, view_names, &view) && (view - 1) != cur_view) {
+ prev_view = cur_view;
+ set_view(view - 1);
+ }
+}
+
+static void cmd_push(char *arg)
+{
+ cmdline_set_text(arg);
+ enter_command_mode();
+}
+
+static void cmd_p_next(char *arg)
+{
+ cmus_next();
+}
+
+static void cmd_p_pause(char *arg)
+{
+ player_pause();
+}
+
+static void cmd_p_play(char *arg)
+{
+ if (arg) {
+ cmus_play_file(arg);
+ } else {
+ player_play();
+ }
+}
+
+static void cmd_p_prev(char *arg)
+{
+ cmus_prev();
+}
+
+static void cmd_p_stop(char *arg)
+{
+ player_stop();
+}
+
+static void cmd_search_next(char *arg)
+{
+ if (search_str) {
+ if (!search_next(searchable, search_str, search_direction))
+ search_not_found();
+ }
+}
+
+static void cmd_search_prev(char *arg)
+{
+ if (search_str) {
+ if (!search_next(searchable, search_str, !search_direction))
+ search_not_found();
+ }
+}
+
+static int sorted_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
+{
+ return editable_for_each_sel(&lib_editable, cb, data, reverse);
+}
+
+static int pl_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
+{
+ return editable_for_each_sel(&pl_editable, cb, data, reverse);
+}
+
+static int pq_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
+{
+ return editable_for_each_sel(&pq_editable, cb, data, reverse);
+}
+
+static for_each_sel_ti_cb view_for_each_sel[4] = {
+ tree_for_each_sel,
+ sorted_for_each_sel,
+ pl_for_each_sel,
+ pq_for_each_sel
+};
+
+/* wrapper for add_ti_cb, (void *) can't store function pointers */
+struct wrapper_cb_data {
+ add_ti_cb cb;
+};
+
+/* wrapper for void lib_add_track(struct track_info *) etc. */
+static int wrapper_cb(void *data, struct track_info *ti)
+{
+ struct wrapper_cb_data *add = data;
+
+ add->cb(ti);
+ return 0;
+}
+
+static void add_from_browser(add_ti_cb add, int job_type)
+{
+ char *sel = browser_get_sel();
+
+ if (sel) {
+ enum file_type ft;
+ char *ret;
+
+ if (ends_with(sel, "/../") || ends_with(sel, "/..")) {
+ info_msg("For convenience, you can not add \"..\" directory from the browser view");
+ return;
+ }
+
+ ft = cmus_detect_ft(sel, &ret);
+ if (ft != FILE_TYPE_INVALID) {
+ cmus_add(add, ret, ft, job_type, 0);
+ window_down(browser_win, 1);
+ }
+ free(ret);
+ free(sel);
+ }
+}
+
+static void cmd_win_add_l(char *arg)
+{
+ if (cur_view == TREE_VIEW || cur_view == SORTED_VIEW)
+ return;
+
+ if (cur_view <= QUEUE_VIEW) {
+ struct wrapper_cb_data add = { lib_add_track };
+ editable_lock();
+ view_for_each_sel[cur_view](wrapper_cb, &add, 0);
+ editable_unlock();
+ } else if (cur_view == BROWSER_VIEW) {
+ add_from_browser(lib_add_track, JOB_TYPE_LIB);
+ }
+}
+
+static void cmd_win_add_p(char *arg)
+{
+ /* could allow adding dups? */
+ if (cur_view == PLAYLIST_VIEW)
+ return;
+
+ if (cur_view <= QUEUE_VIEW) {
+ struct wrapper_cb_data add = { pl_add_track };
+ editable_lock();
+ view_for_each_sel[cur_view](wrapper_cb, &add, 0);
+ editable_unlock();
+ } else if (cur_view == BROWSER_VIEW) {
+ add_from_browser(pl_add_track, JOB_TYPE_PL);
+ }
+}
+
+static void cmd_win_add_Q(char *arg)
+{
+ if (cur_view == QUEUE_VIEW)
+ return;
+
+ if (cur_view <= QUEUE_VIEW) {
+ struct wrapper_cb_data add = { play_queue_prepend };
+ editable_lock();
+ view_for_each_sel[cur_view](wrapper_cb, &add, 1);
+ editable_unlock();
+ } else if (cur_view == BROWSER_VIEW) {
+ add_from_browser(play_queue_prepend, JOB_TYPE_QUEUE);
+ }
+}
+
+static void cmd_win_add_q(char *arg)
+{
+ if (cur_view == QUEUE_VIEW)
+ return;
+
+ if (cur_view <= QUEUE_VIEW) {
+ struct wrapper_cb_data add = { play_queue_append };
+ editable_lock();
+ view_for_each_sel[cur_view](wrapper_cb, &add, 0);
+ editable_unlock();
+ } else if (cur_view == BROWSER_VIEW) {
+ add_from_browser(play_queue_append, JOB_TYPE_QUEUE);
+ }
+}
+
+static void cmd_win_activate(char *arg)
+{
+ struct track_info *info = NULL;
+
+ editable_lock();
+ switch (cur_view) {
+ case TREE_VIEW:
+ info = tree_set_selected();
+ break;
+ case SORTED_VIEW:
+ info = sorted_set_selected();
+ break;
+ case PLAYLIST_VIEW:
+ info = pl_set_selected();
+ break;
+ case QUEUE_VIEW:
+ break;
+ case BROWSER_VIEW:
+ browser_enter();
+ break;
+ case FILTERS_VIEW:
+ filters_activate();
+ break;
+ case HELP_VIEW:
+ help_select();
+ break;
+ }
+ editable_unlock();
+
+ if (info) {
+ /* update lib/pl mode */
+ if (cur_view < 2)
+ play_library = 1;
+ if (cur_view == 2)
+ play_library = 0;
+
+ player_play_file(info);
+ }
+}
+
+static void cmd_win_mv_after(char *arg)
+{
+ editable_lock();
+ switch (cur_view) {
+ case TREE_VIEW:
+ break;
+ case SORTED_VIEW:
+ editable_move_after(&lib_editable);
+ break;
+ case PLAYLIST_VIEW:
+ editable_move_after(&pl_editable);
+ break;
+ case QUEUE_VIEW:
+ editable_move_after(&pq_editable);
+ break;
+ case BROWSER_VIEW:
+ break;
+ case FILTERS_VIEW:
+ break;
+ case HELP_VIEW:
+ break;
+ }
+ editable_unlock();
+}
+
+static void cmd_win_mv_before(char *arg)
+{
+ editable_lock();
+ switch (cur_view) {
+ case TREE_VIEW:
+ break;
+ case SORTED_VIEW:
+ editable_move_before(&lib_editable);
+ break;
+ case PLAYLIST_VIEW:
+ editable_move_before(&pl_editable);
+ break;
+ case QUEUE_VIEW:
+ editable_move_before(&pq_editable);
+ break;
+ case BROWSER_VIEW:
+ break;
+ case FILTERS_VIEW:
+ break;
+ case HELP_VIEW:
+ break;
+ }
+ editable_unlock();
+}
+
+static void cmd_win_remove(char *arg)
+{
+ editable_lock();
+ switch (cur_view) {
+ case TREE_VIEW:
+ tree_remove_sel();
+ break;
+ case SORTED_VIEW:
+ editable_remove_sel(&lib_editable);
+ break;
+ case PLAYLIST_VIEW:
+ editable_remove_sel(&pl_editable);
+ break;
+ case QUEUE_VIEW:
+ editable_remove_sel(&pq_editable);
+ break;
+ case BROWSER_VIEW:
+ browser_delete();
+ break;
+ case FILTERS_VIEW:
+ filters_delete_filter();
+ break;
+ case HELP_VIEW:
+ help_remove();
+ break;
+ }
+ editable_unlock();
+}
+
+static void cmd_win_sel_cur(char *arg)
+{
+ editable_lock();
+ switch (cur_view) {
+ case TREE_VIEW:
+ tree_sel_current();
+ break;
+ case SORTED_VIEW:
+ sorted_sel_current();
+ break;
+ case PLAYLIST_VIEW:
+ pl_sel_current();
+ break;
+ case QUEUE_VIEW:
+ break;
+ case BROWSER_VIEW:
+ break;
+ case FILTERS_VIEW:
+ break;
+ case HELP_VIEW:
+ break;
+ }
+ editable_unlock();
+}
+
+static void cmd_win_toggle(char *arg)
+{
+ switch (cur_view) {
+ case TREE_VIEW:
+ editable_lock();
+ tree_toggle_expand_artist();
+ editable_unlock();
+ break;
+ case SORTED_VIEW:
+ editable_lock();
+ editable_toggle_mark(&lib_editable);
+ editable_unlock();
+ break;
+ case PLAYLIST_VIEW:
+ editable_lock();
+ editable_toggle_mark(&pl_editable);
+ editable_unlock();
+ break;
+ case QUEUE_VIEW:
+ editable_lock();
+ editable_toggle_mark(&pq_editable);
+ editable_unlock();
+ break;
+ case BROWSER_VIEW:
+ break;
+ case FILTERS_VIEW:
+ filters_toggle_filter();
+ break;
+ case HELP_VIEW:
+ help_toggle();
+ break;
+ }
+}
+
+static struct window *current_win(void)
+{
+ switch (cur_view) {
+ case TREE_VIEW:
+ return lib_cur_win;
+ case SORTED_VIEW:
+ return lib_editable.win;
+ case PLAYLIST_VIEW:
+ return pl_editable.win;
+ case QUEUE_VIEW:
+ return pq_editable.win;
+ case BROWSER_VIEW:
+ return browser_win;
+ case HELP_VIEW:
+ return help_win;
+ case FILTERS_VIEW:
+ default:
+ return filters_win;
+ }
+}
+
+static void cmd_win_bottom(char *arg)
+{
+ editable_lock();
+ window_goto_bottom(current_win());
+ editable_unlock();
+}
+
+static void cmd_win_down(char *arg)
+{
+ editable_lock();
+ window_down(current_win(), 1);
+ editable_unlock();
+}
+
+static void cmd_win_next(char *arg)
+{
+ if (cur_view == TREE_VIEW) {
+ editable_lock();
+ tree_toggle_active_window();
+ editable_unlock();
+ }
+}
+
+static void cmd_win_pg_down(char *arg)
+{
+ editable_lock();
+ window_page_down(current_win());
+ editable_unlock();
+}
+
+static void cmd_win_pg_up(char *arg)
+{
+ editable_lock();
+ window_page_up(current_win());
+ editable_unlock();
+}
+
+static void cmd_win_pg_top(char *arg)
+{
+ editable_lock();
+ window_page_top(current_win());
+ editable_unlock();
+}
+
+static void cmd_win_pg_bottom(char *arg)
+{
+ editable_lock();
+ window_page_bottom(current_win());
+ editable_unlock();
+}
+
+static void cmd_win_pg_middle(char *arg)
+{
+ editable_lock();
+ window_page_middle(current_win());
+ editable_unlock();
+}
+
+static void cmd_win_update_cache(char *arg)
+{
+ struct track_info_selection sel = { .tis = NULL };
+ int flag = parse_flags((const char **)&arg, "f");
+
+ if (cur_view != TREE_VIEW && cur_view != SORTED_VIEW)
+ return;
+
+ editable_lock();
+ view_for_each_sel[cur_view](add_ti, &sel, 0);
+ editable_unlock();
+ if (sel.tis_nr == 0)
+ return;
+ sel.tis[sel.tis_nr] = NULL;
+ cmus_update_tis(sel.tis, sel.tis_nr, flag == 'f');
+}
+
+static void cmd_win_top(char *arg)
+{
+ editable_lock();
+ window_goto_top(current_win());
+ editable_unlock();
+}
+
+static void cmd_win_up(char *arg)
+{
+ editable_lock();
+ window_up(current_win(), 1);
+ editable_unlock();
+}
+
+static void cmd_win_update(char *arg)
+{
+ switch (cur_view) {
+ case TREE_VIEW:
+ case SORTED_VIEW:
+ cmus_update_lib();
+ break;
+ case BROWSER_VIEW:
+ browser_reload();
+ break;
+ }
+}
+
+static void cmd_browser_up(char *arg)
+{
+ browser_up();
+}
+
+static void cmd_refresh(char *arg)
+{
+ clearok(curscr, TRUE);
+ refresh();
+}
+
+static int cmp_intp(const void *ap, const void *bp)
+{
+ int a = *(int *)ap;
+ int b = *(int *)bp;
+ return a - b;
+}
+
+static int *rand_array(int size, int nmax)
+{
+ int *r = xnew(int, size + 1);
+ int i, offset = 0;
+ int count = size;
+
+ if (count > nmax / 2) {
+ /*
+ * Imagine that there are 1000 tracks in library and we want to
+ * add 998 random tracks to queue. After we have added 997
+ * random numbers to the array it would be quite hard to find a
+ * random number that isn't already in the array (3/1000
+ * probability).
+ *
+ * So we invert the logic:
+ *
+ * Find two (1000 - 998) random numbers in 0..999 range and put
+ * them at end of the array. Sort the numbers and then fill
+ * the array starting at index 0 with incrementing values that
+ * are not in the set of random numbers.
+ */
+ count = nmax - count;
+ offset = size - count;
+ }
+
+ for (i = 0; i < count; ) {
+ int v, j;
+found:
+ v = rand() % nmax;
+ for (j = 0; j < i; j++) {
+ if (r[offset + j] == v)
+ goto found;
+ }
+ r[offset + i++] = v;
+ }
+ qsort(r + offset, count, sizeof(*r), cmp_intp);
+
+ if (offset) {
+ int j, n;
+
+ /* simplifies next loop */
+ r[size] = nmax;
+
+ /* convert the indexes we don't want to those we want */
+ i = 0;
+ j = offset;
+ n = 0;
+ do {
+ while (n < r[j])
+ r[i++] = n++;
+ j++;
+ n++;
+ } while (i < size);
+ }
+ return r;
+}
+
+static int count_albums(void)
+{
+ struct artist *artist;
+ struct rb_node *tmp1, *tmp2;
+ int count = 0;
+
+ rb_for_each_entry(artist, tmp1, &lib_artist_root, tree_node) {
+ rb_for_each(tmp2, &artist->album_root)
+ count++;
+ }
+ return count;
+}
+
+struct album_list {
+ struct list_head node;
+ const struct album *album;
+};
+
+static void cmd_lqueue(char *arg)
+{
+ LIST_HEAD(head);
+ const struct list_head *item;
+ const struct album *album;
+ int count = 1, nmax, i, pos;
+ int *r;
+
+ if (arg) {
+ long int val;
+
+ if (str_to_int(arg, &val) || val <= 0) {
+ error_msg("argument must be positive integer");
+ return;
+ }
+ count = val;
+ }
+ editable_lock();
+ nmax = count_albums();
+ if (count > nmax)
+ count = nmax;
+ if (!count)
+ goto unlock;
+
+ r = rand_array(count, nmax);
+ album = to_album(rb_first(&to_artist(rb_first(&lib_artist_root))->album_root));
+ pos = 0;
+ for (i = 0; i < count; i++) {
+ struct album_list *a;
+
+ while (pos < r[i]) {
+ struct artist *artist = album->artist;
+ if (!rb_next(&album->tree_node)) {
+ artist = to_artist(rb_next(&artist->tree_node));
+ album = to_album(rb_first(&artist->album_root));
+ } else {
+ album = to_album(rb_next(&album->tree_node));
+ }
+ pos++;
+ }
+ a = xnew(struct album_list, 1);
+ a->album = album;
+ list_add_rand(&head, &a->node, i);
+ }
+ free(r);
+
+ item = head.next;
+ do {
+ struct list_head *next = item->next;
+ struct album_list *a = container_of(item, struct album_list, node);
+ struct tree_track *t;
+ struct rb_node *tmp;
+
+ rb_for_each_entry(t, tmp, &a->album->track_root, tree_node)
+ play_queue_append(tree_track_info(t));
+ free(a);
+ item = next;
+ } while (item != &head);
+unlock:
+ editable_unlock();
+}
+
+struct track_list {
+ struct list_head node;
+ const struct simple_track *track;
+};
+
+static void cmd_tqueue(char *arg)
+{
+ LIST_HEAD(head);
+ struct list_head *item;
+ int count = 1, i, pos;
+ int *r;
+
+ if (arg) {
+ long int val;
+
+ if (str_to_int(arg, &val) || val <= 0) {
+ error_msg("argument must be positive integer");
+ return;
+ }
+ count = val;
+ }
+ editable_lock();
+ if (count > lib_editable.nr_tracks)
+ count = lib_editable.nr_tracks;
+ if (!count)
+ goto unlock;
+
+ r = rand_array(count, lib_editable.nr_tracks);
+ item = lib_editable.head.next;
+ pos = 0;
+ for (i = 0; i < count; i++) {
+ struct track_list *t;
+
+ while (pos < r[i]) {
+ item = item->next;
+ pos++;
+ }
+ t = xnew(struct track_list, 1);
+ t->track = to_simple_track(item);
+ list_add_rand(&head, &t->node, i);
+ }
+ free(r);
+
+ item = head.next;
+ do {
+ struct list_head *next = item->next;
+ struct track_list *t = container_of(item, struct track_list, node);
+ play_queue_append(t->track->info);
+ free(t);
+ item = next;
+ } while (item != &head);
+unlock:
+ editable_unlock();
+}
+
+/* tab exp {{{
+ *
+ * these functions fill tabexp struct, which is resetted beforehand
+ */
+
+/* buffer used for tab expansion */
+static char expbuf[512];
+
+static int filter_directories(const char *name, const struct stat *s)
+{
+ return S_ISDIR(s->st_mode);
+}
+
+static int filter_executable_files(const char *name, const struct stat *s)
+{
+ return S_ISREG(s->st_mode) && (s->st_mode & 0111);
+}
+
+static int filter_any(const char *name, const struct stat *s)
+{
+ return 1;
+}
+
+static int filter_playable(const char *name, const struct stat *s)
+{
+ return S_ISDIR(s->st_mode) || cmus_is_playable(name);
+}
+
+static int filter_playlist(const char *name, const struct stat *s)
+{
+ return S_ISDIR(s->st_mode) || cmus_is_playlist(name);
+}
+
+static int filter_supported(const char *name, const struct stat *s)
+{
+ return S_ISDIR(s->st_mode) || cmus_is_supported(name);
+}
+
+static void expand_files(const char *str)
+{
+ expand_files_and_dirs(str, filter_any);
+}
+
+static void expand_directories(const char *str)
+{
+ expand_files_and_dirs(str, filter_directories);
+}
+
+static void expand_playable(const char *str)
+{
+ expand_files_and_dirs(str, filter_playable);
+}
+
+static void expand_playlist(const char *str)
+{
+ expand_files_and_dirs(str, filter_playlist);
+}
+
+static void expand_supported(const char *str)
+{
+ expand_files_and_dirs(str, filter_supported);
+}
+
+static void expand_add(const char *str)
+{
+ int flag = parse_flags(&str, "lpqQ");
+
+ if (flag == -1)
+ return;
+ if (str == NULL)
+ str = "";
+ expand_supported(str);
+
+ if (tabexp.head && flag) {
+ snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
+ free(tabexp.head);
+ tabexp.head = xstrdup(expbuf);
+ }
+}
+
+static void expand_program_paths(const char *str)
+{
+ if (str == NULL)
+ str = "";
+ if (str[0] == '~' || strchr(str, '/'))
+ expand_files(str);
+ else
+ expand_env_path(str, filter_executable_files);
+}
+
+static void expand_program_paths_option(const char *str, const char *opt)
+{
+ expand_program_paths(str);
+
+ if (tabexp.head && opt) {
+ snprintf(expbuf, sizeof(expbuf), "%s=%s", opt, tabexp.head);
+ free(tabexp.head);
+ tabexp.head = xstrdup(expbuf);
+ }
+}
+
+static void expand_load_save(const char *str)
+{
+ int flag = parse_flags(&str, "lp");
+
+ if (flag == -1)
+ return;
+ if (str == NULL)
+ str = "";
+ expand_playlist(str);
+
+ if (tabexp.head && flag) {
+ snprintf(expbuf, sizeof(expbuf), "-%c %s", flag, tabexp.head);
+ free(tabexp.head);
+ tabexp.head = xstrdup(expbuf);
+ }
+}
+
+static void expand_key_context(const char *str, const char *force)
+{
+ int pos, i, len = strlen(str);
+ char **tails;
+
+ tails = xnew(char *, NR_CTXS);
+ pos = 0;
+ for (i = 0; key_context_names[i]; i++) {
+ int cmp = strncmp(str, key_context_names[i], len);
+ if (cmp > 0)
+ continue;
+ if (cmp < 0)
+ break;
+ tails[pos++] = xstrdup(key_context_names[i] + len);
+ }
+
+ if (pos == 0) {
+ free(tails);
+ return;
+ }
+ if (pos == 1) {
+ char *tmp = xstrjoin(tails[0], " ");
+ free(tails[0]);
+ tails[0] = tmp;
+ }
+ snprintf(expbuf, sizeof(expbuf), "%s%s", force, str);
+ tabexp.head = xstrdup(expbuf);
+ tabexp.tails = tails;
+ tabexp.count = pos;
+}
+
+static int get_context(const char *str, int len)
+{
+ int i, c = -1, count = 0;
+
+ for (i = 0; key_context_names[i]; i++) {
+ if (strncmp(str, key_context_names[i], len) == 0) {
+ if (key_context_names[i][len] == 0) {
+ /* exact */
+ return i;
+ }
+ c = i;
+ count++;
+ }
+ }
+ if (count == 1)
+ return c;
+ return -1;
+}
+
+static void expand_command_line(const char *str);
+
+static void expand_bind_args(const char *str)
+{
+ /* :bind context key function
+ *
+ * possible values for str:
+ * c
+ * context k
+ * context key f
+ *
+ * you need to know context before you can expand function
+ */
+ /* start and end pointers for context, key and function */
+ const char *cs, *ce, *ks, *ke, *fs;
+ int i, c, k, count;
+ int flag = parse_flags((const char **)&str, "f");
+ const char *force = "";
+
+ if (flag == -1)
+ return;
+ if (str == NULL)
+ str = "";
+
+ if (flag == 'f')
+ force = "-f ";
+
+ cs = str;
+ ce = strchr(cs, ' ');
+ if (ce == NULL) {
+ expand_key_context(cs, force);
+ return;
+ }
+
+ /* context must be expandable */
+ c = get_context(cs, ce - cs);
+ if (c == -1) {
+ /* context is ambiguous or invalid */
+ return;
+ }
+
+ ks = ce;
+ while (*ks == ' ')
+ ks++;
+ ke = strchr(ks, ' ');
+ if (ke == NULL) {
+ /* expand key */
+ int len = strlen(ks);
+ PTR_ARRAY(array);
+
+ for (i = 0; key_table[i].name; i++) {
+ int cmp = strncmp(ks, key_table[i].name, len);
+ if (cmp > 0)
+ continue;
+ if (cmp < 0)
+ break;
+ ptr_array_add(&array, xstrdup(key_table[i].name + len));
+ }
+
+ if (!array.count)
+ return;
+
+ if (array.count == 1) {
+ char **ptrs = array.ptrs;
+ char *tmp = xstrjoin(ptrs[0], " ");
+ free(ptrs[0]);
+ ptrs[0] = tmp;
+ }
+
+ snprintf(expbuf, sizeof(expbuf), "%s%s %s", force, key_context_names[c], ks);
+
+ tabexp.head = xstrdup(expbuf);
+ tabexp.tails = array.ptrs;
+ tabexp.count = array.count;
+ return;
+ }
+
+ /* key must be expandable */
+ k = -1;
+ count = 0;
+ for (i = 0; key_table[i].name; i++) {
+ if (strncmp(ks, key_table[i].name, ke - ks) == 0) {
+ if (key_table[i].name[ke - ks] == 0) {
+ /* exact */
+ k = i;
+ count = 1;
+ break;
+ }
+ k = i;
+ count++;
+ }
+ }
+ if (count != 1) {
+ /* key is ambiguous or invalid */
+ return;
+ }
+
+ fs = ke;
+ while (*fs == ' ')
+ fs++;
+
+ if (*fs == ':')
+ fs++;
+
+ /* expand com [arg...] */
+ expand_command_line(fs);
+ if (tabexp.head == NULL) {
+ /* command expand failed */
+ return;
+ }
+
+ /*
+ * tabexp.head is now "com"
+ * tabexp.tails is [ mand1 mand2 ... ]
+ *
+ * need to change tabexp.head to "context key com"
+ */
+
+ snprintf(expbuf, sizeof(expbuf), "%s%s %s %s", force, key_context_names[c],
+ key_table[k].name, tabexp.head);
+ free(tabexp.head);
+ tabexp.head = xstrdup(expbuf);
+}
+
+static void expand_unbind_args(const char *str)
+{
+ /* :unbind context key */
+ /* start and end pointers for context and key */
+ const char *cs, *ce, *ks;
+ const struct binding *b;
+ PTR_ARRAY(array);
+ int c, len;
+
+ cs = str;
+ ce = strchr(cs, ' ');
+ if (ce == NULL) {
+ expand_key_context(cs, "");
+ return;
+ }
+
+ /* context must be expandable */
+ c = get_context(cs, ce - cs);
+ if (c == -1) {
+ /* context is ambiguous or invalid */
+ return;
+ }
+
+ ks = ce;
+ while (*ks == ' ')
+ ks++;
+
+ /* expand key */
+ len = strlen(ks);
+ b = key_bindings[c];
+ while (b) {
+ if (!strncmp(ks, b->key->name, len))
+ ptr_array_add(&array, xstrdup(b->key->name + len));
+ b = b->next;
+ }
+ if (!array.count)
+ return;
+
+ snprintf(expbuf, sizeof(expbuf), "%s %s", key_context_names[c], ks);
+
+ tabexp.head = xstrdup(expbuf);
+ tabexp.tails = array.ptrs;
+ tabexp.count = array.count;
+}
+
+static void expand_factivate(const char *str)
+{
+ /* "name1 name2 name3", expand only name3 */
+ struct filter_entry *e;
+ const char *name;
+ PTR_ARRAY(array);
+ int str_len, len, i;
+
+ str_len = strlen(str);
+ i = str_len;
+ while (i > 0) {
+ if (str[i - 1] == ' ')
+ break;
+ i--;
+ }
+ len = str_len - i;
+ name = str + i;
+
+ list_for_each_entry(e, &filters_head, node) {
+ if (!strncmp(name, e->name, len))
+ ptr_array_add(&array, xstrdup(e->name + len));
+ }
+ if (!array.count)
+ return;
+
+ tabexp.head = xstrdup(str);
+ tabexp.tails = array.ptrs;
+ tabexp.count = array.count;
+}
+
+static void expand_fset(const char *str)
+{
+ struct filter_entry *e;
+ PTR_ARRAY(array);
+
+ list_for_each_entry(e, &filters_head, node) {
+ char *line = xnew(char, strlen(e->name) + strlen(e->filter) + 2);
+ sprintf(line, "%s=%s", e->name, e->filter);
+ if (!strncmp(str, line, strlen(str)))
+ ptr_array_add(&array, xstrdup(line + strlen(str)));
+ free(line);
+ }
+ if (!array.count)
+ return;
+
+ tabexp.head = xstrdup(str);
+ tabexp.tails = array.ptrs;
+ tabexp.count = array.count;
+}
+
+static void expand_options(const char *str)
+{
+ struct cmus_opt *opt;
+ int len;
+ char **tails, *sep;
+
+ /* tabexp is resetted */
+ len = strlen(str);
+ sep = strchr(str, '=');
+ if (len > 1 && sep) {
+ /* expand value */
+ char *var = xstrndup(str, sep - str);
+
+ list_for_each_entry(opt, &option_head, node) {
+ if (strcmp(var, opt->name) == 0) {
+ if (str[len - 1] == '=') {
+ char buf[OPTION_MAX_SIZE];
+
+ tails = xnew(char *, 1);
+
+ buf[0] = 0;
+ opt->get(opt->id, buf);
+ tails[0] = xstrdup(buf);
+
+ tabexp.head = xstrdup(str);
+ tabexp.tails = tails;
+ tabexp.count = 1;
+ } else if (opt->flags & OPT_PROGRAM_PATH) {
+ expand_program_paths_option(sep + 1, var);
+ }
+ break;
+ }
+ }
+ free(var);
+ } else {
+ /* expand variable */
+ int pos;
+
+ tails = xnew(char *, nr_options);
+ pos = 0;
+ list_for_each_entry(opt, &option_head, node) {
+ if (strncmp(str, opt->name, len) == 0)
+ tails[pos++] = xstrdup(opt->name + len);
+ }
+ if (pos > 0) {
+ if (pos == 1) {
+ /* only one variable matches, add '=' */
+ char *tmp = xstrjoin(tails[0], "=");
+
+ free(tails[0]);
+ tails[0] = tmp;
+ }
+
+ tabexp.head = xstrdup(str);
+ tabexp.tails = tails;
+ tabexp.count = pos;
+ } else {
+ free(tails);
+ }
+ }
+}
+
+static void expand_toptions(const char *str)
+{
+ struct cmus_opt *opt;
+ int len, pos;
+ char **tails;
+
+ tails = xnew(char *, nr_options);
+ len = strlen(str);
+ pos = 0;
+ list_for_each_entry(opt, &option_head, node) {
+ if (opt->toggle == NULL)
+ continue;
+ if (strncmp(str, opt->name, len) == 0)
+ tails[pos++] = xstrdup(opt->name + len);
+ }
+ if (pos > 0) {
+ tabexp.head = xstrdup(str);
+ tabexp.tails = tails;
+ tabexp.count = pos;
+ } else {
+ free(tails);
+ }
+}
+
+static void load_themes(const char *dirname, const char *str, struct ptr_array *array)
+{
+ struct directory dir;
+ const char *name, *dot;
+ int len = strlen(str);
+
+ if (dir_open(&dir, dirname))
+ return;
+
+ while ((name = dir_read(&dir))) {
+ if (!S_ISREG(dir.st.st_mode))
+ continue;
+ if (strncmp(name, str, len))
+ continue;
+ dot = strrchr(name, '.');
+ if (dot == NULL || strcmp(dot, ".theme"))
+ continue;
+ if (dot - name < len)
+ /* str is "foo.th"
+ * matches "foo.theme"
+ * which also ends with ".theme"
+ */
+ continue;
+ ptr_array_add(array, xstrndup(name + len, dot - name - len));
+ }
+ dir_close(&dir);
+}
+
+static void expand_colorscheme(const char *str)
+{
+ PTR_ARRAY(array);
+
+ load_themes(cmus_config_dir, str, &array);
+ load_themes(DATADIR "/cmus", str, &array);
+
+ if (array.count) {
+ ptr_array_sort(&array, strptrcmp);
+
+ tabexp.head = xstrdup(str);
+ tabexp.tails = array.ptrs;
+ tabexp.count = array.count;
+ }
+}
+
+static void expand_commands(const char *str);
+
+/* tab exp }}} */
+
+/* sort by name */
+struct command commands[] = {
+ { "add", cmd_add, 1, 1, expand_add, 0, 0 },
+ { "bind", cmd_bind, 1, 1, expand_bind_args, 0, CMD_UNSAFE },
+ { "browser-up", cmd_browser_up, 0, 0, NULL, 0, 0 },
+ { "cd", cmd_cd, 0, 1, expand_directories, 0, 0 },
+ { "clear", cmd_clear, 0, 1, NULL, 0, 0 },
+ { "colorscheme", cmd_colorscheme,1, 1, expand_colorscheme, 0, 0 },
+ { "echo", cmd_echo, 1,-1, NULL, 0, 0 },
+ { "factivate", cmd_factivate, 0, 1, expand_factivate, 0, 0 },
+ { "filter", cmd_filter, 0, 1, NULL, 0, 0 },
+ { "fset", cmd_fset, 1, 1, expand_fset, 0, 0 },
+ { "help", cmd_help, 0, 0, NULL, 0, 0 },
+ { "invert", cmd_invert, 0, 0, NULL, 0, 0 },
+ { "live-filter", cmd_live_filter,0, 1, NULL, 0, CMD_LIVE },
+ { "load", cmd_load, 1, 1, expand_load_save, 0, 0 },
+ { "lqueue", cmd_lqueue, 0, 1, NULL, 0, 0 },
+ { "mark", cmd_mark, 0, 1, NULL, 0, 0 },
+ { "player-next", cmd_p_next, 0, 0, NULL, 0, 0 },
+ { "player-pause", cmd_p_pause, 0, 0, NULL, 0, 0 },
+ { "player-play", cmd_p_play, 0, 1, expand_playable, 0, 0 },
+ { "player-prev", cmd_p_prev, 0, 0, NULL, 0, 0 },
+ { "player-stop", cmd_p_stop, 0, 0, NULL, 0, 0 },
+ { "prev-view", cmd_prev_view, 0, 0, NULL, 0, 0 },
+ { "push", cmd_push, 1,-1, expand_commands, 0, 0 },
+ { "quit", cmd_quit, 0, 1, NULL, 0, 0 },
+ { "refresh", cmd_refresh, 0, 0, NULL, 0, 0 },
+ { "run", cmd_run, 1,-1, expand_program_paths, 0, CMD_UNSAFE },
+ { "save", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
+ { "search-next", cmd_search_next,0, 0, NULL, 0, 0 },
+ { "search-prev", cmd_search_prev,0, 0, NULL, 0, 0 },
+ { "seek", cmd_seek, 1, 1, NULL, 0, 0 },
+ { "set", cmd_set, 1, 1, expand_options, 0, 0 },
+ { "shell", cmd_shell, 1,-1, expand_program_paths, 0, CMD_UNSAFE },
+ { "showbind", cmd_showbind, 1, 1, expand_unbind_args, 0, 0 },
+ { "shuffle", cmd_reshuffle, 0, 0, NULL, 0, 0 },
+ { "source", cmd_source, 1, 1, expand_files, 0, CMD_UNSAFE },
+ { "toggle", cmd_toggle, 1, 1, expand_toptions, 0, 0 },
+ { "tqueue", cmd_tqueue, 0, 1, NULL, 0, 0 },
+ { "unbind", cmd_unbind, 1, 1, expand_unbind_args, 0, 0 },
+ { "unmark", cmd_unmark, 0, 0, NULL, 0, 0 },
+ { "update-cache", cmd_update_cache,0, 1, NULL, 0, 0 },
+ { "view", cmd_view, 1, 1, NULL, 0, 0 },
+ { "vol", cmd_vol, 1, 2, NULL, 0, 0 },
+ { "w", cmd_save, 0, 1, expand_load_save, 0, CMD_UNSAFE },
+ { "win-activate", cmd_win_activate,0, 0, NULL, 0, 0 },
+ { "win-add-l", cmd_win_add_l, 0, 0, NULL, 0, 0 },
+ { "win-add-p", cmd_win_add_p, 0, 0, NULL, 0, 0 },
+ { "win-add-Q", cmd_win_add_Q, 0, 0, NULL, 0, 0 },
+ { "win-add-q", cmd_win_add_q, 0, 0, NULL, 0, 0 },
+ { "win-bottom", cmd_win_bottom, 0, 0, NULL, 0, 0 },
+ { "win-down", cmd_win_down, 0, 0, NULL, 0, 0 },
+ { "win-mv-after", cmd_win_mv_after,0, 0, NULL, 0, 0 },
+ { "win-mv-before", cmd_win_mv_before,0, 0, NULL, 0, 0 },
+ { "win-next", cmd_win_next, 0, 0, NULL, 0, 0 },
+ { "win-page-bottom", cmd_win_pg_bottom,0, 0, NULL, 0, 0 },
+ { "win-page-down", cmd_win_pg_down,0, 0, NULL, 0, 0 },
+ { "win-page-middle", cmd_win_pg_middle,0, 0, NULL, 0, 0 },
+ { "win-page-top", cmd_win_pg_top, 0, 0, NULL, 0, 0 },
+ { "win-page-up", cmd_win_pg_up, 0, 0, NULL, 0, 0 },
+ { "win-remove", cmd_win_remove, 0, 0, NULL, 0, CMD_UNSAFE },
+ { "win-sel-cur", cmd_win_sel_cur,0, 0, NULL, 0, 0 },
+ { "win-toggle", cmd_win_toggle, 0, 0, NULL, 0, 0 },
+ { "win-top", cmd_win_top, 0, 0, NULL, 0, 0 },
+ { "win-up", cmd_win_up, 0, 0, NULL, 0, 0 },
+ { "win-update", cmd_win_update, 0, 0, NULL, 0, 0 },
+ { "win-update-cache", cmd_win_update_cache,0, 1, NULL, 0, 0 },
+ { "wq", cmd_quit, 0, 1, NULL, 0, 0 },
+ { NULL, NULL, 0, 0, 0, 0, 0 }
+};
+
+/* fills tabexp struct */
+static void expand_commands(const char *str)
+{
+ int i, len, pos;
+ char **tails;
+
+ /* tabexp is resetted */
+ tails = xnew(char *, N_ELEMENTS(commands) - 1);
+ len = strlen(str);
+ pos = 0;
+ for (i = 0; commands[i].name; i++) {
+ if (strncmp(str, commands[i].name, len) == 0)
+ tails[pos++] = xstrdup(commands[i].name + len);
+ }
+ if (pos > 0) {
+ if (pos == 1) {
+ /* only one command matches, add ' ' */
+ char *tmp = xstrjoin(tails[0], " ");
+
+ free(tails[0]);
+ tails[0] = tmp;
+ }
+ tabexp.head = xstrdup(str);
+ tabexp.tails = tails;
+ tabexp.count = pos;
+ } else {
+ free(tails);
+ }
+}
+
+struct command *get_command(const char *str)
+{
+ int i, len;
+
+ while (*str == ' ')
+ str++;
+ for (len = 0; str[len] && str[len] != ' '; len++)
+ ;
+
+ for (i = 0; commands[i].name; i++) {
+ if (strncmp(str, commands[i].name, len))
+ continue;
+
+ if (commands[i].name[len] == 0) {
+ /* exact */
+ return &commands[i];
+ }
+
+ if (commands[i + 1].name && strncmp(str, commands[i + 1].name, len) == 0) {
+ /* ambiguous */
+ return NULL;
+ }
+ return &commands[i];
+ }
+ return NULL;
+}
+
+/* fills tabexp struct */
+static void expand_command_line(const char *str)
+{
+ /* :command [arg]...
+ *
+ * examples:
+ *
+ * str expanded value (tabexp.head)
+ * -------------------------------------
+ * fs fset
+ * b c bind common
+ * se se (tabexp.tails = [ ek t ])
+ */
+ /* command start/end, argument start */
+ const char *cs, *ce, *as;
+ const struct command *cmd;
+
+ cs = str;
+ ce = strchr(cs, ' ');
+ if (ce == NULL) {
+ /* expand command */
+ expand_commands(cs);
+ return;
+ }
+
+ /* command must be expandable */
+ cmd = get_command(cs);
+ if (cmd == NULL) {
+ /* command ambiguous or invalid */
+ return;
+ }
+
+ if (cmd->expand == NULL) {
+ /* can't expand argument */
+ return;
+ }
+
+ as = ce;
+ while (*as == ' ')
+ as++;
+
+ /* expand argument */
+ cmd->expand(as);
+ if (tabexp.head == NULL) {
+ /* argument expansion failed */
+ return;
+ }
+
+ /* tabexp.head is now start of the argument string */
+ snprintf(expbuf, sizeof(expbuf), "%s %s", cmd->name, tabexp.head);
+ free(tabexp.head);
+ tabexp.head = xstrdup(expbuf);
+}
+
+static void tab_expand(int direction)
+{
+ char *s1, *s2, *tmp;
+ int pos;
+
+ /* strip white space */
+ pos = 0;
+ while (cmdline.line[pos] == ' ' && pos < cmdline.bpos)
+ pos++;
+
+ /* string to expand */
+ s1 = xstrndup(cmdline.line + pos, cmdline.bpos - pos);
+
+ /* tail */
+ s2 = xstrdup(cmdline.line + cmdline.bpos);
+
+ tmp = tabexp_expand(s1, expand_command_line, direction);
+ if (tmp) {
+ /* tmp.s2 */
+ int l1, l2;
+
+ l1 = strlen(tmp);
+ l2 = strlen(s2);
+ cmdline.blen = l1 + l2;
+ if (cmdline.blen >= cmdline.size) {
+ while (cmdline.blen >= cmdline.size)
+ cmdline.size *= 2;
+ cmdline.line = xrenew(char, cmdline.line, cmdline.size);
+ }
+ sprintf(cmdline.line, "%s%s", tmp, s2);
+ cmdline.bpos = l1;
+ cmdline.cpos = u_strlen_safe(tmp);
+ cmdline.clen = u_strlen_safe(cmdline.line);
+ free(tmp);
+ }
+ free(s1);
+ free(s2);
+}
+
+static void reset_tab_expansion(void)
+{
+ tabexp_reset();
+ arg_expand_cmd = -1;
+}
+
+static void cmdline_modified(void)
+{
+ char *cmd, *arg;
+ struct command *c;
+
+ if (!parse_command(cmdline.line, &cmd, &arg))
+ return;
+
+ c = get_command(cmd);
+ if (!c)
+ goto end;
+
+ if (c->flags & CMD_LIVE)
+ run_parsed_command(cmd, arg);
+
+end:
+ free(cmd);
+ free(arg);
+}
+
+int parse_command(const char *buf, char **cmdp, char **argp)
+{
+ int cmd_start, cmd_end, cmd_len;
+ int arg_start, arg_end;
+ int i;
+
+ i = 0;
+ while (buf[i] && buf[i] == ' ')
+ i++;
+
+ if (buf[i] == '#')
+ return 0;
+
+ cmd_start = i;
+ while (buf[i] && buf[i] != ' ')
+ i++;
+ cmd_end = i;
+ while (buf[i] && buf[i] == ' ')
+ i++;
+ arg_start = i;
+ while (buf[i])
+ i++;
+ arg_end = i;
+
+ cmd_len = cmd_end - cmd_start;
+ if (cmd_len == 0)
+ return 0;
+
+ *cmdp = xstrndup(buf + cmd_start, cmd_len);
+ if (arg_start == arg_end) {
+ *argp = NULL;
+ } else {
+ *argp = xstrndup(buf + arg_start, arg_end - arg_start);
+ }
+ return 1;
+}
+
+int run_only_safe_commands;
+
+void run_parsed_command(char *cmd, char *arg)
+{
+ int cmd_len = strlen(cmd);
+ int i = 0;
+
+ while (1) {
+ const struct command *c = &commands[i];
+
+ if (c->name == NULL) {
+ error_msg("unknown command\n");
+ break;
+ }
+ if (strncmp(cmd, c->name, cmd_len) == 0) {
+ const char *next = commands[i + 1].name;
+ int exact = c->name[cmd_len] == 0;
+
+ if (!exact && next && strncmp(cmd, next, cmd_len) == 0) {
+ error_msg("ambiguous command\n");
+ break;
+ }
+ if (c->min_args > 0 && arg == NULL) {
+ error_msg("not enough arguments\n");
+ break;
+ }
+ if (c->max_args == 0 && arg) {
+ error_msg("too many arguments\n");
+ break;
+ }
+ if (run_only_safe_commands && (c->flags & CMD_UNSAFE)) {
+ if (c->func != cmd_save || !is_stdout_filename(arg)) {
+ d_print("trying to execute unsafe command over net\n");
+ break;
+ }
+ }
+ c->func(arg);
+ break;
+ }
+ i++;
+ }
+}
+
+void run_command(const char *buf)
+{
+ char *cmd, *arg;
+
+ if (!parse_command(buf, &cmd, &arg))
+ return;
+
+ run_parsed_command(cmd, arg);
+ free(arg);
+ free(cmd);
+}
+
+static void reset_history_search(void)
+{
+ history_reset_search(&cmd_history);
+ free(history_search_text);
+ history_search_text = NULL;
+}
+
+static void backspace(void)
+{
+ if (cmdline.clen > 0) {
+ cmdline_backspace();
+ } else {
+ input_mode = NORMAL_MODE;
+ }
+}
+
+void command_mode_ch(uchar ch)
+{
+ switch (ch) {
+ case 0x01: // ^A
+ cmdline_move_home();
+ break;
+ case 0x02: // ^B
+ cmdline_move_left();
+ break;
+ case 0x04: // ^D
+ cmdline_delete_ch();
+ cmdline_modified();
+ break;
+ case 0x05: // ^E
+ cmdline_move_end();
+ break;
+ case 0x06: // ^F
+ cmdline_move_right();
+ break;
+ case 0x03: // ^C
+ case 0x07: // ^G
+ case 0x1B: // ESC
+ if (cmdline.blen) {
+ history_add_line(&cmd_history, cmdline.line);
+ cmdline_clear();
+ }
+ input_mode = NORMAL_MODE;
+ break;
+ case 0x0A:
+ if (cmdline.blen) {
+ run_command(cmdline.line);
+ history_add_line(&cmd_history, cmdline.line);
+ cmdline_clear();
+ }
+ input_mode = NORMAL_MODE;
+ break;
+ case 0x0B:
+ cmdline_clear_end();
+ cmdline_modified();
+ break;
+ case 0x09:
+ tab_expand(1);
+ break;
+ case 0x15:
+ cmdline_backspace_to_bol();
+ cmdline_modified();
+ break;
+ case 0x17: // ^W
+ cmdline_backward_delete_word(cmdline_word_delimiters);
+ cmdline_modified();
+ break;
+ case 0x08: // ^H
+ case 127:
+ backspace();
+ cmdline_modified();
+ break;
+ default:
+ cmdline_insert_ch(ch);
+ cmdline_modified();
+ }
+ reset_history_search();
+ if (ch != 0x09)
+ reset_tab_expansion();
+}
+
+void command_mode_escape(int c)
+{
+ switch (c) {
+ case 98:
+ cmdline_backward_word(cmdline_filename_delimiters);
+ break;
+ case 100:
+ cmdline_delete_word(cmdline_filename_delimiters);
+ cmdline_modified();
+ break;
+ case 102:
+ cmdline_forward_word(cmdline_filename_delimiters);
+ break;
+ case 127:
+ case KEY_BACKSPACE:
+ cmdline_backward_delete_word(cmdline_filename_delimiters);
+ cmdline_modified();
+ break;
+ }
+ reset_history_search();
+}
+
+void command_mode_key(int key)
+{
+ if (key != KEY_BTAB)
+ reset_tab_expansion();
+ switch (key) {
+ case KEY_DC:
+ cmdline_delete_ch();
+ cmdline_modified();
+ break;
+ case KEY_BACKSPACE:
+ backspace();
+ cmdline_modified();
+ break;
+ case KEY_LEFT:
+ cmdline_move_left();
+ return;
+ case KEY_RIGHT:
+ cmdline_move_right();
+ return;
+ case KEY_HOME:
+ cmdline_move_home();
+ return;
+ case KEY_END:
+ cmdline_move_end();
+ return;
+ case KEY_UP:
+ {
+ const char *s;
+
+ if (history_search_text == NULL)
+ history_search_text = xstrdup(cmdline.line);
+ s = history_search_forward(&cmd_history, history_search_text);
+ if (s)
+ cmdline_set_text(s);
+ }
+ return;
+ case KEY_DOWN:
+ if (history_search_text) {
+ const char *s;
+
+ s = history_search_backward(&cmd_history, history_search_text);
+ if (s) {
+ cmdline_set_text(s);
+ } else {
+ cmdline_set_text(history_search_text);
+ }
+ }
+ return;
+ case KEY_BTAB:
+ tab_expand(-1);
+ break;
+ default:
+ d_print("key = %c (%d)\n", key, key);
+ }
+ reset_history_search();
+}
+
+void commands_init(void)
+{
+ cmd_history_filename = xstrjoin(cmus_config_dir, "/command-history");
+ history_load(&cmd_history, cmd_history_filename, 2000);
+}
+
+void commands_exit(void)
+{
+ view_clear(TREE_VIEW);
+ view_clear(SORTED_VIEW);
+ view_clear(PLAYLIST_VIEW);
+ view_clear(QUEUE_VIEW);
+ history_save(&cmd_history);
+ history_free(&cmd_history);
+ free(cmd_history_filename);
+ tabexp_reset();
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _COMMAND_MODE_H
+#define _COMMAND_MODE_H
+
+#include "uchar.h"
+
+enum {
+ /* executing command is disabled over net */
+ CMD_UNSAFE = 1 << 0,
+ /* execute command after every typed/deleted character */
+ CMD_LIVE = 1 << 1,
+};
+
+struct command {
+ const char *name;
+ void (*func)(char *arg);
+
+ /* min/max number of arguments */
+ int min_args;
+ int max_args;
+
+ void (*expand)(const char *str);
+
+ /* bind count (0 means: unbound) */
+ int bc;
+
+ /* CMD_* */
+ unsigned int flags;
+};
+
+extern struct command commands[];
+extern int run_only_safe_commands;
+
+void command_mode_ch(uchar ch);
+void command_mode_escape(int c);
+void command_mode_key(int key);
+void commands_init(void);
+void commands_exit(void);
+int parse_command(const char *buf, char **cmdp, char **argp);
+void run_parsed_command(char *cmd, char *arg);
+void run_command(const char *buf);
+
+struct command *get_command(const char *str);
+
+void view_clear(int view);
+void view_add(int view, char *arg, int prepend);
+void view_load(int view, char *arg);
+void view_save(int view, char *arg, int to_stdout, int filtered, int extended);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2007 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "comment.h"
+#include "xmalloc.h"
+#include "utils.h"
+#include "uchar.h"
+
+#include <string.h>
+#include <strings.h>
+
+static int is_various_artists(const char *a)
+{
+ return strcasecmp(a, "Various Artists") == 0 ||
+ strcasecmp(a, "Various") == 0 ||
+ strcasecmp(a, "VA") == 0 ||
+ strcasecmp(a, "V/A") == 0;
+}
+
+int track_is_compilation(const struct keyval *comments)
+{
+ const char *c, *a, *aa;
+
+ c = keyvals_get_val(comments, "compilation");
+ if (c && is_freeform_true(c))
+ return 1;
+
+ aa = keyvals_get_val(comments, "albumartist");
+ if (aa && is_various_artists(aa))
+ return 1;
+
+ a = keyvals_get_val(comments, "artist");
+ if (aa && a && !u_strcase_equal(aa, a))
+ return 1;
+
+ return 0;
+}
+
+int track_is_va_compilation(const struct keyval *comments)
+{
+ const char *c, *aa;
+
+ aa = keyvals_get_val(comments, "albumartist");
+ if (aa)
+ return is_various_artists(aa);
+
+ c = keyvals_get_val(comments, "compilation");
+
+ return c && is_freeform_true(c);
+}
+
+const char *comments_get_albumartist(const struct keyval *comments)
+{
+ const char *val = keyvals_get_val(comments, "albumartist");
+
+ if (!val || strcmp(val, "") == 0)
+ val = keyvals_get_val(comments, "artist");
+
+ return val;
+}
+
+const char *comments_get_artistsort(const struct keyval *comments)
+{
+ const char *val;
+
+ if (track_is_va_compilation(comments))
+ return NULL;
+
+ val = keyvals_get_val(comments, "albumartistsort");
+ if (!track_is_compilation(comments)) {
+ if (!val || strcmp(val, "") == 0)
+ val = keyvals_get_val(comments, "artistsort");
+ }
+
+ if (!val || strcmp(val, "") == 0)
+ return NULL;
+
+ return val;
+}
+
+int comments_get_int(const struct keyval *comments, const char *key)
+{
+ const char *val;
+ long int ival;
+
+ val = keyvals_get_val(comments, key);
+ if (val == NULL)
+ return -1;
+ while (*val && !(*val >= '0' && *val <= '9'))
+ val++;
+ if (str_to_int(val, &ival) == -1)
+ return -1;
+ return ival;
+}
+
+double comments_get_double(const struct keyval *comments, const char *key)
+{
+ const char *val;
+ char *end;
+ double d;
+
+ val = keyvals_get_val(comments, key);
+ if (!val || strcmp(val, "") == 0)
+ goto error;
+
+ d = strtod(val, &end);
+ if (val == end)
+ goto error;
+
+ return d;
+
+error:
+ return strtod("NAN", NULL);
+}
+
+/* Return date as an integer in the form YYYYMMDD, for sorting purposes.
+ * This function is not year 10000 compliant. */
+int comments_get_date(const struct keyval *comments, const char *key)
+{
+ const char *val;
+ char *endptr;
+ int year, month, day;
+ long int ival;
+
+ val = keyvals_get_val(comments, key);
+ if (val == NULL)
+ return -1;
+
+ year = strtol(val, &endptr, 10);
+ /* Looking for a four-digit number */
+ if (year < 1000 || year > 9999)
+ return -1;
+ ival = year * 10000;
+
+ if (*endptr == '-' || *endptr == ' ' || *endptr == '/') {
+ month = strtol(endptr+1, &endptr, 10);
+ if (month < 1 || month > 12)
+ return ival;
+ ival += month * 100;
+ }
+
+ if (*endptr == '-' || *endptr == ' ' || *endptr == '/') {
+ day = strtol(endptr+1, &endptr, 10);
+ if (day < 1 || day > 31)
+ return ival;
+ ival += day;
+ }
+
+
+ return ival;
+}
+
+static const char *interesting[] = {
+ "artist", "album", "title", "tracknumber", "discnumber", "genre",
+ "date", "compilation", "albumartist", "artistsort", "albumartistsort",
+ "albumsort",
+ "originaldate",
+ "replaygain_track_gain",
+ "replaygain_track_peak",
+ "replaygain_album_gain",
+ "replaygain_album_peak",
+ "musicbrainz_trackid",
+ "comment",
+ "arranger", "composer", "conductor", "lyricist", "performer",
+ "remixer", "label", "publisher", "work", "opus", "partnumber", "part",
+ "subtitle", "media",
+ NULL
+};
+
+static struct {
+ const char *old;
+ const char *new;
+} key_map[] = {
+ { "album_artist", "albumartist" },
+ { "album artist", "albumartist" },
+ { "disc", "discnumber" },
+ { "track", "tracknumber" },
+ { "WM/Year", "date" },
+ { "WM/ArtistSortOrder", "artistsort" },
+ { "WM/AlbumArtistSortOrder", "albumartistsort" },
+ { "WM/AlbumSortOrder", "albumsort" },
+ { "WM/OriginalReleaseYear", "originaldate" },
+ { "WM/Media", "media" },
+ { "sourcemedia", "media" },
+ { "MusicBrainz Track Id", "musicbrainz_trackid" },
+ { "version", "subtitle" },
+ { NULL, NULL }
+};
+
+static const char *fix_key(const char *key)
+{
+ int i;
+
+ for (i = 0; interesting[i]; i++) {
+ if (!strcasecmp(key, interesting[i]))
+ return interesting[i];
+ }
+ for (i = 0; key_map[i].old; i++) {
+ if (!strcasecmp(key, key_map[i].old))
+ return key_map[i].new;
+ }
+ return NULL;
+}
+
+int comments_add(struct growing_keyvals *c, const char *key, char *val)
+{
+ int i;
+
+ if (!strcasecmp(key, "songwriter")) {
+ int r = comments_add_const(c, "lyricist", val);
+ return comments_add(c, "composer", val) && r;
+ }
+
+ key = fix_key(key);
+ if (!key) {
+ free(val);
+ return 0;
+ }
+
+ if (!strcmp(key, "tracknumber") || !strcmp(key, "discnumber")) {
+ char *slash = strchr(val, '/');
+ if (slash)
+ *slash = 0;
+ }
+
+ /* don't add duplicates. can't use keyvals_get_val() */
+ for (i = 0; i < c->count; i++) {
+ if (!strcasecmp(key, c->keyvals[i].key) && !strcmp(val, c->keyvals[i].val)) {
+ free(val);
+ return 0;
+ }
+ }
+
+ keyvals_add(c, key, val);
+ return 1;
+}
+
+int comments_add_const(struct growing_keyvals *c, const char *key, const char *val)
+{
+ return comments_add(c, key, xstrdup(val));
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _COMMENT_H
+#define _COMMENT_H
+
+#include "keyval.h"
+
+int track_is_compilation(const struct keyval *comments);
+int track_is_va_compilation(const struct keyval *comments);
+
+const char *comments_get_albumartist(const struct keyval *comments);
+const char *comments_get_artistsort(const struct keyval *comments); /* can return NULL */
+
+int comments_get_int(const struct keyval *comments, const char *key);
+double comments_get_double(const struct keyval *comments, const char *key);
+int comments_get_date(const struct keyval *comments, const char *key);
+
+int comments_add(struct growing_keyvals *c, const char *key, char *val);
+int comments_add_const(struct growing_keyvals *c, const char *key, const char *val);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef COMPILER_H
+#define COMPILER_H
+
+#include <stddef.h>
+
+/*
+ * GCC 2.96 or compatible required
+ */
+#if defined(__GNUC__)
+
+#if __GNUC__ > 3
+#undef offsetof
+#define offsetof(type, member) __builtin_offsetof(type, member)
+#endif
+
+/* Optimization: Condition @x is likely */
+#define likely(x) __builtin_expect(!!(x), 1)
+
+/* Optimization: Condition @x is unlikely */
+#define unlikely(x) __builtin_expect(!!(x), 0)
+
+#else
+
+#define likely(x) (x)
+#define unlikely(x) (x)
+
+#endif
+
+/* Optimization: Function never returns */
+#define __NORETURN __attribute__((__noreturn__))
+
+/* Argument at index @fmt_idx is printf compatible format string and
+ * argument at index @first_idx is the first format argument */
+#define __FORMAT(fmt_idx, first_idx) __attribute__((format(printf, (fmt_idx), (first_idx))))
+
+#if defined(__GNUC__) && (__GNUC__ >= 3)
+
+/* Optimization: Pointer returned can't alias other pointers */
+#define __MALLOC __attribute__((__malloc__))
+
+#else
+
+#define __MALLOC
+
+#endif
+
+
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ *
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of_portable(ptr, type, member) \
+ ((type *)(void *)( (char *)(ptr) - offsetof(type,member) ))
+#undef container_of
+#if defined(__GNUC__)
+#define container_of(ptr, type, member) __extension__ ({ \
+ const __typeof__( ((type *)0)->member ) *__mptr = (ptr); \
+ container_of_portable(__mptr, type, member);})
+#else
+#define container_of(ptr, type, member) container_of_portable(ptr, type, member)
+#endif
+
+#endif
--- /dev/null
+#!/bin/sh
+
+. scripts/configure.sh || exit 1
+
+check_cflags()
+{
+ check_cc_flag -std=gnu99 -pipe -Wall -Wshadow -Wcast-align -Wpointer-arith \
+ -Wwrite-strings -Wundef -Wmissing-prototypes -Wredundant-decls \
+ -Wextra -Wno-sign-compare -Wformat-security
+
+ for i in -Wdeclaration-after-statement \
+ -Wold-style-definition \
+ -Wno-pointer-sign \
+ -Werror-implicit-function-declaration \
+ -Wno-unused-parameter
+ do
+ check_cc_flag $i
+ done
+ return 0
+}
+
+check_compat()
+{
+ COMPAT_LIBS=
+ case `uname -s` in
+ SunOS)
+ # connect() etc.
+ try_link -lsocket && COMPAT_LIBS="$COMPAT_LIBS -lsocket"
+
+ # gethostbyname()
+ try_link -lnsl && COMPAT_LIBS="$COMPAT_LIBS -lnsl"
+
+ # nanosleep()
+ if try_link -lrt
+ then
+ COMPAT_LIBS="$COMPAT_LIBS -lrt"
+ elif try_link -lposix4
+ then
+ COMPAT_LIBS="$COMPAT_LIBS -lposix4"
+ fi
+ ;;
+ CYGWIN*)
+ CONFIG_CYGWIN=y
+ makefile_vars CONFIG_CYGWIN
+ esac
+ makefile_vars COMPAT_LIBS
+}
+
+rtsched_code="
+#include <pthread.h>
+
+int main(int argc, char *argv[])
+{
+ pthread_attr_t attr;
+ struct sched_param param;
+
+ pthread_attr_init(&attr);
+ pthread_attr_setschedpolicy(&attr, SCHED_RR);
+ param.sched_priority = sched_get_priority_max(SCHED_RR);
+ pthread_attr_setschedparam(&attr, ¶m);
+ return 0;
+}
+"
+
+check_rtsched()
+{
+ msg_checking "for realtime scheduling"
+ if try_compile_link "$rtsched_code" $PTHREAD_CFLAGS $PTHREAD_LIBS
+ then
+ msg_result yes
+ EXTRA_CFLAGS="$EXTRA_CFLAGS -DREALTIME_SCHEDULING"
+ else
+ msg_result no
+ fi
+ return 0
+}
+
+ncurses_code="
+#if defined(__sun__) || defined(__CYGWIN__)
+#include <termios.h>
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+int main(void)
+{
+ initscr();
+ endwin();
+ return 0;
+}
+"
+
+check_ncurses()
+{
+ if check_library NCURSES "" -lncursesw
+ then
+ widechars=y
+ elif check_library NCURSES "" -lncurses || check_library NCURSES "" -lcurses
+ then
+ widechars=n
+ msg_error "Your ncurses does not support wide characters!"
+ msg_error "Install ncursesw if you need wide character support,"
+ msg_error "you can ignore this warning otherwise."
+ fi
+ test -z "$widechars" && return 1
+
+ msg_checking "for working ncurses setup"
+ for flag in "" "-I/usr/include/ncurses" "-I/usr/include/ncursesw"
+ do
+ if try_compile_link "$ncurses_code" $flag $NCURSES_LIBS
+ then
+ NCURSES_CFLAGS="$NCURSES_CFLAGS $flag"
+ msg_result yes
+ working_curses=y
+ break
+ fi
+ done
+ if test -z "$working_curses"
+ then
+ msg_result no
+ return 1
+ fi
+
+ check_function "resizeterm" $NCURSES_CFLAGS $NCURSES_LIBS
+ HAVE_RESIZETERM=`test $? -ne 0 ; echo $?`
+
+ check_function "use_default_colors" $NCURSES_CFLAGS $NCURSES_LIBS
+ HAVE_USE_DEFAULT_COLORS=`test $? -ne 0 ; echo $?`
+
+ return 0
+}
+
+check_discid()
+{
+ HAVE_DISCID=n
+ pkg_config DISCID "libdiscid" "" "-ldiscid" && HAVE_DISCID=y
+ return $?
+}
+
+check_mpc()
+{
+ MPC_SV8=0
+ if check_header mpc/mpcdec.h
+ then
+ MPC_SV8=1
+ else
+ check_header mpcdec/mpcdec.h || return $?
+ fi
+ check_library MPC "" "-lmpcdec -lm"
+ return $?
+}
+
+check_cddb()
+{
+ pkg_config CDDB "libcddb" "" "-lcddb" && HAVE_CDDB=y
+ return $?
+}
+
+check_cdio()
+{
+ pkg_config CDIO "libcdio_cdda" "" "-lcdio_cdio -lcdio -lm"
+ return $?
+}
+
+check_flac()
+{
+ if pkg_config FLAC "flac" "" "-lFLAC -lm"
+ then
+ # Make sure the FLAC_CFLAGS value is sane, strip trailing '/FLAC'.
+ FLAC_CFLAGS=`echo $FLAC_CFLAGS | sed "s/FLAC$//"`
+ return 0
+ fi
+ check_library FLAC "" "-lFLAC -lvorbisfile -lm"
+ return $?
+}
+
+check_mad()
+{
+ pkg_config MAD "mad" "" "-lmad -lm"
+ return $?
+}
+
+check_cue()
+{
+ pkg_config CUE "libcue >= 1.3"
+ return $?
+}
+
+
+mikmod_code="
+#include <mikmod.h>
+int main() {
+ MikMod_RegisterAllDrivers();
+ return 0;
+}
+"
+check_mikmod()
+{
+ # mikmod is linked against pthread
+ app_config MIKMOD libmikmod-config || \
+ check_library MIKMOD "$PTHREAD_CFLAGS" "-lmikmod $PTHREAD_LIBS" || \
+ return 1
+ try_compile_link "$mikmod_code" $MIKMOD_CFLAGS $MIKMOD_LIBS
+ return $?
+}
+
+check_modplug()
+{
+ pkg_config MODPLUG "libmodplug" "-I/usr/include/libmodplug" "-lmodplug -lstdc++ -lm" || return $?
+ MODPLUG_API_8=0
+ if check_function "ModPlug_GetModuleType" $MODPLUG_CFLAGS $MODPLUG_LIBS
+ then
+ MODPLUG_API_8=1
+ fi
+ return 0
+}
+
+check_vorbis()
+{
+ if test "$CONFIG_TREMOR" = y
+ then
+ pkg_config VORBIS "vorbisidec" "" "-lvorbisidec -lm"
+ return $?
+ else
+ pkg_config VORBIS "vorbisfile" "" "-lvorbisfile -lvorbis -lm -logg"
+ return $?
+ fi
+}
+
+check_wavpack()
+{
+ pkg_config WAVPACK "wavpack" "" "-lwavpack"
+ return $?
+}
+
+check_pulse()
+{
+ pkg_config PULSE "libpulse >= 0.9.19"
+ return $?
+}
+
+check_alsa()
+{
+ # the alsa.pc file should be always available
+ pkg_config ALSA "alsa >= 1.0.11"
+ return $?
+}
+
+check_ao()
+{
+ pkg_config AO "ao" "" "-lao"
+ return $?
+}
+
+arts_code="
+#include <artsc.h>
+int main() {
+ return arts_init();
+}
+"
+check_arts()
+{
+ app_config ARTS artsc-config || return 1
+ try_compile_link "$arts_code" $ARTS_CFLAGS $ARTS_LIBS
+ return $?
+}
+
+check_oss()
+{
+ case `uname -s` in
+ Linux|*FreeBSD)
+ ;;
+ *BSD)
+ check_library OSS "" "-lossaudio"
+ return $?
+ ;;
+ *)
+ # unknown
+ ;;
+ esac
+
+ OSS_CFLAGS=""
+ OSS_LIBS=""
+ msg_checking "for header <sys/soundcard.h>"
+ if test -f /usr/include/sys/soundcard.h
+ then
+ msg_result "yes"
+ makefile_vars OSS_CFLAGS OSS_LIBS
+ return 0
+ else
+ msg_result "no"
+ fi
+ return 1
+}
+
+check_sun()
+{
+ msg_checking "for header <sys/audioio.h>"
+ if test -f /usr/include/sys/audioio.h
+ then
+ msg_result "yes"
+ return 0
+ else
+ msg_result "no"
+ return 1
+ fi
+}
+
+check_waveout()
+{
+ case `uname -s` in
+ CYGWIN*)
+ check_library WAVEOUT "" "-lwinmm"
+ return $?
+ esac
+ return 1
+}
+
+check_roar()
+{
+ pkg_config ROAR "libroar >= 0.4.5"
+ return $?
+}
+
+check_mp4()
+{
+ USE_MPEG4IP=1
+ if check_header mp4v2/mp4v2.h
+ then
+ USE_MPEG4IP=0
+ else
+ check_header mp4.h || return $?
+ fi
+ check_header neaacdec.h &&
+ check_library MP4 "" "-lmp4v2 -lfaad -lm"
+ return $?
+}
+
+check_aac()
+{
+ check_header neaacdec.h &&
+ check_library AAC "" "-lfaad -lm"
+ return $?
+}
+
+check_ffmpeg()
+{
+ HAVE_FFMPEG_AVCODEC_H=y
+ pkg_config FFMPEG "libavformat" || return $?
+ if check_header "libavcodec/avcodec.h" $FFMPEG_CFLAGS
+ then
+ HAVE_FFMPEG_AVCODEC_H=n
+ else
+ check_header "ffmpeg/avcodec.h" $FFMPEG_CFLAGS || return $?
+ fi
+ # ffmpeg api changes so frequently that it is best to compile the module
+ libs="$LDDLFLAGS $FFMPEG_LIBS"
+ cflags="$SOFLAGS $FFMPEG_CFLAGS"
+ if test "$HAVE_FFMPEG_AVCODEC_H" = y
+ then
+ cflags="$cflags -DHAVE_FFMPEG_AVCODEC_H"
+ fi
+ topdir=`dirname "$0"`
+ ffmpeg_code=`cat "$topdir"/ffmpeg.c | sed 's/\\\n//g'`
+ msg_checking "for successful build of ffmpeg.c"
+ if try_compile_link "$ffmpeg_code" $cflags $libs
+ then
+ msg_result yes
+ return 0
+ fi
+ msg_result no
+ return 1
+}
+
+check_string_function()
+{
+ msg_checking "for function $1"
+ string_function_code="
+#include <string.h>
+int main() {
+ return $1;
+}
+"
+ if try_compile_link "$string_function_code"
+ then
+ msg_result yes
+ return 0
+ fi
+ msg_result no
+ return 1
+}
+
+
+# defaults
+prefix=/usr/local
+DEBUG=1
+HAVE_CDDB=n
+CONFIG_TREMOR=n
+CONFIG_MIKMOD=n
+USE_FALLBACK_IP=n
+HAVE_BYTESWAP_H=n
+HAVE_STRDUP=n
+HAVE_STRNDUP=n
+# unset CONFIG_* variables: if check succeeds 'y', otherwise 'n'
+
+USAGE="
+Options:
+ prefix Installation prefix [$prefix]
+ bindir User executables [\$prefix/bin]
+ datadir Read-only data [\$prefix/share]
+ libdir Libraries [\$prefix/lib]
+ mandir Man pages [\$datadir/man]
+ exampledir Examples [\$datadir/doc/cmus/examples]
+ DEBUG Debugging level (0-2) [$DEBUG]
+
+Optional Features: y/n
+ CONFIG_CDDB libcddb CDDA identification [auto]
+ CONFIG_CDIO libcdio CDDA input [auto]
+ CONFIG_DISCID libdiscid CDDA identification [auto]
+ CONFIG_FLAC Free Lossless Audio Codec (.flac, .fla) [auto]
+ CONFIG_MAD MPEG Audio Decoder (.mp3, .mp2, streams) [auto]
+ CONFIG_MODPLUG libmodplug (.mod, .x3m, ...) [auto]
+ CONFIG_MIKMOD libmikmod (.mod, .x3m, ...) [n]
+ CONFIG_MPC libmpcdec (Musepack .mpc, .mpp, .mp+) [auto]
+ CONFIG_VORBIS Ogg/Vorbis (.ogg, application/ogg, audio/x-ogg) [auto]
+ CONFIG_TREMOR Use Tremor as Ogg/Vorbis input plugin [n]
+ CONFIG_WAV WAV [y]
+ CONFIG_WAVPACK WavPack (.wv, audio/x-wavpack) [auto]
+ CONFIG_MP4 MPEG-4 AAC (.mp4, .m4a, .m4b) [auto]
+ CONFIG_AAC AAC (.aac, audio/aac, audio/aacp) [auto]
+ CONFIG_FFMPEG FFMPEG (.shn, .wma) [auto]
+ CONFIG_CUE CUE sheets (.cue) [auto]
+ CONFIG_ROAR native RoarAudio output [auto]
+ CONFIG_PULSE native PulseAudio output [auto]
+ CONFIG_ALSA ALSA [auto]
+ CONFIG_AO Libao cross-platform audio library [auto]
+ CONFIG_ARTS ARTS [auto]
+ CONFIG_OSS Open Sound System [auto]
+ CONFIG_SUN Sun Audio [auto]
+ CONFIG_WAVEOUT Windows Wave Out [auto]
+ USE_FALLBACK_IP Use a specific IP for every unrecognized [n]
+ input format. Currently set to FFMPEG.
+
+Also many standard variables like CC, LD, CFLAGS, LDFLAGS are recognized.
+Cross compiling is supported via CROSS=target-prefix-
+ optionally set HOSTCC=this-machine-gcc, HOSTLD, HOST_CFLAGS, HOST_LDFLAGS."
+
+parse_command_line "$@"
+
+case $DEBUG in
+[0-2])
+ ;;
+*)
+ die "DEBUG must be 0-2"
+ ;;
+esac
+
+var_default bindir "${prefix}/bin"
+var_default datadir "${prefix}/share"
+var_default libdir "${prefix}/lib"
+var_default mandir "${datadir}/man"
+var_default exampledir "${datadir}/doc/cmus/examples"
+
+check check_cc
+check check_host_cc
+check check_cflags
+check check_cc_depgen
+check check_endianness
+check check_compat
+check check_dl
+check check_pthread
+check check_rtsched
+check check_ncurses
+check check_iconv
+check_header byteswap.h && HAVE_BYTESWAP_H=y
+check_string_function "strdup" && HAVE_STRDUP=y
+check_string_function "strndup" && HAVE_STRNDUP=y
+
+check check_cddb CONFIG_CDDB
+check check_cdio CONFIG_CDIO
+check check_flac CONFIG_FLAC
+check check_mad CONFIG_MAD
+check check_mikmod CONFIG_MIKMOD
+check check_modplug CONFIG_MODPLUG
+check check_mpc CONFIG_MPC
+check check_vorbis CONFIG_VORBIS
+check check_wavpack CONFIG_WAVPACK
+check check_mp4 CONFIG_MP4
+check check_aac CONFIG_AAC
+check check_ffmpeg CONFIG_FFMPEG
+check check_cue CONFIG_CUE
+# nothing to check, just validate the variable values
+check true CONFIG_TREMOR
+check true CONFIG_WAV
+check check_pulse CONFIG_PULSE
+check check_alsa CONFIG_ALSA
+check check_ao CONFIG_AO
+check check_arts CONFIG_ARTS
+check check_oss CONFIG_OSS
+check check_sun CONFIG_SUN
+check check_waveout CONFIG_WAVEOUT
+check check_roar CONFIG_ROAR
+
+# discid is only needed if at least one cdda plugin is active
+test -z "$CONFIG_DISCID" && CONFIG_DISCID=a
+if test "$CONFIG_DISCID" = a
+then
+ test "$CONFIG_CDIO" = n && CONFIG_DISCID=n
+fi
+check check_discid CONFIG_DISCID
+
+test "$WORDS_BIGENDIAN" = y && CFLAGS="${CFLAGS} -DWORDS_BIGENDIAN"
+test "$HAVE_DISCID" = y && CFLAGS="${CFLAGS} -DHAVE_DISCID"
+
+DATADIR="$datadir"
+LIBDIR="$libdir"
+
+config_header config/cdio.h HAVE_CDDB
+config_header config/datadir.h DATADIR
+config_header config/libdir.h LIBDIR
+config_header config/debug.h DEBUG
+config_header config/tremor.h CONFIG_TREMOR
+config_header config/modplug.h MODPLUG_API_8
+config_header config/mpc.h MPC_SV8
+config_header config/mp4.h USE_MPEG4IP
+config_header config/curses.h HAVE_RESIZETERM HAVE_USE_DEFAULT_COLORS
+config_header config/ffmpeg.h HAVE_FFMPEG_AVCODEC_H USE_FALLBACK_IP
+config_header config/utils.h HAVE_BYTESWAP_H
+config_header config/iconv.h HAVE_ICONV
+config_header config/xmalloc.h HAVE_STRDUP HAVE_STRNDUP
+config_header config/cue.h CONFIG_CUE
+
+CFLAGS="${CFLAGS} -DHAVE_CONFIG"
+
+makefile_vars bindir datadir libdir mandir exampledir
+makefile_vars CONFIG_CDIO CONFIG_FLAC CONFIG_MAD CONFIG_MIKMOD CONFIG_MODPLUG CONFIG_MPC CONFIG_VORBIS CONFIG_WAVPACK CONFIG_WAV CONFIG_MP4 CONFIG_AAC CONFIG_FFMPEG CONFIG_CUE
+makefile_vars CONFIG_ROAR CONFIG_PULSE CONFIG_ALSA CONFIG_AO CONFIG_ARTS CONFIG_OSS CONFIG_SUN CONFIG_WAVEOUT
+
+generate_config_mk
--- /dev/null
+_cmus (originally _cmus_remote)
+ zsh completion for cmus-remote by Christian Schneider strcat AT gmx DOT net
+ extended for the main cmus binary by Frank Terbeck.
+
+cmus-updategaim.py
+ Joshua Kwan <joshk@triplehelix.org>
+
+cmus-updatepidgin.py
+ Based on Joshua's cmus-updategaim.py script.
+ David Thiel <lx@redundancy.redundancy.org>
--- /dev/null
+#compdef cmus cmus-remote
+
+local expl cmus_commands
+
+cmus_commands=(
+ add bind browser-up cd clear colorscheme echo factivate
+ filter fset invert load mark player-next player-pause
+ player-play quit refresh run save search-next search-prev
+ seek set showbind shuffle source toggle unbind unmark view
+ vol win-activate win-add-l win-add-p win-add-Q win-add-q
+ win-bottom win-down win-mv-after win-mv-before win-next
+ win-page-down win-page-up win-remove win-sel-cur
+ win-toggle win-top win-up win-update
+)
+_cmus_volume() {
+ local expl
+ compset -P '[-+]'
+ _wanted list expl volume compadd $expl - {0..100}
+}
+
+case $service in
+ (cmus-remote)
+ _arguments -C -s\
+ '--server[connect using socket SOCKET]:socket:_files' \
+ '--help[display this help and exit]:' \
+ '--version[Display version information and exit.]:' \
+ '(--play -p)'{--play,-p}'[Start playing.]:' \
+ '(--pause -u)'{--pause,-u}'[Toggle pause.]:' \
+ '(--stop -s)'{--stop,-s}'[Stop playing.]:' \
+ '(--next -n)'{--next,-n}'[Skip forward in playlist.]:' \
+ '(--prev -r)'{--prev,-r}'[Skip backward in playlist.]:' \
+ '(--repeat -R)'{--repeat,-R}'[Toggle repeat.]:' \
+ '(--shuffle -S)'{--shuffle,-S}'[Toggle shuffle.]:' \
+ '(--volume -v)'{--volume,-V+}'[Change volume. See vol command in cmus(1).]:volume:_cmus_volume' \
+ '(--seek -k)'{--seek,-k+}'[Seek. See seek command in cmus(1).]:seek [+-]<num>[m/h]'\
+ '(--library -l)'{--library,-l+}'[Modify library instead of playlist.]:playlists/files/directories/URLs:_files'\
+ '(--playlist -P)'{--playlist,-P}'[Modify playlist (default).]::Playlist:_files'\
+ '(--queue -q)'{--queue,-q}'[Modify play queue instead of playlist.]:'\
+ '(--clear -c)'{--clear,-c}'[Clear playlist, library (-l) or play queue (-q).]:playlist'\
+ '(--raw -C)'{--raw,-C+}'[Treat arguments (instead of stdin) as raw commands.]:command:(${cmus_commands[@]}):'\
+ ;;
+ (cmus)
+ _arguments \
+ '--listen[listen on ADDR instead of ~/.cmus/socket]:socket:_files' \
+ '--plugins[list available plugins and exit]' \
+ '--help[display this help and exit]' \
+ '--version[display version information]'
+ ;;
+esac
+
--- /dev/null
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import dbus
+import sys
+
+args = {}
+
+for n in range(1, len(sys.argv) - 1, 2):
+ args[sys.argv[n]] = sys.argv[n + 1]
+
+obj = dbus.SessionBus().get_object("net.sf.gaim.GaimService", "/net/sf/gaim/GaimObject")
+gaim = dbus.Interface(obj, "net.sf.gaim.GaimInterface")
+
+current = gaim.GaimSavedstatusGetCurrent()
+status_type = gaim.GaimSavedstatusGetType(current)
+saved = gaim.GaimSavedstatusNew("", status_type)
+gaim.GaimSavedstatusSetMessage(saved, "♪ %s - %s" % (args["artist"], args["title"]))
+gaim.GaimSavedstatusActivate(saved)
+
--- /dev/null
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import dbus
+import sys
+
+args = {}
+
+for n in range(1, len(sys.argv) - 1, 2):
+ args[sys.argv[n]] = sys.argv[n + 1]
+
+print args
+
+bus = dbus.SessionBus()
+
+obj = bus.get_object("im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject")
+pidgin = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
+
+current = pidgin.PurpleSavedstatusGetCurrent()
+status_type = pidgin.PurpleSavedstatusGetType(current)
+saved = pidgin.PurpleSavedstatusNew("", status_type)
+pidgin.PurpleSavedstatusSetMessage(saved, "♪ %s - %s" % (args["artist"], args["title"]))
+pidgin.PurpleSavedstatusActivate(saved)
+
+
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "convert.h"
+#include "xmalloc.h"
+#include "uchar.h"
+#ifdef HAVE_CONFIG
+#include "config/iconv.h"
+#endif
+
+#ifdef HAVE_ICONV
+#include <iconv.h>
+#endif
+#include <string.h>
+#include <errno.h>
+
+ssize_t convert(const char *inbuf, ssize_t inbuf_size,
+ char **outbuf, ssize_t outbuf_estimate,
+ const char *tocode, const char *fromcode)
+{
+#ifdef HAVE_ICONV
+ const char *in;
+ char *out;
+ size_t outbuf_size, inbytesleft, outbytesleft;
+ iconv_t cd;
+ int rc, finished = 0, err_save;
+
+ cd = iconv_open(tocode, fromcode);
+ if (cd == (iconv_t) -1)
+ return -1;
+
+ if (inbuf_size < 0)
+ inbuf_size = strlen(inbuf);
+ inbytesleft = inbuf_size;
+
+ if (outbuf_estimate < 0)
+ outbuf_size = inbuf_size;
+ else
+ outbuf_size = outbuf_estimate;
+ outbytesleft = outbuf_size;
+
+ in = inbuf;
+ out = *outbuf = xnew(char, outbuf_size + 1);
+
+ while (!finished) {
+ finished = 1;
+ rc = iconv(cd, (char **)&in, &inbytesleft, &out, &outbytesleft);
+ if (rc == (size_t) -1) {
+ if (errno == E2BIG) {
+ size_t used = out - *outbuf;
+ outbytesleft += outbuf_size;
+ outbuf_size *= 2;
+ *outbuf = xrenew(char, *outbuf, outbuf_size + 1);
+ out = *outbuf + used;
+ continue;
+ } else if (errno != EINVAL)
+ goto error;
+ }
+ }
+ /* NUL-terminate for safety reasons */
+ *out = '\0';
+ iconv_close(cd);
+ return outbuf_size - outbytesleft;
+
+error:
+ err_save = errno;
+ free(*outbuf);
+ *outbuf = NULL;
+ iconv_close(cd);
+ errno = err_save;
+ return -1;
+
+#else
+ if (inbuf_size < 0)
+ inbuf_size = strlen(inbuf);
+ *outbuf = xnew(char, inbuf_size + 1);
+ memcpy(*outbuf, inbuf, inbuf_size);
+ (*outbuf)[inbuf_size] = '\0';
+ return inbuf_size;
+#endif
+}
+
+int utf8_encode(const char *inbuf, const char *encoding, char **outbuf)
+{
+ size_t inbuf_size, outbuf_size, i;
+ int rc;
+
+ inbuf_size = strlen(inbuf);
+ outbuf_size = inbuf_size;
+ for (i = 0; i < inbuf_size; i++) {
+ unsigned char ch;
+
+ ch = inbuf[i];
+ if (ch > 127)
+ outbuf_size++;
+ }
+
+ rc = convert(inbuf, inbuf_size, outbuf, outbuf_size, "UTF-8", encoding);
+
+ return rc < 0 ? -1 : 0;
+}
+
+char *to_utf8(const char *str, const char *enc)
+{
+ char *outbuf = NULL;
+ int rc;
+
+ if (u_is_valid(str)) {
+ return xstrdup(str);
+ } else {
+ rc = utf8_encode(str, enc, &outbuf);
+ return rc < 0 ? xstrdup(str) : outbuf;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CONVERT_H
+#define CONVERT_H
+
+#include <sys/types.h> /* ssize_t */
+
+/* Returns length of *outbuf in bytes (without closing '\0'), -1 on error. */
+ssize_t convert(const char *inbuf, ssize_t inbuf_size,
+ char **outbuf, ssize_t outbuf_estimate,
+ const char *tocode, const char *fromcode);
+
+int utf8_encode(const char *inbuf, const char *encoding, char **outbuf);
+
+char *to_utf8(const char *str, const char *enc);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) 2008-2011 Various Authors
+ * Copyright (C) 2011 Gregory Petrosyan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "debug.h"
+#include "input.h"
+#include "utils.h"
+#include "comment.h"
+#include "xmalloc.h"
+#include "cue_utils.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+
+
+struct cue_private {
+ struct input_plugin *child;
+
+ char *cue_filename;
+ int track_n;
+
+ double start_offset;
+ double current_offset;
+ double end_offset;
+};
+
+
+static int __parse_cue_url(const char *url, char **filename, int *track_n)
+{
+ const char *slash;
+ long n;
+
+ if (!is_cue_url(url))
+ return 1;
+
+ url += 6;
+
+ slash = strrchr(url, '/');
+ if (!slash)
+ return 1;
+
+ if (str_to_int(slash + 1, &n) != 0)
+ return 1;
+
+ *filename = xstrndup(url, slash - url);
+ *track_n = n;
+ return 0;
+}
+
+
+static double __to_seconds(long v)
+{
+ const int FRAMES_IN_SECOND = 75;
+
+ return (double)v / FRAMES_IN_SECOND;
+}
+
+
+static char *__make_absolute_path(const char *abs_filename, const char *rel_filename)
+{
+ char *s;
+ const char *slash;
+ char buf[4096] = {0};
+
+ slash = strrchr(abs_filename, '/');
+ if (slash == NULL)
+ return xstrdup(rel_filename);
+
+ s = xstrndup(abs_filename, slash - abs_filename);
+ snprintf(buf, sizeof buf, "%s/%s", s, rel_filename);
+
+ free(s);
+ return xstrdup(buf);
+}
+
+
+static int cue_open(struct input_plugin_data *ip_data)
+{
+ int rc;
+ FILE *cue;
+ Cd *cd;
+ Track *t;
+ char *child_filename;
+ struct cue_private *priv;
+
+ priv = xnew(struct cue_private, 1);
+
+ rc = __parse_cue_url(ip_data->filename, &priv->cue_filename, &priv->track_n);
+ if (rc) {
+ rc = -IP_ERROR_INVALID_URI;
+ goto url_parse_failed;
+ }
+
+ cue = fopen(priv->cue_filename, "r");
+ if (cue == NULL) {
+ rc = -IP_ERROR_ERRNO;
+ goto cue_open_failed;
+ }
+
+ cd = cue_parse_file__no_stderr_garbage(cue);
+ if (cd == NULL) {
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto cue_parse_failed;
+ }
+
+ t = cd_get_track(cd, priv->track_n);
+ if (t == NULL) {
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto cue_read_failed;
+ }
+
+ child_filename = track_get_filename(t);
+ if (child_filename == NULL) {
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto cue_read_failed;
+ }
+ child_filename = __make_absolute_path(priv->cue_filename, child_filename);
+
+ priv->child = ip_new(child_filename);
+ free(child_filename);
+
+ rc = ip_open(priv->child);
+ if (rc)
+ goto ip_open_failed;
+
+ ip_setup(priv->child);
+
+ priv->start_offset = __to_seconds(track_get_start(t));
+ priv->current_offset = priv->start_offset;
+
+ rc = ip_seek(priv->child, priv->start_offset);
+ if (rc)
+ goto ip_open_failed;
+
+ if (track_get_length(t) != 0)
+ priv->end_offset = priv->start_offset + __to_seconds(track_get_length(t));
+ else
+ priv->end_offset = ip_duration(priv->child);
+
+ ip_data->fd = open(ip_get_filename(priv->child), O_RDONLY);
+ if (ip_data->fd == -1)
+ goto ip_open_failed;
+
+ ip_data->private = priv;
+ ip_data->sf = ip_get_sf(priv->child);
+ ip_get_channel_map(priv->child, ip_data->channel_map);
+
+ fclose(cue);
+ cd_delete(cd);
+ return 0;
+
+ip_open_failed:
+ ip_delete(priv->child);
+
+cue_read_failed:
+ cd_delete(cd);
+
+cue_parse_failed:
+ fclose(cue);
+
+cue_open_failed:
+ free(priv->cue_filename);
+
+url_parse_failed:
+ free(priv);
+
+ return rc;
+}
+
+
+static int cue_close(struct input_plugin_data *ip_data)
+{
+ struct cue_private *priv = ip_data->private;
+
+ close(ip_data->fd);
+ ip_data->fd = -1;
+
+ ip_delete(priv->child);
+ free(priv->cue_filename);
+
+ free(priv);
+ ip_data->private = NULL;
+
+ return 0;
+}
+
+
+static int cue_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ int rc;
+ sample_format_t sf;
+ double len;
+ double rem_len;
+ struct cue_private *priv = ip_data->private;
+
+ if (priv->current_offset >= priv->end_offset)
+ return 0;
+
+ rc = ip_read(priv->child, buffer, count);
+ if (rc <= 0)
+ return rc;
+
+ sf = ip_get_sf(priv->child);
+ len = (double)rc / sf_get_second_size(sf);
+
+ rem_len = priv->end_offset - priv->current_offset;
+ priv->current_offset += len;
+
+ if (priv->current_offset >= priv->end_offset)
+ rc = (int)(rem_len * sf_get_rate(sf)) * sf_get_frame_size(sf);
+
+ return rc;
+}
+
+
+static int cue_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct cue_private *priv = ip_data->private;
+ double new_offset = priv->start_offset + offset;
+
+ if (new_offset > priv->end_offset)
+ new_offset = priv->end_offset;
+
+ priv->current_offset = new_offset;
+
+ return ip_seek(priv->child, new_offset);
+}
+
+
+static int cue_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
+{
+ int rc;
+ FILE *cue;
+ Cd *cd;
+ Rem *cd_rem;
+ Cdtext *cd_cdtext;
+ Track *t;
+ Rem *track_rem;
+ Cdtext *track_cdtext;
+ char *val;
+ char buf[32] = {0};
+ GROWING_KEYVALS(c);
+ struct cue_private *priv = ip_data->private;
+
+ cue = fopen(priv->cue_filename, "r");
+ if (cue == NULL) {
+ rc = -IP_ERROR_ERRNO;
+ goto cue_open_failed;
+ }
+
+ cd = cue_parse_file__no_stderr_garbage(cue);
+ if (cd == NULL) {
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto cue_parse_failed;
+ }
+
+ t = cd_get_track(cd, priv->track_n);
+ if (t == NULL) {
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto get_track_failed;
+ }
+
+ snprintf(buf, sizeof buf, "%d", priv->track_n);
+ comments_add(&c, "tracknumber", xstrdup(buf));
+
+ cd_rem = cd_get_rem(cd);
+ cd_cdtext = cd_get_cdtext(cd);
+ track_rem = track_get_rem(t);
+ track_cdtext = track_get_cdtext(t);
+
+ val = cdtext_get(PTI_TITLE, track_cdtext);
+ if (val != NULL)
+ comments_add(&c, "title", xstrdup(val));
+
+ val = cdtext_get(PTI_TITLE, cd_cdtext);
+ if (val != NULL)
+ comments_add(&c, "album", xstrdup(val));
+
+ val = cdtext_get(PTI_PERFORMER, track_cdtext);
+ if (val != NULL)
+ comments_add(&c, "artist", xstrdup(val));
+
+ val = cdtext_get(PTI_PERFORMER, cd_cdtext);
+ if (val != NULL)
+ comments_add(&c, "albumartist", xstrdup(val));
+
+ val = rem_get(REM_DATE, track_rem);
+ if (val != NULL) {
+ comments_add(&c, "date", xstrdup(val));
+ } else {
+ val = rem_get(REM_DATE, cd_rem);
+ if (val != NULL)
+ comments_add(&c, "date", xstrdup(val));
+ }
+
+ /*
+ * TODO:
+ * - replaygain REMs
+ * - genre?
+ */
+
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+
+ cd_delete(cd);
+ fclose(cue);
+ return 0;
+
+get_track_failed:
+ cd_delete(cd);
+
+cue_parse_failed:
+ fclose(cue);
+
+cue_open_failed:
+ return rc;
+}
+
+
+static int cue_duration(struct input_plugin_data *ip_data)
+{
+ struct cue_private *priv = ip_data->private;
+
+ return priv->end_offset - priv->start_offset;
+}
+
+
+static long cue_bitrate(struct input_plugin_data *ip_data)
+{
+ struct cue_private *priv = ip_data->private;
+
+ return ip_bitrate(priv->child);
+}
+
+
+static long cue_current_bitrate(struct input_plugin_data *ip_data)
+{
+ struct cue_private *priv = ip_data->private;
+
+ return ip_current_bitrate(priv->child);
+}
+
+
+static char *cue_codec(struct input_plugin_data *ip_data)
+{
+ struct cue_private *priv = ip_data->private;
+
+ return ip_codec(priv->child);
+}
+
+
+static char *cue_codec_profile(struct input_plugin_data *ip_data)
+{
+ struct cue_private *priv = ip_data->private;
+
+ return ip_codec_profile(priv->child);
+}
+
+
+static int cue_set_option(int key, const char *val)
+{
+ return -IP_ERROR_NOT_OPTION;
+}
+
+
+static int cue_get_option(int key, char **val)
+{
+ return -IP_ERROR_NOT_OPTION;
+}
+
+
+const struct input_plugin_ops ip_ops = {
+ .open = cue_open,
+ .close = cue_close,
+ .read = cue_read,
+ .seek = cue_seek,
+ .read_comments = cue_read_comments,
+ .duration = cue_duration,
+ .bitrate = cue_bitrate,
+ .bitrate_current = cue_current_bitrate,
+ .codec = cue_codec,
+ .codec_profile = cue_codec_profile,
+ .set_option = cue_set_option,
+ .get_option = cue_get_option
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = { NULL };
+const char * const ip_mime_types[] = { "application/x-cue", NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright (C) 2008-2011 Various Authors
+ * Copyright (C) 2011 Gregory Petrosyan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "path.h"
+#include "cue_utils.h"
+#include "xmalloc.h"
+
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+
+Cd *cue_parse_file__no_stderr_garbage(FILE *f)
+{
+ int stderr_fd;
+ int devnull_fd;
+ Cd *ret;
+
+ stderr_fd = dup(2);
+ devnull_fd = open("/dev/null", O_WRONLY);
+
+ if (devnull_fd != -1)
+ dup2(devnull_fd, 2);
+
+ ret = cue_parse_file(f);
+
+ if (stderr_fd != -1) {
+ dup2(stderr_fd, 2);
+ close(stderr_fd);
+ }
+
+ if (devnull_fd != -1)
+ close(devnull_fd);
+
+ return ret;
+}
+
+
+char *associated_cue(const char *filename)
+{
+ FILE *fp;
+ const char *ext;
+ char buf[4096] = {0};
+ const char *dot;
+
+ ext = get_extension(filename);
+ if (ext != NULL && strcmp(ext, "cue") == 0)
+ return NULL;
+
+ dot = strrchr(filename, '.');
+ if (dot == NULL)
+ return NULL;
+
+ snprintf(buf, sizeof buf, "%.*s.cue", (int) (dot - filename), filename);
+ fp = fopen(buf, "r");
+ if (!fp)
+ snprintf(buf, sizeof buf, "%s.cue", filename);
+ else
+ fclose(fp);
+
+ return xstrdup(buf);
+}
+
+
+int cue_get_ntracks(const char *filename)
+{
+ int n;
+ FILE *cue;
+ Cd *cd;
+
+ cue = fopen(filename, "r");
+ if (cue == NULL)
+ return -1;
+
+ cd = cue_parse_file__no_stderr_garbage(cue);
+ if (cd == NULL) {
+ fclose(cue);
+ return -1;
+ }
+
+ n = cd_get_ntrack(cd);
+
+ cd_delete(cd);
+ fclose(cue);
+
+ return n;
+}
+
+
+char *construct_cue_url(const char *cue_filename, int track_n)
+{
+ char buf[4096] = {0};
+
+ snprintf(buf, sizeof buf, "cue://%s/%d", cue_filename, track_n);
+
+ return xstrdup(buf);
+}
--- /dev/null
+/*
+ * Copyright (C) 2008-2011 Various Authors
+ * Copyright (C) 2011 Gregory Petrosyan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef __CUE_UTILS_H__
+#define __CUE_UTILS_H__
+
+#include <stdio.h>
+
+/*
+ * warning: this header does not contain include guards!
+ */
+#include <libcue/libcue.h>
+
+
+/*
+ * libcue developers think that printing parsing errors to stderr is a good idea
+ * they are wrong
+ */
+Cd *cue_parse_file__no_stderr_garbage(FILE *f);
+
+char *associated_cue(const char *filename);
+int cue_get_ntracks(const char *filename);
+char *construct_cue_url(const char *cue_filename, int track_n);
+
+
+#endif
--- /dev/null
+set color_cmdline_bg=default
+set color_cmdline_fg=cyan
+set color_error=lightred
+set color_info=lightyellow
+set color_separator=cyan
+set color_statusline_bg=default
+set color_statusline_fg=cyan
+set color_titleline_bg=blue
+set color_titleline_fg=white
+set color_win_bg=default
+set color_win_cur=green
+set color_win_cur_sel_bg=blue
+set color_win_cur_sel_fg=lightyellow
+set color_win_dir=lightblue
+set color_win_fg=cyan
+set color_win_inactive_cur_sel_bg=cyan
+set color_win_inactive_cur_sel_fg=black
+set color_win_inactive_sel_bg=gray
+set color_win_inactive_sel_fg=black
+set color_win_sel_bg=red
+set color_win_sel_fg=gray
+set color_win_title_bg=blue
+set color_win_title_fg=white
--- /dev/null
+set color_cmdline_bg=default
+set color_cmdline_fg=default
+set color_error=lightred
+set color_info=lightyellow
+set color_separator=blue
+set color_statusline_bg=gray
+set color_statusline_fg=black
+set color_titleline_bg=blue
+set color_titleline_fg=white
+set color_win_bg=default
+set color_win_cur=lightyellow
+set color_win_cur_sel_bg=blue
+set color_win_cur_sel_fg=lightyellow
+set color_win_dir=lightblue
+set color_win_fg=default
+set color_win_inactive_cur_sel_bg=gray
+set color_win_inactive_cur_sel_fg=lightyellow
+set color_win_inactive_sel_bg=gray
+set color_win_inactive_sel_fg=black
+set color_win_sel_bg=blue
+set color_win_sel_fg=white
+set color_win_title_bg=blue
+set color_win_title_fg=white
--- /dev/null
+# 88 color gray theme for urxvt
+set color_cmdline_bg=80
+set color_cmdline_fg=85
+set color_error=lightgreen
+set color_info=green
+set color_separator=darkgray
+set color_statusline_bg=80
+set color_statusline_fg=85
+set color_titleline_bg=darkgray
+set color_titleline_fg=85
+set color_win_bg=80
+set color_win_cur=lightgreen
+set color_win_cur_sel_bg=82
+set color_win_cur_sel_fg=lightyellow
+set color_win_dir=83
+set color_win_fg=85
+set color_win_inactive_cur_sel_bg=darkgray
+set color_win_inactive_cur_sel_fg=lightgreen
+set color_win_inactive_sel_bg=darkgray
+set color_win_inactive_sel_fg=black
+set color_win_sel_bg=82
+set color_win_sel_fg=black
+set color_win_title_bg=darkgray
+set color_win_title_fg=85
--- /dev/null
+# Clay's Green Monochromatic Color Scheme 1.0 and example file.
+#
+# This theme is designed to be 1) Very readable on high-resolution or blurry
+# displays (dying CRTs and TVs, for example). 2) Be more accessable to color-
+# blind people, by removing chroma cues in favor of brightness and color
+# inversion. 3) Show how to incorperate named and numbered colors into a
+# single theme. 4) Provide a stating point for creating other mono-chromatic
+# themes.
+#
+# Questions, comments (just a heads-up that you use/like this, especially),
+# and such should be directed to clay.barnes@gmail.com
+
+##### Common User Interface Components #########################################
+# Default text color
+set color_win_fg=green
+
+# Overall background color
+set color_win_bg=black
+
+# Command-line colors
+set color_cmdline_bg=black
+set color_cmdline_fg=green
+set color_error=lightgreen
+set color_info=green
+set color_separator=green
+
+# Bottom status line
+set color_statusline_bg=black
+set color_statusline_fg=green
+
+# Bottom title line
+set color_titleline_bg=green
+set color_titleline_fg=black
+
+# Top title area
+set color_win_title_bg=green
+set color_win_title_fg=black
+##### Playing File Colors ######################################################
+# Unselected currently playing track's text
+set color_win_cur=lightgreen
+
+# Active selection for currently playing track
+set color_win_cur_sel_bg=28
+set color_win_cur_sel_fg=darkgray
+
+# Inactive selection for currently playing track
+set color_win_inactive_cur_sel_bg=20
+set color_win_inactive_cur_sel_fg=lightgreen
+
+##### Non-Playing File Colors ##################################################
+# Active selection
+set color_win_sel_bg=green
+set color_win_sel_fg=black
+
+# Inactive selection
+set color_win_inactive_sel_bg=20
+set color_win_inactive_sel_fg=black
+
+##### File Browser View Colors #################################################
+# Directory listing color
+set color_win_dir=lightgreen
+
--- /dev/null
+# Another Green Theme for cmus
+#
+# Change this to a red or blue theme by doing the following in vim:
+#
+# :%s/lightgreen/light<color>/g
+# :%s/green/<color>/g
+#
+# - roobert@gmail.com
+
+# Directory colors
+set color_win_dir=default
+
+# Normal text
+set color_win_fg=default
+
+# Window background color.
+set color_win_bg=default
+
+# Command line color.
+set color_cmdline_bg=default
+set color_cmdline_fg=default
+
+# Color of error messages displayed on the command line.
+set color_error=lightred
+
+# Color of informational messages displayed on the command line.
+set color_info=lightgreen
+
+# Color of currently playing track.
+set color_win_cur=lightgreen
+
+# Color of the separator line between windows in view (1).
+set color_separator=black
+
+# Color of window titles (topmost line of the screen).
+set color_win_title_bg=default
+set color_win_title_fg=green
+
+# Status line color.
+set color_statusline_bg=default
+set color_statusline_fg=gray
+
+# Color of the line displaying currently playing track.
+set color_titleline_bg=default
+set color_titleline_fg=green
+
+# Color of the selected row which is also the currently playing track in active window.
+set color_win_cur_sel_bg=default
+set color_win_cur_sel_fg=white
+
+# Color of the selected row which is also the currently playing track in inactive window.
+set color_win_inactive_cur_sel_bg=default
+set color_win_inactive_cur_sel_fg=lightgreen
+
+# Color of selected row in inactive window.
+set color_win_inactive_sel_bg=default
+set color_win_inactive_sel_fg=default
+
+# Color of selected row in active window.
+set color_win_sel_bg=default
+set color_win_sel_fg=green
+
+# Command line color.
+set color_cmdline_bg=default
+set color_cmdline_fg=default
--- /dev/null
+bind browser backspace browser-up
+bind browser space win-activate
+bind browser i toggle show_hidden
+bind browser u win-update
+bind common ! push shell
+bind common + vol +10%
+bind common , seek -1m
+bind common - vol -10%
+bind common . seek +1m
+bind common 1 view tree
+bind common 2 view sorted
+bind common 3 view playlist
+bind common 4 view queue
+bind common 5 view browser
+bind common 6 view filters
+bind common 7 view settings
+bind common = vol +10%
+bind common C toggle continue
+bind common D win-remove
+bind common E win-add-Q
+bind common F push filter
+bind common G win-bottom
+bind common I echo {}
+bind common L push live-filter
+bind common M toggle play_library
+bind common N search-prev
+bind common P win-mv-before
+bind common [ vol +1% +0
+bind common ] vol +0 +1%
+bind common ^B win-page-up
+bind common ^C echo Type :quit<enter> to exit cmus.
+bind common ^F win-page-down
+bind common ^L refresh
+bind common ^R toggle repeat_current
+bind common a win-add-l
+bind common b player-next
+bind common c player-pause
+bind common delete win-remove
+bind common down win-down
+bind common e win-add-q
+bind common end win-bottom
+bind common enter win-activate
+bind common g win-top
+bind common h seek -5
+bind common home win-top
+bind common i win-sel-cur
+bind common j win-down
+bind common k win-up
+bind common l seek +5
+bind common left seek -5
+bind common m toggle aaa_mode
+bind common n search-next
+bind common o toggle play_sorted
+bind common p win-mv-after
+bind common q quit -i
+bind common page_down win-page-down
+bind common page_up win-page-up
+bind common r toggle repeat
+bind common right seek +5
+bind common s toggle shuffle
+bind common space win-toggle
+bind common t toggle show_remaining_time
+bind common tab win-next
+bind common u update-cache
+bind common U win-update-cache
+bind common up win-up
+bind common v player-stop
+bind common x player-play
+bind common y win-add-p
+bind common z player-prev
+bind common { vol -1% -0
+bind common } vol -0 -1%
+fset 90s=date>=1990&date<2000
+fset classical=genre="Classical"
+fset missing-tag=!stream&(artist=""|album=""|title=""|tracknumber=-1|date=-1)
+fset mp3=filename="*.mp3"
+fset ogg=filename="*.ogg"
+fset ogg-or-mp3=ogg|mp3
--- /dev/null
+# looks good with xterm using default settings (white bg)
+set color_cmdline_bg=default
+set color_cmdline_fg=default
+set color_error=lightred
+set color_info=lightblue
+set color_separator=default
+set color_statusline_bg=default
+set color_statusline_fg=default
+set color_titleline_bg=gray
+set color_titleline_fg=default
+set color_win_bg=default
+set color_win_cur=lightblue
+set color_win_cur_sel_bg=green
+set color_win_cur_sel_fg=lightblue
+set color_win_dir=blue
+set color_win_fg=default
+set color_win_inactive_cur_sel_bg=gray
+set color_win_inactive_cur_sel_fg=lightblue
+set color_win_inactive_sel_bg=gray
+set color_win_inactive_sel_fg=default
+set color_win_sel_bg=green
+set color_win_sel_fg=default
+set color_win_title_bg=gray
+set color_win_title_fg=black
--- /dev/null
+# Directory colors
+set color_win_dir=108
+
+# Normal text
+set color_win_fg=188
+
+# Window background color.
+set color_win_bg=237
+
+# Command line color.
+set color_cmdline_bg=237
+set color_cmdline_fg=108
+
+# Color of error messages displayed on the command line.
+set color_error=lightred
+
+# Color of informational messages displayed on the command line.
+set color_info=lightgreen
+
+# Color of the separator line between windows in view (1).
+set color_separator=246
+
+# Color of window titles (topmost line of the screen).
+set color_win_title_bg=235
+set color_win_title_fg=174
+
+# Status line color.
+set color_statusline_bg=237
+set color_statusline_fg=142
+
+# Color of currently playing track.
+set color_win_cur=172
+
+# Color of the line displaying currently playing track.
+set color_titleline_bg=235
+set color_titleline_fg=144
+
+# Color of the selected row which is also the currently playing track in active window.
+set color_win_cur_sel_bg=235
+set color_win_cur_sel_fg=223
+
+# Color of the selected row which is also the currently playing track in inactive window.
+set color_win_inactive_cur_sel_bg=238
+set color_win_inactive_cur_sel_fg=116
+
+# Color of selected row in active window.
+set color_win_sel_bg=235
+set color_win_sel_fg=223
+
+# Color of selected row in inactive window.
+set color_win_inactive_sel_bg=238
+set color_win_inactive_sel_fg=116
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "debug.h"
+#include "prog.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+#if DEBUG > 1
+static FILE *debug_stream = NULL;
+#endif
+
+void debug_init(void)
+{
+#if DEBUG > 1
+ char filename[512];
+ const char *dir = getenv("CMUS_HOME");
+
+ if (!dir || !dir[0]) {
+ dir = getenv("HOME");
+ if (!dir)
+ die("error: environment variable HOME not set\n");
+ }
+ snprintf(filename, sizeof(filename), "%s/cmus-debug.txt", dir);
+
+ debug_stream = fopen(filename, "w");
+ if (debug_stream == NULL)
+ die_errno("error opening `%s' for writing", filename);
+#endif
+}
+
+/* This function must be defined even if debugging is disabled in the program
+ * because debugging might still be enabled in some plugin.
+ */
+void __debug_bug(const char *function, const char *fmt, ...)
+{
+ const char *format = "\n%s: BUG: ";
+ va_list ap;
+
+ /* debug_stream exists only if debugging is enabled */
+#if DEBUG > 1
+ fprintf(debug_stream, format, function);
+ va_start(ap, fmt);
+ vfprintf(debug_stream, fmt, ap);
+ va_end(ap);
+#endif
+
+ /* always print bug message to stderr */
+ fprintf(stderr, format, function);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ exit(127);
+}
+
+void __debug_print(const char *function, const char *fmt, ...)
+{
+#if DEBUG > 1
+ va_list ap;
+
+ fprintf(debug_stream, "%s: ", function);
+ va_start(ap, fmt);
+ vfprintf(debug_stream, fmt, ap);
+ va_end(ap);
+ fflush(debug_stream);
+#endif
+}
+
+uint64_t timer_get(void)
+{
+#if DEBUG > 1
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ return tv.tv_sec * 1e6L + tv.tv_usec;
+#else
+ return 0;
+#endif
+}
+
+void timer_print(const char *what, uint64_t usec)
+{
+#if DEBUG > 1
+ uint64_t a = usec / 1e6;
+ uint64_t b = usec - a * 1e6;
+
+ __debug_print("TIMER", "%s: %11u.%06u\n", what, (unsigned int)a, (unsigned int)b);
+#endif
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DEBUG_H
+#define DEBUG_H
+
+#include "compiler.h"
+#ifdef HAVE_CONFIG
+#include "config/debug.h"
+#endif
+
+#include <errno.h>
+#include <stdint.h>
+
+void debug_init(void);
+void __debug_bug(const char *function, const char *fmt, ...) __FORMAT(2, 3) __NORETURN;
+void __debug_print(const char *function, const char *fmt, ...) __FORMAT(2, 3);
+
+uint64_t timer_get(void);
+void timer_print(const char *what, uint64_t usec);
+
+#define BUG(...) __debug_bug(__FUNCTION__, __VA_ARGS__)
+
+#define __STR(a) #a
+
+#define BUG_ON(a) \
+do { \
+ if (unlikely(a)) \
+ BUG("%s\n", __STR(a)); \
+} while (0)
+
+#define d_print(...) __debug_print(__FUNCTION__, __VA_ARGS__)
+
+#endif
--- /dev/null
+/*
+ * Copyright 2011 Various Authors
+ * Copyright 2011 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "discid.h"
+#include "xmalloc.h"
+#include "path.h"
+#include "utils.h"
+#include "debug.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <limits.h>
+
+#ifdef HAVE_DISCID
+#include <discid/discid.h>
+#endif
+
+char *get_default_cdda_device(void)
+{
+ const char *dev = NULL;
+#ifdef HAVE_DISCID
+ dev = discid_get_default_device();
+#endif
+ if (!dev)
+ dev = "/dev/cdrom";
+ return xstrdup(dev);
+}
+
+int parse_cdda_url(const char *url, char **disc_id, int *start_track, int *end_track)
+{
+ char *slash, *dash;
+ long int t;
+
+ if (!is_cdda_url(url))
+ return 0;
+ url += 7;
+
+ slash = strrchr(url, '/');
+ if (slash) {
+ *disc_id = xstrndup(url, slash - url);
+ url = slash + 1;
+ }
+ dash = strchr(url, '-');
+ if (dash) {
+ char *tmp = xstrndup(url, dash - url);
+ if (str_to_int(tmp, &t) == 0)
+ *start_track = t;
+ if (end_track) {
+ if (str_to_int(dash + 1, &t) == 0)
+ *end_track = t;
+ else
+ *end_track = INT_MAX;
+ }
+ free(tmp);
+ } else {
+ if (str_to_int(url, &t) == 0)
+ *start_track = t;
+ }
+
+ return 1;
+}
+
+char *gen_cdda_url(const char *disc_id, int start_track, int end_track)
+{
+ char buf[256];
+ if (end_track != -1)
+ snprintf(buf, sizeof(buf), "cdda://%s/%d-%d", disc_id, start_track, end_track);
+ else
+ snprintf(buf, sizeof(buf), "cdda://%s/%d", disc_id, start_track);
+ return xstrdup(buf);
+}
+
+char *complete_cdda_url(const char *device, const char *url)
+{
+ char *new_url, *url_disc_id = NULL, *disc_id = NULL;
+ int is_range, start_track = -1, end_track = -1, num_tracks = -1;
+
+ parse_cdda_url(url, &url_disc_id, &start_track, &end_track);
+ is_range = (start_track == -1 && end_track == -1) || end_track == INT_MAX;
+ if (!url_disc_id || is_range) {
+ if (url_disc_id && strchr(url_disc_id, '/'))
+ device = url_disc_id;
+ get_disc_id(device, &disc_id, &num_tracks);
+ if (is_range)
+ end_track = num_tracks;
+ if (!url_disc_id)
+ url_disc_id = disc_id;
+ }
+ if (start_track == -1)
+ start_track = 1;
+
+ new_url = gen_cdda_url(url_disc_id, start_track, end_track);
+ free(disc_id);
+
+ return new_url;
+}
+
+static int get_device_disc_id(const char *device, char **disc_id, int *num_tracks)
+{
+#ifdef HAVE_DISCID
+ DiscId *disc = discid_new();
+ if (!disc)
+ return 0;
+
+ if (!discid_read(disc, device)) {
+ d_print("%s\n", discid_get_error_msg(disc));
+ discid_free(disc);
+ return 0;
+ }
+
+ *disc_id = xstrdup(discid_get_id(disc));
+ if (num_tracks)
+ *num_tracks = discid_get_last_track_num(disc);
+
+ discid_free(disc);
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+int get_disc_id(const char *device, char **disc_id, int *num_tracks)
+{
+ struct stat st;
+
+ if (stat(device, &st) == -1)
+ return 0;
+
+ if (S_ISBLK(st.st_mode))
+ return get_device_disc_id(device, disc_id, num_tracks);
+
+ *disc_id = path_absolute(device);
+ return 1;
+}
--- /dev/null
+/*
+ * Copyright 2011 Various Authors
+ * Copyright 2011 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _DISCID_H
+#define _DISCID_H
+
+char *get_default_cdda_device(void);
+int parse_cdda_url(const char *url, char **disc_id, int *start_track, int *end_track);
+char *gen_cdda_url(const char *disc_id, int start_track, int end_track);
+char *complete_cdda_url(const char *device, const char *url);
+int get_disc_id(const char *device, char **disc_id, int *num_tracks);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "editable.h"
+#include "search.h"
+#include "track.h"
+#include "track_info.h"
+#include "expr.h"
+#include "filters.h"
+#include "locking.h"
+#include "mergesort.h"
+#include "xmalloc.h"
+
+pthread_mutex_t editable_mutex = CMUS_MUTEX_INITIALIZER;
+
+static const struct searchable_ops simple_search_ops = {
+ .get_prev = simple_track_get_prev,
+ .get_next = simple_track_get_next,
+ .get_current = simple_track_search_get_current,
+ .matches = simple_track_search_matches
+};
+
+static struct simple_track *get_selected(struct editable *e)
+{
+ struct iter sel;
+
+ if (window_get_sel(e->win, &sel))
+ return iter_to_simple_track(&sel);
+ return NULL;
+}
+
+void editable_init(struct editable *e, void (*free_track)(struct list_head *item))
+{
+ struct iter iter;
+
+ list_init(&e->head);
+ e->tree_root = RB_ROOT;
+ e->nr_tracks = 0;
+ e->nr_marked = 0;
+ e->total_time = 0;
+ e->sort_keys = xnew(sort_key_t, 1);
+ e->sort_keys[0] = SORT_INVALID;
+ e->sort_str[0] = 0;
+ e->free_track = free_track;
+
+ e->win = window_new(simple_track_get_prev, simple_track_get_next);
+ window_set_contents(e->win, &e->head);
+
+ iter.data0 = &e->head;
+ iter.data1 = NULL;
+ iter.data2 = NULL;
+ e->searchable = searchable_new(e->win, &iter, &simple_search_ops);
+}
+
+static void do_editable_add(struct editable *e, struct simple_track *track, int tiebreak)
+{
+ sorted_list_add_track(&e->head, &e->tree_root, track, e->sort_keys, tiebreak);
+ e->nr_tracks++;
+ if (track->info->duration != -1)
+ e->total_time += track->info->duration;
+ window_changed(e->win);
+}
+
+void editable_add(struct editable *e, struct simple_track *track)
+{
+ do_editable_add(e, track, +1);
+}
+
+void editable_add_before(struct editable *e, struct simple_track *track)
+{
+ do_editable_add(e, track, -1);
+}
+
+void editable_remove_track(struct editable *e, struct simple_track *track)
+{
+ struct track_info *ti = track->info;
+ struct iter iter;
+
+ editable_track_to_iter(e, track, &iter);
+ window_row_vanishes(e->win, &iter);
+
+ e->nr_tracks--;
+ e->nr_marked -= track->marked;
+ if (ti->duration != -1)
+ e->total_time -= ti->duration;
+
+ sorted_list_remove_track(&e->head, &e->tree_root, track);
+ e->free_track(&track->node);
+}
+
+void editable_remove_sel(struct editable *e)
+{
+ struct simple_track *t;
+
+ if (e->nr_marked) {
+ /* treat marked tracks as selected */
+ struct list_head *next, *item = e->head.next;
+
+ while (item != &e->head) {
+ next = item->next;
+ t = to_simple_track(item);
+ if (t->marked)
+ editable_remove_track(e, t);
+ item = next;
+ }
+ } else {
+ t = get_selected(e);
+ if (t)
+ editable_remove_track(e, t);
+ }
+}
+
+void editable_sort(struct editable *e)
+{
+ if (e->nr_tracks <= 1)
+ return;
+ sorted_list_rebuild(&e->head, &e->tree_root, e->sort_keys);
+
+ window_changed(e->win);
+ window_goto_top(e->win);
+}
+
+void editable_set_sort_keys(struct editable *e, sort_key_t *keys)
+{
+ free(e->sort_keys);
+ e->sort_keys = keys;
+ editable_sort(e);
+}
+
+void editable_toggle_mark(struct editable *e)
+{
+ struct simple_track *t;
+
+ t = get_selected(e);
+ if (t) {
+ e->nr_marked -= t->marked;
+ t->marked ^= 1;
+ e->nr_marked += t->marked;
+ e->win->changed = 1;
+ window_down(e->win, 1);
+ }
+}
+
+static void move_item(struct editable *e, struct list_head *head, struct list_head *item)
+{
+ struct simple_track *t = to_simple_track(item);
+ struct iter iter;
+
+ editable_track_to_iter(e, t, &iter);
+ window_row_vanishes(e->win, &iter);
+
+ list_del(item);
+ list_add(item, head);
+}
+
+static void reset_tree(struct editable *e)
+{
+ struct simple_track *old, *first_track;
+
+ old = tree_node_to_simple_track(rb_first(&e->tree_root));
+ first_track = to_simple_track(e->head.next);
+ if (old != first_track) {
+ rb_replace_node(&old->tree_node, &first_track->tree_node, &e->tree_root);
+ RB_CLEAR_NODE(&old->tree_node);
+ }
+}
+
+static void move_sel(struct editable *e, struct list_head *after)
+{
+ struct simple_track *t;
+ struct list_head *item, *next;
+ struct iter iter;
+ LIST_HEAD(tmp_head);
+
+ if (e->nr_marked) {
+ /* collect marked */
+ item = e->head.next;
+ while (item != &e->head) {
+ t = to_simple_track(item);
+ next = item->next;
+ if (t->marked)
+ move_item(e, &tmp_head, item);
+ item = next;
+ }
+ } else {
+ /* collect the selected track */
+ t = get_selected(e);
+ move_item(e, &tmp_head, &t->node);
+ }
+
+ /* put them back to the list after @after */
+ item = tmp_head.next;
+ while (item != &tmp_head) {
+ next = item->next;
+ list_add(item, after);
+ item = next;
+ }
+ reset_tree(e);
+
+ /* select top-most of the moved tracks */
+ editable_track_to_iter(e, to_simple_track(after->next), &iter);
+ window_set_sel(e->win, &iter);
+ window_changed(e->win);
+}
+
+static struct list_head *find_insert_after_point(struct editable *e, struct list_head *item)
+{
+ if (e->nr_marked == 0) {
+ /* move the selected track down one row */
+ return item->next;
+ }
+
+ /* move marked after the selected
+ *
+ * if the selected track itself is marked we find the first unmarked
+ * track (or head) before the selected one
+ */
+ while (item != &e->head) {
+ struct simple_track *t = to_simple_track(item);
+
+ if (!t->marked)
+ break;
+ item = item->prev;
+ }
+ return item;
+}
+
+static struct list_head *find_insert_before_point(struct editable *e, struct list_head *item)
+{
+ item = item->prev;
+ if (e->nr_marked == 0) {
+ /* move the selected track up one row */
+ return item->prev;
+ }
+
+ /* move marked before the selected
+ *
+ * if the selected track itself is marked we find the first unmarked
+ * track (or head) before the selected one
+ */
+ while (item != &e->head) {
+ struct simple_track *t = to_simple_track(item);
+
+ if (!t->marked)
+ break;
+ item = item->prev;
+ }
+ return item;
+}
+
+void editable_move_after(struct editable *e)
+{
+ struct simple_track *sel;
+
+ if (e->nr_tracks <= 1 || e->sort_keys[0] != SORT_INVALID)
+ return;
+
+ sel = get_selected(e);
+ if (sel)
+ move_sel(e, find_insert_after_point(e, &sel->node));
+}
+
+void editable_move_before(struct editable *e)
+{
+ struct simple_track *sel;
+
+ if (e->nr_tracks <= 1 || e->sort_keys[0] != SORT_INVALID)
+ return;
+
+ sel = get_selected(e);
+ if (sel)
+ move_sel(e, find_insert_before_point(e, &sel->node));
+}
+
+void editable_clear(struct editable *e)
+{
+ struct list_head *item, *tmp;
+
+ list_for_each_safe(item, tmp, &e->head)
+ editable_remove_track(e, to_simple_track(item));
+}
+
+void editable_remove_matching_tracks(struct editable *e,
+ int (*cb)(void *data, struct track_info *ti), void *data)
+{
+ struct list_head *item, *tmp;
+
+ list_for_each_safe(item, tmp, &e->head) {
+ struct simple_track *t = to_simple_track(item);
+ if (cb(data, t->info))
+ editable_remove_track(e, t);
+ }
+}
+
+void editable_mark(struct editable *e, const char *filter)
+{
+ struct expr *expr = NULL;
+ struct simple_track *t;
+
+ if (filter) {
+ expr = parse_filter(filter);
+ if (expr == NULL)
+ return;
+ }
+
+ list_for_each_entry(t, &e->head, node) {
+ e->nr_marked -= t->marked;
+ t->marked = 0;
+ if (expr == NULL || expr_eval(expr, t->info)) {
+ t->marked = 1;
+ e->nr_marked++;
+ }
+ }
+ e->win->changed = 1;
+}
+
+void editable_unmark(struct editable *e)
+{
+ struct simple_track *t;
+
+ list_for_each_entry(t, &e->head, node) {
+ e->nr_marked -= t->marked;
+ t->marked = 0;
+ }
+ e->win->changed = 1;
+}
+
+void editable_invert_marks(struct editable *e)
+{
+ struct simple_track *t;
+
+ list_for_each_entry(t, &e->head, node) {
+ e->nr_marked -= t->marked;
+ t->marked ^= 1;
+ e->nr_marked += t->marked;
+ }
+ e->win->changed = 1;
+}
+
+int __editable_for_each_sel(struct editable *e, int (*cb)(void *data, struct track_info *ti),
+ void *data, int reverse)
+{
+ int rc = 0;
+
+ if (e->nr_marked) {
+ /* treat marked tracks as selected */
+ rc = simple_list_for_each_marked(&e->head, cb, data, reverse);
+ } else {
+ struct simple_track *t = get_selected(e);
+
+ if (t)
+ rc = cb(data, t->info);
+ }
+ return rc;
+}
+
+int editable_for_each_sel(struct editable *e, int (*cb)(void *data, struct track_info *ti),
+ void *data, int reverse)
+{
+ int rc;
+
+ rc = __editable_for_each_sel(e, cb, data, reverse);
+ if (e->nr_marked == 0)
+ window_down(e->win, 1);
+ return rc;
+}
+
+void editable_update_track(struct editable *e, struct track_info *old, struct track_info *new)
+{
+ struct list_head *item, *tmp;
+ int changed = 0;
+
+ list_for_each_safe(item, tmp, &e->head) {
+ struct simple_track *track = to_simple_track(item);
+ if (track->info == old) {
+ if (new) {
+ track_info_unref(old);
+ track_info_ref(new);
+ track->info = new;
+ } else {
+ editable_remove_track(e, track);
+ }
+ changed = 1;
+ }
+ }
+ if (changed)
+ e->win->changed = changed;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EDITABLE_H
+#define EDITABLE_H
+
+#include "window.h"
+#include "list.h"
+#include "rbtree.h"
+#include "track.h"
+#include "locking.h"
+
+struct editable {
+ struct window *win;
+ struct list_head head;
+ struct rb_root tree_root;
+ unsigned int nr_tracks;
+ unsigned int nr_marked;
+ unsigned int total_time;
+ sort_key_t *sort_keys;
+ char sort_str[128];
+ struct searchable *searchable;
+
+ void (*free_track)(struct list_head *item);
+};
+
+extern pthread_mutex_t editable_mutex;
+
+void editable_init(struct editable *e, void (*free_track)(struct list_head *item));
+void editable_add(struct editable *e, struct simple_track *track);
+void editable_add_before(struct editable *e, struct simple_track *track);
+void editable_remove_track(struct editable *e, struct simple_track *track);
+void editable_remove_sel(struct editable *e);
+void editable_sort(struct editable *e);
+void editable_set_sort_keys(struct editable *e, sort_key_t *keys);
+void editable_toggle_mark(struct editable *e);
+void editable_move_after(struct editable *e);
+void editable_move_before(struct editable *e);
+void editable_clear(struct editable *e);
+void editable_remove_matching_tracks(struct editable *e,
+ int (*cb)(void *data, struct track_info *ti), void *data);
+void editable_mark(struct editable *e, const char *filter);
+void editable_unmark(struct editable *e);
+void editable_invert_marks(struct editable *e);
+int __editable_for_each_sel(struct editable *e, int (*cb)(void *data, struct track_info *ti),
+ void *data, int reverse);
+int editable_for_each_sel(struct editable *e, int (*cb)(void *data, struct track_info *ti),
+ void *data, int reverse);
+void editable_update_track(struct editable *e, struct track_info *old, struct track_info *new);
+
+static inline void editable_track_to_iter(struct editable *e, struct simple_track *track, struct iter *iter)
+{
+ iter->data0 = &e->head;
+ iter->data1 = track;
+ iter->data2 = NULL;
+}
+
+#define editable_lock() cmus_mutex_lock(&editable_mutex)
+#define editable_unlock() cmus_mutex_unlock(&editable_mutex)
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "expr.h"
+#include "glob.h"
+#include "uchar.h"
+#include "track_info.h"
+#include "comment.h"
+#include "xmalloc.h"
+#include "utils.h"
+#include "debug.h"
+#include "list.h"
+#include "ui_curses.h" /* using_utf8, charset */
+#include "convert.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <limits.h>
+
+enum token_type {
+ /* speacial chars */
+ TOK_NOT,
+ TOK_LT,
+ TOK_GT,
+
+#define NR_COMBINATIONS TOK_EQ
+
+ /* speacial chars */
+ TOK_EQ,
+ TOK_AND,
+ TOK_OR,
+ TOK_LPAREN,
+ TOK_RPAREN,
+
+#define NR_SPECIALS TOK_NE
+#define COMB_BASE TOK_NE
+
+ /* same as the first 3 + '=' */
+ TOK_NE,
+ TOK_LE,
+ TOK_GE,
+
+ TOK_KEY,
+ TOK_INT_OR_KEY,
+ TOK_STR
+};
+#define NR_TOKS (TOK_STR + 1)
+
+struct token {
+ struct list_head node;
+ enum token_type type;
+ /* for TOK_KEY, TOK_INT_OR_KEY and TOK_STR */
+ char str[];
+};
+
+/* same order as TOK_* */
+static const char specials[NR_SPECIALS] = "!<>=&|()";
+
+static const int tok_to_op[NR_TOKS] = {
+ -1, OP_LT, OP_GT, OP_EQ, -1, -1, -1, -1, OP_NE, OP_LE, OP_GE, -1, -1, -1
+};
+
+static const char * const op_names[NR_OPS] = { "<", "<=", "=", ">=", ">", "!=" };
+static const char * const expr_names[NR_EXPRS] = {
+ "&", "|", "!", "a string", "an integer", "a boolean"
+};
+
+static char error_buf[64] = { 0, };
+
+
+static void set_error(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vsnprintf(error_buf, sizeof(error_buf), format, ap);
+ va_end(ap);
+}
+
+static struct token *get_str(const char *str, int *idxp)
+{
+ struct token *tok;
+ int s = *idxp + 1;
+ int e = s;
+
+ /* can't remove all backslashes here => don't remove any */
+ while (str[e] != '"') {
+ int c = str[e];
+
+ if (c == 0)
+ goto err;
+ if (c == '\\') {
+ if (str[e + 1] == 0)
+ goto err;
+ e += 2;
+ continue;
+ }
+ e++;
+ }
+
+ tok = xmalloc(sizeof(struct token) + e - s + 1);
+ memcpy(tok->str, str + s, e - s);
+ tok->str[e - s] = 0;
+ tok->type = TOK_STR;
+ *idxp = e + 1;
+ return tok;
+err:
+ set_error("end of expression at middle of string");
+ return NULL;
+}
+
+static struct token *get_int_or_key(const char *str, int *idxp)
+{
+ int s = *idxp;
+ int e = s;
+ int digits_only = 1;
+ struct token *tok;
+
+ if (str[e] == '-')
+ e++;
+ while (str[e]) {
+ int i, c = str[e];
+
+ for (i = 0; i < NR_SPECIALS; i++) {
+ if (c == specials[i])
+ goto out;
+ }
+ if (c < '0' || c > '9') {
+ digits_only = 0;
+ if (!isalpha(c) && c != '_' && c != '-') {
+ set_error("unexpected '%c'", c);
+ return NULL;
+ }
+ }
+ e++;
+ }
+out:
+ tok = xmalloc(sizeof(struct token) + e - s + 1);
+ memcpy(tok->str, str + s, e - s);
+ tok->str[e - s] = 0;
+ tok->type = TOK_KEY;
+ if (digits_only)
+ tok->type = TOK_INT_OR_KEY;
+ *idxp = e;
+ return tok;
+}
+
+static struct token *get_token(const char *str, int *idxp)
+{
+ int idx = *idxp;
+ int c, i;
+
+ c = str[idx];
+ for (i = 0; i < NR_SPECIALS; i++) {
+ struct token *tok;
+
+ if (c != specials[i])
+ continue;
+
+ idx++;
+ tok = xnew(struct token, 1);
+ tok->type = i;
+ if (i < NR_COMBINATIONS && str[idx] == '=') {
+ tok->type = COMB_BASE + i;
+ idx++;
+ }
+ *idxp = idx;
+ return tok;
+ }
+ if (c == '"')
+ return get_str(str, idxp);
+ return get_int_or_key(str, idxp);
+}
+
+static void free_tokens(struct list_head *head)
+{
+ struct list_head *item = head->next;
+
+ while (item != head) {
+ struct list_head *next = item->next;
+ struct token *tok = container_of(item, struct token, node);
+
+ free(tok);
+ item = next;
+ }
+}
+
+static int tokenize(struct list_head *head, const char *str)
+{
+ struct token *tok;
+ int idx = 0;
+
+ while (1) {
+ if (str[idx] == 0)
+ break;
+ tok = get_token(str, &idx);
+ if (tok == NULL) {
+ free_tokens(head);
+ return -1;
+ }
+ list_add_tail(&tok->node, head);
+ }
+ return 0;
+}
+
+static struct expr *expr_new(int type)
+{
+ struct expr *new = xnew(struct expr, 1);
+
+ new->type = type;
+ new->key = NULL;
+ new->parent = NULL;
+ new->left = NULL;
+ new->right = NULL;
+ return new;
+}
+
+static int parse(struct expr **rootp, struct list_head *head, struct list_head **itemp, int level);
+
+static int parse_one(struct expr **exprp, struct list_head *head, struct list_head **itemp)
+{
+ struct list_head *item = *itemp;
+ struct token *tok;
+ enum token_type type;
+ int rc;
+
+ *exprp = NULL;
+ if (item == head) {
+ set_error("expression expected");
+ return -1;
+ }
+
+ tok = container_of(item, struct token, node);
+ type = tok->type;
+ if (type == TOK_NOT) {
+ struct expr *new, *tmp;
+
+ *itemp = item->next;
+ rc = parse_one(&tmp, head, itemp);
+ if (rc)
+ return rc;
+ new = expr_new(EXPR_NOT);
+ new->left = tmp;
+ *exprp = new;
+ return 0;
+ } else if (type == TOK_LPAREN) {
+ *itemp = item->next;
+ *exprp = NULL;
+ return parse(exprp, head, itemp, 1);
+ /* ')' already eaten */
+ } else if (type == TOK_KEY || type == TOK_INT_OR_KEY) {
+ const char *key = tok->str;
+ struct expr *new;
+ int op = -1;
+
+ item = item->next;
+ if (item != head) {
+ tok = container_of(item, struct token, node);
+ op = tok_to_op[tok->type];
+ }
+ if (item == head || op == -1) {
+ /* must be a bool */
+ new = expr_new(EXPR_BOOL);
+ new->key = xstrdup(key);
+ *itemp = item;
+ *exprp = new;
+ return 0;
+ }
+ item = item->next;
+ if (item == head) {
+ set_error("right side of expression expected");
+ return -1;
+ }
+ tok = container_of(item, struct token, node);
+ type = tok->type;
+ *itemp = item->next;
+ if (type == TOK_STR) {
+ if (op != OP_EQ && op != OP_NE) {
+ set_error("invalid string operator '%s'", op_names[op]);
+ return -1;
+ }
+ new = expr_new(EXPR_STR);
+ new->key = xstrdup(key);
+ glob_compile(&new->estr.glob_head, tok->str);
+ new->estr.op = op;
+ *exprp = new;
+ return 0;
+ } else if (type == TOK_INT_OR_KEY) {
+ long int val = 0;
+
+ if (str_to_int(tok->str, &val)) {
+ }
+ new = expr_new(EXPR_INT);
+ new->key = xstrdup(key);
+ new->eint.val = val;
+ new->eint.op = op;
+ *exprp = new;
+ return 0;
+ }
+ if (op == OP_EQ || op == OP_NE) {
+ set_error("integer or string expected");
+ } else {
+ set_error("integer expected");
+ }
+ return -1;
+ }
+ set_error("key expected");
+ return -1;
+}
+
+static void add(struct expr **rootp, struct expr *expr)
+{
+ struct expr *tmp, *root = *rootp;
+
+ if (root == NULL) {
+ *rootp = expr;
+ return;
+ }
+
+ tmp = root;
+ while (tmp->right)
+ tmp = tmp->right;
+ if (tmp->type <= EXPR_OR) {
+ /* tmp is binary, tree is incomplete */
+ tmp->right = expr;
+ expr->parent = tmp;
+ return;
+ }
+
+ /* tmp is unary, tree is complete
+ * expr must be a binary operator */
+ BUG_ON(expr->type > EXPR_OR);
+
+ expr->left = root;
+ root->parent = expr;
+ *rootp = expr;
+}
+
+static int parse(struct expr **rootp, struct list_head *head, struct list_head **itemp, int level)
+{
+ struct list_head *item = *itemp;
+
+ while (1) {
+ struct token *tok;
+ struct expr *expr;
+ int rc, type;
+
+ rc = parse_one(&expr, head, &item);
+ if (rc)
+ return rc;
+ add(rootp, expr);
+ if (item == head) {
+ if (level > 0) {
+ set_error("')' expected");
+ return -1;
+ }
+ *itemp = item;
+ return 0;
+ }
+ tok = container_of(item, struct token, node);
+ if (tok->type == TOK_RPAREN) {
+ if (level == 0) {
+ set_error("unexpected ')'");
+ return -1;
+ }
+ *itemp = item->next;
+ return 0;
+ }
+
+ if (tok->type == TOK_AND) {
+ type = EXPR_AND;
+ } else if (tok->type == TOK_OR) {
+ type = EXPR_OR;
+ } else {
+ set_error("'&' or '|' expected");
+ return -1;
+ }
+ expr = expr_new(type);
+ add(rootp, expr);
+ item = item->next;
+ }
+}
+
+static const struct {
+ char short_key;
+ const char *long_key;
+} map_short2long[] = {
+ { 'A', "albumartist" },
+ { 'D', "discnumber" },
+ { 'T', "tag", },
+ { 'a', "artist" },
+ { 'c', "comment" },
+ { 'd', "duration" },
+ { 'f', "filename" },
+ { 'g', "genre" },
+ { 'l', "album" },
+ { 'n', "tracknumber" },
+ { 's', "stream" },
+ { 't', "title" },
+ { 'y', "date" },
+ { '\0', NULL },
+};
+
+static const struct {
+ const char *key;
+ enum expr_type type;
+} builtin[] = {
+ { "album", EXPR_STR },
+ { "albumartist",EXPR_STR },
+ { "artist", EXPR_STR },
+ { "bitrate", EXPR_INT },
+ { "codec", EXPR_STR },
+ { "codec_profile",EXPR_STR },
+ { "comment", EXPR_STR },
+ { "date", EXPR_INT },
+ { "discnumber", EXPR_INT },
+ { "duration", EXPR_INT },
+ { "filename", EXPR_STR },
+ { "genre", EXPR_STR },
+ { "media", EXPR_STR },
+ { "originaldate",EXPR_INT },
+ { "stream", EXPR_BOOL },
+ { "tag", EXPR_BOOL },
+ { "title", EXPR_STR },
+ { "tracknumber",EXPR_INT },
+ { NULL, -1 },
+};
+
+static const char *lookup_long_key(char c)
+{
+ int i;
+ for (i = 0; map_short2long[i].short_key; i++) {
+ if (map_short2long[i].short_key == c)
+ return map_short2long[i].long_key;
+ }
+ return NULL;
+}
+
+static enum expr_type lookup_key_type(const char *key)
+{
+ int i;
+ for (i = 0; builtin[i].key; i++) {
+ int cmp = strcmp(key, builtin[i].key);
+ if (cmp == 0)
+ return builtin[i].type;
+ if (cmp < 0)
+ break;
+ }
+ return -1;
+}
+
+static unsigned long stack4_new(void)
+{
+ return 0;
+}
+static void stack4_push(unsigned long *s, unsigned long e)
+{
+ *s = (*s << 4) | e;
+}
+static void stack4_pop(unsigned long *s)
+{
+ *s = *s >> 4;
+}
+static unsigned long stack4_top(unsigned long s)
+{
+ return s & 0xf;
+}
+static void stack4_replace_top(unsigned long *s, unsigned long e)
+{
+ *s = (*s & ~0xf) | e;
+}
+
+static char *expand_short_expr(const char *expr_short)
+{
+ /* state space, can contain maximal 15 states */
+ enum state_type {
+ ST_SKIP_SPACE = 1,
+ ST_TOP,
+ ST_EXPECT_KEY,
+ ST_EXPECT_OP,
+ ST_EXPECT_INT,
+ ST_IN_INT,
+ ST_MEM_INT,
+ ST_IN_2ND_INT,
+ ST_EXPECT_STR,
+ ST_IN_QUOTE_STR,
+ ST_IN_STR,
+ };
+
+ size_t len_expr_short = strlen(expr_short);
+ /* worst case blowup of expr_short is 31/5 (e.g. ~n1-2), so take x7:
+ * strlen("~n1-2") == 5
+ * strlen("(tracknumber>=1&tracknumber<=2)") == 31
+ */
+ char *out = xnew(char, len_expr_short * 7);
+ char *num = NULL;
+ size_t i, i_num = 0, k = 0;
+ const char *key = NULL;
+ int level = 0;
+ enum expr_type etype;
+ /* used as state-stack, can contain at least 32/4 = 8 states */
+ unsigned long state_stack = stack4_new();
+ stack4_push(&state_stack, ST_TOP);
+ stack4_push(&state_stack, ST_SKIP_SPACE);
+
+ /* include terminal '\0' to recognize end of string */
+ for (i = 0; i <= len_expr_short; i++) {
+ unsigned char c = expr_short[i];
+ switch (stack4_top(state_stack)) {
+ case ST_SKIP_SPACE:
+ if (c != ' ') {
+ stack4_pop(&state_stack);
+ i--;
+ }
+ break;
+ case ST_TOP:
+ switch (c) {
+ case '~':
+ stack4_push(&state_stack, ST_EXPECT_OP);
+ stack4_push(&state_stack, ST_SKIP_SPACE);
+ stack4_push(&state_stack, ST_EXPECT_KEY);
+ break;
+ case '(':
+ level++;
+ /* Fall through */
+ case '!':
+ case '|':
+ out[k++] = c;
+ stack4_push(&state_stack, ST_SKIP_SPACE);
+ break;
+ case ')':
+ level--;
+ out[k++] = c;
+ stack4_push(&state_stack, ST_EXPECT_OP);
+ stack4_push(&state_stack, ST_SKIP_SPACE);
+ break;
+ case '\0':
+ if (level > 0) {
+ set_error("')' expected");
+ goto error_exit;
+ }
+ out[k++] = c;
+ break;
+ default:
+ set_error("unexpected '%c'", c);
+ goto error_exit;
+ }
+ break;
+ case ST_EXPECT_KEY:
+ stack4_pop(&state_stack);
+ key = lookup_long_key(c);
+ if (!key) {
+ set_error("unkown short key %c", c);
+ goto error_exit;
+ }
+ etype = lookup_key_type(key);
+ if (etype == EXPR_INT) {
+ stack4_push(&state_stack, ST_EXPECT_INT);
+ out[k++] = '(';
+ } else if (etype == EXPR_STR) {
+ stack4_push(&state_stack, ST_EXPECT_STR);
+ } else if (etype != EXPR_BOOL) {
+ BUG("wrong etype: %d\n", etype);
+ }
+ strcpy(out+k, key);
+ k += strlen(key);
+ stack4_push(&state_stack, ST_SKIP_SPACE);
+ break;
+ case ST_EXPECT_OP:
+ if (c == '~' || c == '(' || c == '!')
+ out[k++] = '&';
+ i--;
+ stack4_replace_top(&state_stack, ST_SKIP_SPACE);
+ break;
+ case ST_EXPECT_INT:
+ if (c == '<' || c == '>') {
+ out[k++] = c;
+ stack4_replace_top(&state_stack, ST_IN_INT);
+ } else if (c == '-') {
+ out[k++] = '<';
+ out[k++] = '=';
+ stack4_replace_top(&state_stack, ST_IN_INT);
+ } else if (isdigit(c)) {
+ if (!num)
+ num = xnew(char, len_expr_short);
+ num[i_num++] = c;
+ stack4_replace_top(&state_stack, ST_MEM_INT);
+ } else {
+ set_error("integer expected", expr_short);
+ goto error_exit;
+ }
+ break;
+ case ST_IN_INT:
+ if (isdigit(c)) {
+ out[k++] = c;
+ } else {
+ i -= 1;
+ stack4_pop(&state_stack);
+ out[k++] = ')';
+ }
+ break;
+ case ST_MEM_INT:
+ if (isdigit(c)) {
+ num[i_num++] = c;
+ } else {
+ if (c == '-') {
+ out[k++] = '>';
+ out[k++] = '=';
+ stack4_replace_top(&state_stack, ST_IN_2ND_INT);
+ } else {
+ out[k++] = '=';
+ i--;
+ stack4_pop(&state_stack);
+ }
+ strncpy(out+k, num, i_num);
+ k += i_num;
+ i_num = 0;
+ if (c != '-')
+ out[k++] = ')';
+ }
+ break;
+ case ST_IN_2ND_INT:
+ if (isdigit(c)) {
+ num[i_num++] = c;
+ } else {
+ i--;
+ stack4_pop(&state_stack);
+ if (i_num > 0) {
+ out[k++] = '&';
+ strcpy(out+k, key);
+ k += strlen(key);
+ out[k++] = '<';
+ out[k++] = '=';
+ strncpy(out+k, num, i_num);
+ k += i_num;
+ }
+ out[k++] = ')';
+ }
+ break;
+ case ST_EXPECT_STR:
+ out[k++] = '=';
+ if (c == '"') {
+ stack4_replace_top(&state_stack, ST_IN_QUOTE_STR);
+ out[k++] = c;
+ } else {
+ stack4_replace_top(&state_stack, ST_IN_STR);
+ out[k++] = '"';
+ out[k++] = '*';
+ out[k++] = c;
+ }
+ break;
+ case ST_IN_QUOTE_STR:
+ if (c == '"' && expr_short[i-1] != '\\') {
+ stack4_pop(&state_stack);
+ }
+ out[k++] = c;
+ break;
+ case ST_IN_STR:
+ /* isalnum() doesn't work for multi-byte characters */
+ if (c != '~' && c != '!' && c != '|' &&
+ c != '(' && c != ')' && c != '\0') {
+ out[k++] = c;
+ } else {
+ while (k > 0 && out[k-1] == ' ')
+ k--;
+ out[k++] = '*';
+ out[k++] = '"';
+ i--;
+ stack4_pop(&state_stack);
+ }
+ break;
+ default:
+ BUG("state %ld not covered", stack4_top(state_stack));
+ break;
+ }
+ }
+
+ if (num)
+ free(num);
+
+ d_print("expanded \"%s\" to \"%s\"\n", expr_short, out);
+
+ return out;
+
+error_exit:
+ if (num)
+ free(num);
+ free(out);
+ return NULL;
+}
+
+int expr_is_short(const char *str)
+{
+ int i;
+ for (i = 0; str[i]; i++) {
+ if (str[i] == '~')
+ return 1;
+ if (str[i] != '!' && str[i] != '(' && str[i] != ' ')
+ return 0;
+ }
+ return 0;
+}
+
+struct expr *expr_parse(const char *str)
+{
+ LIST_HEAD(head);
+ struct expr *root = NULL;
+ struct list_head *item;
+ char *long_str = NULL, *u_str = NULL;
+ int i;
+
+ for (i = 0; str[i]; i++) {
+ unsigned char c = str[i];
+ if (c < 0x20) {
+ set_error("filter contains control characters");
+ goto out;
+ }
+ }
+ if (!using_utf8 && utf8_encode(str, charset, &u_str) == 0) {
+ str = u_str;
+ }
+ if (!u_is_valid(str)) {
+ set_error("invalid UTF-8");
+ goto out;
+ }
+
+ if (expr_is_short(str)) {
+ str = long_str = expand_short_expr(str);
+ if (!str)
+ goto out;
+ }
+
+ if (tokenize(&head, str))
+ goto out;
+
+ item = head.next;
+ if (parse(&root, &head, &item, 0))
+ root = NULL;
+ free_tokens(&head);
+
+out:
+ free(u_str);
+ free(long_str);
+ return root;
+}
+
+int expr_check_leaves(struct expr **exprp, const char *(*get_filter)(const char *name))
+{
+ struct expr *expr = *exprp;
+ struct expr *e;
+ const char *filter;
+ int i, rc;
+
+ if (expr->left) {
+ if (expr_check_leaves(&expr->left, get_filter))
+ return -1;
+ if (expr->right)
+ return expr_check_leaves(&expr->right, get_filter);
+ return 0;
+ }
+
+ for (i = 0; builtin[i].key; i++) {
+ int cmp = strcmp(expr->key, builtin[i].key);
+
+ if (cmp > 0)
+ continue;
+ if (cmp < 0)
+ break;
+
+ if (builtin[i].type != expr->type) {
+ /* type mismatch */
+ set_error("%s is %s", builtin[i].key, expr_names[builtin[i].type]);
+ return -1;
+ }
+ return 0;
+ }
+ if (expr->type != EXPR_BOOL) {
+ /* unknown key */
+ set_error("unknown key %s", expr->key);
+ return -1;
+ }
+
+ /* user defined filter */
+ filter = get_filter(expr->key);
+ if (filter == NULL) {
+ set_error("unknown filter or boolean %s", expr->key);
+ return -1;
+ }
+ e = expr_parse(filter);
+ if (e == NULL) {
+ return -1;
+ }
+ rc = expr_check_leaves(&e, get_filter);
+ if (rc) {
+ expr_free(e);
+ return rc;
+ }
+
+ /* replace */
+ e->parent = expr->parent;
+ expr_free(expr);
+
+ /* this sets parents left pointer */
+ *exprp = e;
+ return 0;
+}
+
+unsigned int expr_get_match_type(struct expr *expr)
+{
+ const char *key;
+
+ if (expr->left) {
+ unsigned int left = expr_get_match_type(expr->left);
+ if (expr->type == EXPR_AND || expr->type == EXPR_OR)
+ return left | expr_get_match_type(expr->right);
+ return left;
+ }
+
+ key = expr->key;
+ if (strcmp(key, "artist") == 0 || strcmp(key, "albumartist") == 0)
+ return TI_MATCH_ARTIST;
+ if (strcmp(key, "album") == 0 || strcmp(key, "discnumber") == 0)
+ return TI_MATCH_ALBUM;
+ if (strcmp(key, "title") == 0 || strcmp(key, "tracknumber") == 0)
+ return TI_MATCH_TITLE;
+
+ return 0;
+}
+
+int expr_is_harmless(const struct expr *expr)
+{
+ switch (expr->type) {
+ case EXPR_OR:
+ case EXPR_NOT:
+ return 0;
+ case EXPR_AND:
+ expr = expr->right;
+ default:
+ break;
+ }
+ if (expr->type == EXPR_INT) {
+ switch (expr->eint.op) {
+ case IOP_LT:
+ case IOP_EQ:
+ case IOP_LE:
+ return 0;
+ default:
+ return 1;
+ }
+ }
+ return 1;
+}
+
+int expr_eval(struct expr *expr, struct track_info *ti)
+{
+ enum expr_type type = expr->type;
+ const char *key;
+
+ if (expr->left) {
+ int left = expr_eval(expr->left, ti);
+
+ if (type == EXPR_AND)
+ return left && expr_eval(expr->right, ti);
+ if (type == EXPR_OR)
+ return left || expr_eval(expr->right, ti);
+ /* EXPR_NOT */
+ return !left;
+ }
+
+ key = expr->key;
+ if (type == EXPR_STR) {
+ const char *val;
+ char *uval = NULL;
+ int res;
+
+ if (strcmp(key, "filename") == 0) {
+ val = ti->filename;
+ if (!using_utf8 && utf8_encode(val, charset, &uval) == 0)
+ val = uval;
+ } else if (strcmp(key, "codec") == 0) {
+ val = ti->codec;
+ } else if (strcmp(key, "codec_profile") == 0) {
+ val = ti->codec_profile;
+ } else {
+ val = keyvals_get_val(ti->comments, key);
+ }
+ /* non-existing string tag equals to "" */
+ if (!val)
+ val = "";
+ res = glob_match(&expr->estr.glob_head, val);
+ free(uval);
+ if (expr->estr.op == SOP_EQ)
+ return res;
+ return !res;
+ } else if (type == EXPR_INT) {
+ int val, res;
+
+ if (strcmp(key, "duration") == 0) {
+ val = ti->duration;
+ /* duration of a stream is infinite (well, almost) */
+ if (is_http_url(ti->filename))
+ val = INT_MAX;
+ } else if (strcmp(key, "date") == 0) {
+ val = (ti->date >= 0) ? (ti->date / 10000) : -1;
+ } else if (strcmp(key, "originaldate") == 0) {
+ val = (ti->originaldate >= 0) ? (ti->originaldate / 10000) : -1;
+ } else if (strcmp(key, "bitrate") == 0) {
+ val = (ti->bitrate >= 0) ? (int) (ti->bitrate / 1000. + 0.5) : -1;
+ } else {
+ val = comments_get_int(ti->comments, key);
+ }
+ if (expr->eint.val == -1) {
+ /* -1 is "not set"
+ * doesn't make sense to do 123 < "not set"
+ * but it makes sense to do date=-1 (date is not set)
+ */
+ if (expr->eint.op == IOP_EQ)
+ return val == -1;
+ if (expr->eint.op == IOP_NE)
+ return val != -1;
+ }
+ if (val == -1) {
+ /* tag not set, can't compare */
+ return 0;
+ }
+ res = val - expr->eint.val;
+ switch (expr->eint.op) {
+ case IOP_LT:
+ return res < 0;
+ case IOP_LE:
+ return res <= 0;
+ case IOP_EQ:
+ return res == 0;
+ case IOP_GE:
+ return res >= 0;
+ case IOP_GT:
+ return res > 0;
+ case IOP_NE:
+ return res != 0;
+ }
+ }
+ if (strcmp(key, "stream") == 0)
+ return is_http_url(ti->filename);
+ return track_info_has_tag(ti);
+}
+
+void expr_free(struct expr *expr)
+{
+ if (expr->left) {
+ expr_free(expr->left);
+ if (expr->right)
+ expr_free(expr->right);
+ }
+ free(expr->key);
+ if (expr->type == EXPR_STR)
+ glob_free(&expr->estr.glob_head);
+ free(expr);
+}
+
+const char *expr_error(void)
+{
+ return error_buf;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef EXPR_H
+#define EXPR_H
+
+#include "track_info.h"
+#include "list.h"
+
+enum { OP_LT, OP_LE, OP_EQ, OP_GE, OP_GT, OP_NE };
+#define NR_OPS (OP_NE + 1)
+
+enum expr_type {
+ EXPR_AND,
+ EXPR_OR,
+
+ EXPR_NOT,
+
+ EXPR_STR,
+ EXPR_INT,
+ EXPR_BOOL
+};
+#define NR_EXPRS (EXPR_BOOL + 1)
+
+struct expr {
+ struct expr *left, *right, *parent;
+ enum expr_type type;
+ char *key;
+ union {
+ struct {
+ struct list_head glob_head;
+ enum {
+ SOP_EQ = OP_EQ,
+ SOP_NE = OP_NE
+ } op;
+ } estr;
+ struct {
+ int val;
+ enum {
+ IOP_LT = OP_LT,
+ IOP_LE = OP_LE,
+ IOP_EQ = OP_EQ,
+ IOP_GE = OP_GE,
+ IOP_GT = OP_GT,
+ IOP_NE = OP_NE
+ } op;
+ } eint;
+ };
+};
+
+struct expr *expr_parse(const char *str);
+int expr_check_leaves(struct expr **exprp, const char *(*get_filter)(const char *name));
+int expr_eval(struct expr *expr, struct track_info *ti);
+void expr_free(struct expr *expr);
+const char *expr_error(void);
+int expr_is_short(const char *str);
+
+unsigned int expr_get_match_type(struct expr *expr);
+/* "harmless" expressions will reduce filter results when adding characters at the beginning/end */
+int expr_is_harmless(const struct expr *expr);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2007 Kevin Ko <kevin.s.ko@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "utils.h"
+#include "comment.h"
+#ifdef HAVE_CONFIG
+#include "config/ffmpeg.h"
+#endif
+
+#include <stdio.h>
+#ifdef HAVE_FFMPEG_AVCODEC_H
+#include <ffmpeg/avcodec.h>
+#include <ffmpeg/avformat.h>
+#include <ffmpeg/avio.h>
+#else
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+#ifndef AVUTIL_MATHEMATICS_H
+#include <libavutil/mathematics.h>
+#endif
+#endif
+
+#if (LIBAVFORMAT_VERSION_INT < ((52<<16)+(31<<8)+0))
+# define NUM_FFMPEG_KEYS 8
+#endif
+
+#if (LIBAVCODEC_VERSION_INT < ((52<<16)+(64<<8)+0))
+# define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO
+#endif
+
+#if (LIBAVCODEC_VERSION_INT < ((52<<16)+(94<<8)+1))
+#define AV_SAMPLE_FMT_U8 SAMPLE_FMT_U8
+#define AV_SAMPLE_FMT_S16 SAMPLE_FMT_S16
+#define AV_SAMPLE_FMT_S32 SAMPLE_FMT_S32
+#define AV_SAMPLE_FMT_FLT SAMPLE_FMT_FLT
+#if (LIBAVCODEC_VERSION_INT > ((51<<16)+(64<<8)+0))
+#define AV_SAMPLE_FMT_DBL SAMPLE_FMT_DBL
+#endif
+#endif
+
+#if (LIBAVUTIL_VERSION_INT < ((51<<16)+(5<<8)+0))
+#define AV_DICT_IGNORE_SUFFIX AV_METADATA_IGNORE_SUFFIX
+#define av_dict_get av_metadata_get
+#define AVDictionaryEntry AVMetadataTag
+#endif
+
+struct ffmpeg_input {
+ AVPacket pkt;
+ int curr_pkt_size;
+ uint8_t *curr_pkt_buf;
+
+ unsigned long curr_size;
+ unsigned long curr_duration;
+};
+
+struct ffmpeg_output {
+ uint8_t *buffer;
+ uint8_t *buffer_malloc;
+ uint8_t *buffer_pos; /* current buffer position */
+ int buffer_used_len;
+};
+
+struct ffmpeg_private {
+ AVCodecContext *codec_context;
+ AVFormatContext *input_context;
+ AVCodec *codec;
+ int stream_index;
+
+ struct ffmpeg_input *input;
+ struct ffmpeg_output *output;
+};
+
+static struct ffmpeg_input *ffmpeg_input_create(void)
+{
+ struct ffmpeg_input *input = xnew(struct ffmpeg_input, 1);
+
+ if (av_new_packet(&input->pkt, 0) != 0) {
+ free(input);
+ return NULL;
+ }
+ input->curr_pkt_size = 0;
+ input->curr_pkt_buf = input->pkt.data;
+ return input;
+}
+
+static void ffmpeg_input_free(struct ffmpeg_input *input)
+{
+ av_free_packet(&input->pkt);
+ free(input);
+}
+
+static struct ffmpeg_output *ffmpeg_output_create(void)
+{
+ struct ffmpeg_output *output = xnew(struct ffmpeg_output, 1);
+
+ output->buffer_malloc = xnew(uint8_t, AVCODEC_MAX_AUDIO_FRAME_SIZE + 15);
+ output->buffer = output->buffer_malloc;
+ /* align to 16 bytes so avcodec can SSE/Altivec/etc */
+ while ((intptr_t) output->buffer % 16)
+ output->buffer += 1;
+ output->buffer_pos = output->buffer;
+ output->buffer_used_len = 0;
+ return output;
+}
+
+static void ffmpeg_output_free(struct ffmpeg_output *output)
+{
+ free(output->buffer_malloc);
+ output->buffer_malloc = NULL;
+ output->buffer = NULL;
+ free(output);
+}
+
+static inline void ffmpeg_buffer_flush(struct ffmpeg_output *output)
+{
+ output->buffer_pos = output->buffer;
+ output->buffer_used_len = 0;
+}
+
+static void ffmpeg_init(void)
+{
+ static int inited = 0;
+
+ if (inited != 0)
+ return;
+ inited = 1;
+
+ av_log_set_level(AV_LOG_QUIET);
+
+#if (LIBAVFORMAT_VERSION_INT <= ((50<<16) + (4<<8) + 0))
+ avcodec_init();
+ register_avcodec(&wmav1_decoder);
+ register_avcodec(&wmav2_decoder);
+
+ /* libavformat versions <= 50.4.0 have asf_init(). From SVN revision
+ * 5697->5707 of asf.c, this function was removed, preferring the use of
+ * explicit calls. Note that version 50.5.0 coincides with SVN revision
+ * 5729, so there is a window of incompatibility for revisions 5707 and 5720
+ * of asf.c.
+ */
+ asf_init();
+
+ /* Uncomment this for shorten (.shn) support.
+ register_avcodec(&shorten_decoder);
+ raw_init();
+ */
+
+ register_protocol(&file_protocol);
+#else
+ /* We could register decoders explicitly to save memory, but we have to
+ * be careful about compatibility. */
+ av_register_all();
+#endif
+}
+
+static int ffmpeg_open(struct input_plugin_data *ip_data)
+{
+ struct ffmpeg_private *priv;
+ int err = 0;
+ int i;
+ int stream_index = -1;
+ int64_t channel_layout = 0;
+ AVCodec *codec;
+ AVCodecContext *cc = NULL;
+ AVFormatContext *ic = NULL;
+
+ ffmpeg_init();
+
+#if (LIBAVFORMAT_VERSION_INT <= ((53<<16)+(2<<8)+0))
+ err = av_open_input_file(&ic, ip_data->filename, NULL, 0, NULL);
+#else
+ err = avformat_open_input(&ic, ip_data->filename, NULL, NULL);
+#endif
+ if (err < 0) {
+ d_print("av_open failed: %d\n", err);
+ return -IP_ERROR_FILE_FORMAT;
+ }
+
+ do {
+#if (LIBAVFORMAT_VERSION_INT <= ((53<<16)+(5<<8)+0))
+ err = av_find_stream_info(ic);
+#else
+ err = avformat_find_stream_info(ic, NULL);
+#endif
+ if (err < 0) {
+ d_print("unable to find stream info: %d\n", err);
+ err = -IP_ERROR_FILE_FORMAT;
+ break;
+ }
+
+ for (i = 0; i < ic->nb_streams; i++) {
+ cc = ic->streams[i]->codec;
+ if (cc->codec_type == AVMEDIA_TYPE_AUDIO) {
+ stream_index = i;
+ break;
+ }
+ }
+
+ if (stream_index == -1) {
+ d_print("could not find audio stream\n");
+ err = -IP_ERROR_FILE_FORMAT;
+ break;
+ }
+
+ codec = avcodec_find_decoder(cc->codec_id);
+ if (!codec) {
+ d_print("codec not found: %d, %s\n", cc->codec_id, cc->codec_name);
+ err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
+ break;
+ }
+
+ if (codec->capabilities & CODEC_CAP_TRUNCATED)
+ cc->flags |= CODEC_FLAG_TRUNCATED;
+
+#if (LIBAVCODEC_VERSION_INT < ((53<<16)+(8<<8)+0))
+ if (avcodec_open(cc, codec) < 0) {
+#else
+ if (avcodec_open2(cc, codec, NULL) < 0) {
+#endif
+ d_print("could not open codec: %d, %s\n", cc->codec_id, cc->codec_name);
+ err = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
+ break;
+ }
+
+#if (LIBAVCODEC_VERSION_INT > ((51<<16)+(64<<8)+0))
+ if (cc->sample_fmt == AV_SAMPLE_FMT_FLT || cc->sample_fmt == AV_SAMPLE_FMT_DBL) {
+#else
+ if (cc->sample_fmt == AV_SAMPLE_FMT_FLT) {
+#endif
+ err = -IP_ERROR_SAMPLE_FORMAT;
+ break;
+ }
+ /* We assume below that no more errors follow. */
+ } while (0);
+
+ if (err < 0) {
+ /* Clean up. cc is never opened at this point. (See above assumption.) */
+ av_close_input_file(ic);
+ return err;
+ }
+
+ priv = xnew(struct ffmpeg_private, 1);
+ priv->codec_context = cc;
+ priv->input_context = ic;
+ priv->codec = codec;
+ priv->stream_index = stream_index;
+ priv->input = ffmpeg_input_create();
+ if (priv->input == NULL) {
+ avcodec_close(cc);
+ av_close_input_file(ic);
+ free(priv);
+ return -IP_ERROR_INTERNAL;
+ }
+ priv->output = ffmpeg_output_create();
+
+ ip_data->private = priv;
+ ip_data->sf = sf_rate(cc->sample_rate) | sf_channels(cc->channels);
+ switch (cc->sample_fmt) {
+ case AV_SAMPLE_FMT_U8:
+ ip_data->sf |= sf_bits(8) | sf_signed(0);
+ break;
+ case AV_SAMPLE_FMT_S32:
+ ip_data->sf |= sf_bits(32) | sf_signed(1);
+ break;
+ /* AV_SAMPLE_FMT_S16 */
+ default:
+ ip_data->sf |= sf_bits(16) | sf_signed(1);
+ break;
+ }
+#ifdef WORDS_BIGENDIAN
+ ip_data->sf |= sf_bigendian(1);
+#endif
+#if (LIBAVCODEC_VERSION_INT > ((52<<16)+(1<<8)+0))
+ channel_layout = cc->channel_layout;
+#endif
+ channel_map_init_waveex(cc->channels, channel_layout, ip_data->channel_map);
+ return 0;
+}
+
+static int ffmpeg_close(struct input_plugin_data *ip_data)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+
+ avcodec_close(priv->codec_context);
+ av_close_input_file(priv->input_context);
+ ffmpeg_input_free(priv->input);
+ ffmpeg_output_free(priv->output);
+ free(priv);
+ ip_data->private = NULL;
+ return 0;
+}
+
+/*
+ * This returns the number of bytes added to the buffer.
+ * It returns < 0 on error. 0 on EOF.
+ */
+static int ffmpeg_fill_buffer(AVFormatContext *ic, AVCodecContext *cc, struct ffmpeg_input *input,
+ struct ffmpeg_output *output)
+{
+ while (1) {
+ /* frame_size specifies the size of output->buffer for
+ * avcodec_decode_audio2. */
+ int frame_size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
+ int len;
+
+ if (input->curr_pkt_size <= 0) {
+ av_free_packet(&input->pkt);
+ if (av_read_frame(ic, &input->pkt) < 0) {
+ /* Force EOF once we can read no longer. */
+ return 0;
+ }
+ input->curr_pkt_size = input->pkt.size;
+ input->curr_pkt_buf = input->pkt.data;
+ input->curr_size += input->pkt.size;
+ input->curr_duration += input->pkt.duration;
+ continue;
+ }
+
+ /* The change to avcodec_decode_audio2 occurred between
+ * 51.28.0 and 51.29.0 */
+#if (LIBAVCODEC_VERSION_INT <= ((51<<16) + (28<<8) + 0))
+ len = avcodec_decode_audio(cc, (int16_t *)output->buffer, &frame_size,
+ input->curr_pkt_buf, input->curr_pkt_size);
+ /* The change to avcodec_decode_audio3 occurred between
+ * 52.25.0 and 52.26.0 */
+#elif (LIBAVCODEC_VERSION_INT <= ((52<<16) + (25<<8) + 0))
+ len = avcodec_decode_audio2(cc, (int16_t *) output->buffer, &frame_size,
+ input->curr_pkt_buf, input->curr_pkt_size);
+#else
+ {
+ AVPacket avpkt;
+ av_init_packet(&avpkt);
+ avpkt.data = input->curr_pkt_buf;
+ avpkt.size = input->curr_pkt_size;
+ len = avcodec_decode_audio3(cc, (int16_t *) output->buffer, &frame_size, &avpkt);
+ av_free_packet(&avpkt);
+ }
+#endif
+ if (len < 0) {
+ /* this is often reached when seeking, not sure why */
+ input->curr_pkt_size = 0;
+ continue;
+ }
+ input->curr_pkt_size -= len;
+ input->curr_pkt_buf += len;
+ if (frame_size > 0) {
+ output->buffer_pos = output->buffer;
+ output->buffer_used_len = frame_size;
+ return frame_size;
+ }
+ }
+ /* This should never get here. */
+ return -IP_ERROR_INTERNAL;
+}
+
+static int ffmpeg_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ struct ffmpeg_output *output = priv->output;
+ int rc;
+ int out_size;
+
+ if (output->buffer_used_len == 0) {
+ rc = ffmpeg_fill_buffer(priv->input_context, priv->codec_context, priv->input, priv->output);
+ if (rc <= 0) {
+ return rc;
+ }
+ }
+ out_size = min(output->buffer_used_len, count);
+ memcpy(buffer, output->buffer_pos, out_size);
+ output->buffer_used_len -= out_size;
+ output->buffer_pos += out_size;
+ return out_size;
+}
+
+static int ffmpeg_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ AVStream *st = priv->input_context->streams[priv->stream_index];
+ int ret;
+
+ /* There is a bug that was fixed in ffmpeg revision 5099 that affects seeking.
+ * Apparently, the stream's timebase was not used consistently in asf.c.
+ * Prior to 5099, ASF seeking assumed seconds as inputs. There is a
+ * window of incompatibility, since avformat's version was not updated at
+ * the same time. Instead, the transition to 50.3.0 occurred at
+ * revision 5028. */
+#if (LIBAVFORMAT_VERSION_INT < ((50<<16)+(3<<8)+0))
+ int64_t pts = (int64_t) offset;
+#else
+ int64_t pts = av_rescale_q(offset * AV_TIME_BASE, AV_TIME_BASE_Q, st->time_base);
+#endif
+
+ ret = av_seek_frame(priv->input_context, priv->stream_index, pts, 0);
+
+ if (ret < 0) {
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+ } else {
+ ffmpeg_buffer_flush(priv->output);
+ return 0;
+ }
+}
+
+#if (LIBAVFORMAT_VERSION_INT < ((52<<16)+(31<<8)+0))
+/* Return new i. */
+static int set_comment(struct keyval *comment, int i, const char *key, const char *val)
+{
+ if (val[0] == 0) {
+ return i;
+ }
+ comment[i].key = xstrdup(key);
+ comment[i].val = xstrdup(val);
+ return i + 1;
+}
+#endif
+
+static int ffmpeg_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ AVFormatContext *ic = priv->input_context;
+
+#if (LIBAVFORMAT_VERSION_INT < ((52<<16)+(31<<8)+0))
+ char buff[16];
+ int i = 0;
+
+ *comments = keyvals_new(NUM_FFMPEG_KEYS);
+
+ i = set_comment(*comments, i, "artist", ic->author);
+ i = set_comment(*comments, i, "album", ic->album);
+ i = set_comment(*comments, i, "title", ic->title);
+ i = set_comment(*comments, i, "genre", ic->genre);
+
+ if (ic->year != 0) {
+ snprintf(buff, sizeof(buff), "%d", ic->year);
+ i = set_comment(*comments, i, "date", buff);
+ }
+
+ if (ic->track != 0) {
+ snprintf(buff, sizeof(buff), "%d", ic->track);
+ i = set_comment(*comments, i, "tracknumber", buff);
+ }
+#else
+ GROWING_KEYVALS(c);
+ AVDictionaryEntry *tag = NULL;
+
+ while ((tag = av_dict_get(ic->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
+ if (tag && tag->value[0])
+ comments_add_const(&c, tag->key, tag->value);
+ }
+
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+#endif
+
+ return 0;
+}
+
+static int ffmpeg_duration(struct input_plugin_data *ip_data)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ return priv->input_context->duration / AV_TIME_BASE;
+}
+
+static long ffmpeg_bitrate(struct input_plugin_data *ip_data)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ long bitrate = priv->input_context->bit_rate;
+ return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+static long ffmpeg_current_bitrate(struct input_plugin_data *ip_data)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ AVStream *st = priv->input_context->streams[priv->stream_index];
+ long bitrate = -1;
+#if (LIBAVFORMAT_VERSION_INT > ((51<<16)+(43<<8)+0))
+ /* ape codec returns silly numbers */
+ if (priv->codec->id == CODEC_ID_APE)
+ return -1;
+#endif
+ if (priv->input->curr_duration > 0) {
+ double seconds = priv->input->curr_duration * av_q2d(st->time_base);
+ bitrate = (8 * priv->input->curr_size) / seconds;
+ priv->input->curr_size = 0;
+ priv->input->curr_duration = 0;
+ }
+ return bitrate;
+}
+
+static char *ffmpeg_codec(struct input_plugin_data *ip_data)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ return xstrdup(priv->codec->name);
+}
+
+#if (LIBAVCODEC_VERSION_INT < ((52<<16)+(104<<8)+0))
+static const char *codec_profile_to_str(int profile)
+{
+#if (LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0))
+ switch (profile) {
+ case FF_PROFILE_AAC_MAIN: return "Main";
+ case FF_PROFILE_AAC_LOW: return "LC";
+ case FF_PROFILE_AAC_SSR: return "SSR";
+ case FF_PROFILE_AAC_LTP: return "LTP";
+ }
+#endif
+ return NULL;
+}
+#endif
+
+static char *ffmpeg_codec_profile(struct input_plugin_data *ip_data)
+{
+ struct ffmpeg_private *priv = ip_data->private;
+ const char *profile;
+
+#if (LIBAVCODEC_VERSION_INT < ((52<<16)+(104<<8)+0))
+ profile = codec_profile_to_str(priv->codec_context->profile);
+#else
+ profile = av_get_profile_name(priv->codec, priv->codec_context->profile);
+#endif
+
+ return profile ? xstrdup(profile) : NULL;
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = ffmpeg_open,
+ .close = ffmpeg_close,
+ .read = ffmpeg_read,
+ .seek = ffmpeg_seek,
+ .read_comments = ffmpeg_read_comments,
+ .duration = ffmpeg_duration,
+ .bitrate = ffmpeg_bitrate,
+ .bitrate_current = ffmpeg_current_bitrate,
+ .codec = ffmpeg_codec,
+ .codec_profile = ffmpeg_codec_profile
+};
+
+const int ip_priority = 30;
+const char *const ip_extensions[] = {
+ "ac3", "aif", "aifc", "aiff", "ape", "au", "mka", "shn", "tta", "wma",
+ /* also supported by other plugins */
+ "aac", "fla", "flac", "m4a", "m4b", "mp+", "mp2", "mp3", "mp4", "mpc",
+ "mpp", "ogg", "wav", "wv",
+#ifdef USE_FALLBACK_IP
+ "*",
+#endif
+ NULL
+};
+const char *const ip_mime_types[] = { NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "file.h"
+#include "xmalloc.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+ssize_t read_all(int fd, void *buf, size_t count)
+{
+ char *buffer = buf;
+ ssize_t pos = 0;
+
+ do {
+ ssize_t rc;
+
+ rc = read(fd, buffer + pos, count - pos);
+ if (rc == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return -1;
+ }
+ if (rc == 0) {
+ /* eof */
+ break;
+ }
+ pos += rc;
+ } while (count - pos > 0);
+ return pos;
+}
+
+ssize_t write_all(int fd, const void *buf, size_t count)
+{
+ const char *buffer = buf;
+ int count_save = count;
+
+ do {
+ int rc;
+
+ rc = write(fd, buffer, count);
+ if (rc == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return -1;
+ }
+ buffer += rc;
+ count -= rc;
+ } while (count > 0);
+ return count_save;
+}
+
+char *mmap_file(const char *filename, int *size)
+{
+ struct stat st;
+ char *buf;
+ int fd;
+
+ fd = open(filename, O_RDONLY);
+ if (fd == -1)
+ goto err;
+
+ if (fstat(fd, &st) == -1)
+ goto close_err;
+
+ /* can't mmap empty files */
+ buf = NULL;
+ if (st.st_size) {
+ buf = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (buf == MAP_FAILED)
+ goto close_err;
+ }
+
+ close(fd);
+ *size = st.st_size;
+ return buf;
+
+close_err:
+ close(fd);
+err:
+ *size = -1;
+ return NULL;
+}
+
+void buffer_for_each_line(const char *buf, int size,
+ int (*cb)(void *data, const char *line),
+ void *data)
+{
+ char *line = NULL;
+ int line_size = 0, pos = 0;
+
+ while (pos < size) {
+ int end, len;
+
+ end = pos;
+ while (end < size && buf[end] != '\n')
+ end++;
+
+ len = end - pos;
+ if (end > pos && buf[end - 1] == '\r')
+ len--;
+
+ if (len >= line_size) {
+ line_size = len + 1;
+ line = xrenew(char, line, line_size);
+ }
+ memcpy(line, buf + pos, len);
+ line[len] = 0;
+ pos = end + 1;
+
+ if (cb(data, line))
+ break;
+ }
+ free(line);
+}
+
+void buffer_for_each_line_reverse(const char *buf, int size,
+ int (*cb)(void *data, const char *line),
+ void *data)
+{
+ char *line = NULL;
+ int line_size = 0, end = size - 1;
+
+ while (end >= 0) {
+ int pos, len;
+
+ if (end > 1 && buf[end] == '\n' && buf[end - 1] == '\r')
+ end--;
+
+ pos = end;
+ while (pos > 0 && buf[pos - 1] != '\n')
+ pos--;
+
+ len = end - pos;
+ if (len >= line_size) {
+ line_size = len + 1;
+ line = xrenew(char, line, line_size);
+ }
+ memcpy(line, buf + pos, len);
+ line[len] = 0;
+ end = pos - 1;
+
+ if (cb(data, line))
+ break;
+ }
+ free(line);
+}
+
+int file_for_each_line(const char *filename,
+ int (*cb)(void *data, const char *line),
+ void *data)
+{
+ char *buf;
+ int size;
+
+ buf = mmap_file(filename, &size);
+ if (size == -1)
+ return -1;
+
+ if (buf) {
+ buffer_for_each_line(buf, size, cb, data);
+ munmap(buf, size);
+ }
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _FILE_H
+#define _FILE_H
+
+#include <stddef.h> /* size_t */
+#include <sys/types.h> /* ssize_t */
+
+ssize_t read_all(int fd, void *buf, size_t count);
+ssize_t write_all(int fd, const void *buf, size_t count);
+
+/* @filename file to mmap for reading
+ * @size returned size of the file or -1 if failed
+ *
+ * returns buffer or NULL if empty file or failed
+ */
+char *mmap_file(const char *filename, int *size);
+
+void buffer_for_each_line(const char *buf, int size,
+ int (*cb)(void *data, const char *line),
+ void *data);
+void buffer_for_each_line_reverse(const char *buf, int size,
+ int (*cb)(void *data, const char *line),
+ void *data);
+int file_for_each_line(const char *filename,
+ int (*cb)(void *data, const char *line),
+ void *data);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "filters.h"
+#include "cmdline.h"
+#include "expr.h"
+#include "window.h"
+#include "search.h"
+#include "uchar.h"
+#include "lib.h"
+#include "misc.h"
+#include "file.h"
+#include "ui_curses.h"
+#include "xmalloc.h"
+
+#include <stdio.h>
+#include <ctype.h>
+
+struct window *filters_win;
+struct searchable *filters_searchable;
+LIST_HEAD(filters_head);
+
+static const char *recursive_filter;
+
+static inline void filter_entry_to_iter(struct filter_entry *e, struct iter *iter)
+{
+ iter->data0 = &filters_head;
+ iter->data1 = e;
+ iter->data2 = NULL;
+}
+
+static GENERIC_ITER_PREV(filters_get_prev, struct filter_entry, node)
+static GENERIC_ITER_NEXT(filters_get_next, struct filter_entry, node)
+
+static int filters_search_get_current(void *data, struct iter *iter)
+{
+ return window_get_sel(filters_win, iter);
+}
+
+static int filters_search_matches(void *data, struct iter *iter, const char *text)
+{
+ char **words = get_words(text);
+ int matched = 0;
+
+ if (words[0] != NULL) {
+ struct filter_entry *e;
+ int i;
+
+ e = iter_to_filter_entry(iter);
+ for (i = 0; ; i++) {
+ if (words[i] == NULL) {
+ window_set_sel(filters_win, iter);
+ matched = 1;
+ break;
+ }
+ if (u_strcasestr(e->name, words[i]) == NULL)
+ break;
+ }
+ }
+ free_str_array(words);
+ return matched;
+}
+
+static const struct searchable_ops filters_search_ops = {
+ .get_prev = filters_get_prev,
+ .get_next = filters_get_next,
+ .get_current = filters_search_get_current,
+ .matches = filters_search_matches
+};
+
+static void free_filter(struct filter_entry *e)
+{
+ free(e->name);
+ free(e->filter);
+ free(e);
+}
+
+static struct filter_entry *find_filter(const char *name)
+{
+ struct filter_entry *e;
+
+ list_for_each_entry(e, &filters_head, node) {
+ if (strcmp(e->name, name) == 0)
+ return e;
+ }
+ return NULL;
+}
+
+static const char *get_filter(const char *name)
+{
+ struct filter_entry *e = find_filter(name);
+
+ if (e) {
+ if (e->visited) {
+ recursive_filter = e->name;
+ return NULL;
+ }
+ e->visited = 1;
+ return e->filter;
+ }
+ return NULL;
+}
+
+static void edit_sel_filter(void)
+{
+ struct iter sel;
+ struct filter_entry *e;
+ char buf[512];
+
+ if (!window_get_sel(filters_win, &sel))
+ return;
+
+ e = iter_to_filter_entry(&sel);
+ snprintf(buf, sizeof(buf), "fset %s=%s", e->name, e->filter);
+ cmdline_set_text(buf);
+ enter_command_mode();
+}
+
+void filters_activate(void)
+{
+ struct filter_entry *f;
+ struct expr *e, *expr = NULL;
+ int unchanged = 1;
+
+ /* if no pending selection is to apply, edit currently select filter */
+ list_for_each_entry(f, &filters_head, node) {
+ if (f->act_stat != f->sel_stat)
+ unchanged = 0;
+ }
+ if (unchanged)
+ edit_sel_filter();
+
+ /* mark visited and AND together all selected filters
+ * mark any other filters unvisited */
+ list_for_each_entry(f, &filters_head, node) {
+ f->visited = 0;
+ if (f->sel_stat == FS_IGNORE)
+ continue;
+
+ f->visited = 1;
+ e = expr_parse(f->filter);
+ if (e == NULL) {
+ error_msg("error parsing filter %s: %s", f->name, expr_error());
+ if (expr)
+ expr_free(expr);
+ return;
+ }
+
+ if (f->sel_stat == FS_NO) {
+ /* add ! */
+ struct expr *not = xnew(struct expr, 1);
+
+ not->type = EXPR_NOT;
+ not->key = NULL;
+ not->left = e;
+ not->right = NULL;
+ e = not;
+ }
+ if (expr == NULL) {
+ expr = e;
+ } else {
+ struct expr *and = xnew(struct expr, 1);
+
+ and->type = EXPR_AND;
+ and->key = NULL;
+ and->left = expr;
+ and->right = e;
+ expr->parent = and;
+ e->parent = and;
+ expr = and;
+ }
+ }
+
+ recursive_filter = NULL;
+ if (expr && expr_check_leaves(&expr, get_filter)) {
+ if (recursive_filter) {
+ error_msg("recursion detected in filter %s", recursive_filter);
+ } else {
+ error_msg("error parsing filter: %s", expr_error());
+ }
+ expr_free(expr);
+ return;
+ }
+
+ /* update active flag */
+ list_for_each_entry(f, &filters_head, node) {
+ f->act_stat = f->sel_stat;
+ }
+ lib_set_filter(expr);
+ filters_win->changed = 1;
+}
+
+static int for_each_name(const char *str, int (*cb)(const char *name, int sel_stat))
+{
+ char buf[64];
+ int s, e, len;
+
+ e = 0;
+ do {
+ int sel_stat = FS_YES;
+
+ s = e;
+ while (str[s] == ' ')
+ s++;
+ if (str[s] == '!') {
+ sel_stat = FS_NO;
+ s++;
+ }
+ e = s;
+ while (str[e] && str[e] != ' ')
+ e++;
+
+ len = e - s;
+ if (len == 0)
+ return 0;
+ if (len >= sizeof(buf)) {
+ error_msg("filter name too long");
+ return -1;
+ }
+
+ memcpy(buf, str + s, len);
+ buf[len] = 0;
+
+ if (cb(buf, sel_stat))
+ return -1;
+ } while (1);
+}
+
+static int ensure_filter_name(const char *name, int sel_stat)
+{
+ if (find_filter(name) == NULL) {
+ error_msg("no such filter %s", name);
+ return -1;
+ }
+ return 0;
+}
+
+static int select_filter(const char *name, int sel_stat)
+{
+ struct filter_entry *e = find_filter(name);
+
+ e->sel_stat = sel_stat;
+ return 0;
+}
+
+void filters_activate_names(const char *str)
+{
+ struct filter_entry *f;
+
+ /* first validate all filter names */
+ if (str && for_each_name(str, ensure_filter_name))
+ return;
+
+ /* mark all filters unselected */
+ list_for_each_entry(f, &filters_head, node)
+ f->sel_stat = FS_IGNORE;
+
+ /* select the filters */
+ if (str)
+ for_each_name(str, select_filter);
+
+ /* activate selected */
+ filters_activate();
+}
+
+void filters_toggle_filter(void)
+{
+ struct iter iter;
+
+ if (window_get_sel(filters_win, &iter)) {
+ struct filter_entry *e;
+
+ e = iter_to_filter_entry(&iter);
+ e->sel_stat = (e->sel_stat + 1) % 3;
+ filters_win->changed = 1;
+ }
+}
+
+void filters_delete_filter(void)
+{
+ struct iter iter;
+
+ if (window_get_sel(filters_win, &iter)) {
+ struct filter_entry *e;
+
+ e = iter_to_filter_entry(&iter);
+ if (yes_no_query("Delete filter '%s'? [y/N]", e->name)) {
+ window_row_vanishes(filters_win, &iter);
+ list_del(&e->node);
+ free_filter(e);
+ }
+ }
+}
+
+static int validate_filter_name(const char *name)
+{
+ int i;
+
+ for (i = 0; name[i]; i++) {
+ if (isalnum((unsigned char)name[i]))
+ continue;
+ if (name[i] == '_' || name[i] == '-')
+ continue;
+ return 0;
+ }
+ return i != 0;
+}
+
+static void do_filters_set_filter(const char *keyval)
+{
+ const char *eq = strchr(keyval, '=');
+ char *key, *val;
+ struct expr *expr;
+ struct filter_entry *new;
+ struct list_head *item;
+
+ if (eq == NULL) {
+ if (ui_initialized)
+ error_msg("invalid argument ('key=value' expected)");
+ return;
+ }
+ key = xstrndup(keyval, eq - keyval);
+ val = xstrdup(eq + 1);
+ if (!validate_filter_name(key)) {
+ if (ui_initialized)
+ error_msg("invalid filter name (can only contain 'a-zA-Z0-9_-' characters)");
+ free(key);
+ free(val);
+ return;
+ }
+ expr = expr_parse(val);
+ if (expr == NULL) {
+ if (ui_initialized)
+ error_msg("error parsing filter %s: %s", val, expr_error());
+ free(key);
+ free(val);
+ return;
+ }
+ expr_free(expr);
+
+ new = xnew(struct filter_entry, 1);
+ new->name = key;
+ new->filter = val;
+ new->act_stat = FS_IGNORE;
+ new->sel_stat = FS_IGNORE;
+
+ /* add or replace filter */
+ list_for_each(item, &filters_head) {
+ struct filter_entry *e = container_of(item, struct filter_entry, node);
+ int res = strcmp(key, e->name);
+
+ if (res < 0)
+ break;
+ if (res == 0) {
+ /* replace */
+ struct iter iter;
+
+ new->sel_stat = e->sel_stat;
+ if (ui_initialized) {
+ filter_entry_to_iter(e, &iter);
+ window_row_vanishes(filters_win, &iter);
+ }
+ item = item->next;
+ list_del(&e->node);
+ free_filter(e);
+ break;
+ }
+ }
+ /* add before item */
+ list_add_tail(&new->node, item);
+ if (ui_initialized)
+ window_changed(filters_win);
+}
+
+void filters_init(void)
+{
+ struct iter iter;
+
+ filters_win = window_new(filters_get_prev, filters_get_next);
+ window_set_contents(filters_win, &filters_head);
+ window_changed(filters_win);
+
+ iter.data0 = &filters_head;
+ iter.data1 = NULL;
+ iter.data2 = NULL;
+ filters_searchable = searchable_new(NULL, &iter, &filters_search_ops);
+}
+
+void filters_exit(void)
+{
+ searchable_free(filters_searchable);
+ window_free(filters_win);
+}
+
+void filters_set_filter(const char *keyval)
+{
+ do_filters_set_filter(keyval);
+}
+
+struct expr *parse_filter(const char *val)
+{
+ struct expr *e = NULL;
+ struct filter_entry *f;
+
+ if (val) {
+ e = expr_parse(val);
+ if (e == NULL) {
+ error_msg("error parsing filter %s: %s", val, expr_error());
+ return NULL;
+ }
+ }
+
+ /* mark all unvisited so that we can check recursion */
+ list_for_each_entry(f, &filters_head, node)
+ f->visited = 0;
+
+ recursive_filter = NULL;
+ if (e && expr_check_leaves(&e, get_filter)) {
+ if (recursive_filter) {
+ error_msg("recursion detected in filter %s", recursive_filter);
+ } else {
+ error_msg("error parsing filter: %s", expr_error());
+ }
+ expr_free(e);
+ return NULL;
+ }
+ return e;
+}
+
+void filters_set_anonymous(const char *val)
+{
+ struct filter_entry *f;
+ struct expr *e = NULL;
+
+ if (val) {
+ e = parse_filter(val);
+ if (e == NULL)
+ return;
+ }
+
+ /* deactive all filters */
+ list_for_each_entry(f, &filters_head, node)
+ f->act_stat = FS_IGNORE;
+
+ lib_set_filter(e);
+
+ filters_win->changed = 1;
+}
+
+void filters_set_live(const char *val)
+{
+ lib_set_live_filter(val);
+ update_filterline();
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef FILTERS_H
+#define FILTERS_H
+
+#include "list.h"
+#include "window.h"
+#include "search.h"
+
+/* factivate foo !bar
+ *
+ * foo: FS_YES
+ * bar: FS_NO
+ * baz: FS_IGNORE
+ */
+enum {
+ /* [ ] filter not selected */
+ FS_IGNORE,
+ /* [*] filter selected */
+ FS_YES,
+ /* [!] filter selected and inverted */
+ FS_NO,
+};
+
+struct filter_entry {
+ struct list_head node;
+
+ char *name;
+ char *filter;
+ unsigned visited : 1;
+
+ /* selected and activated status (FS_* enum) */
+ unsigned sel_stat : 2;
+ unsigned act_stat : 2;
+};
+
+static inline struct filter_entry *iter_to_filter_entry(struct iter *iter)
+{
+ return iter->data1;
+}
+
+extern struct window *filters_win;
+extern struct searchable *filters_searchable;
+extern struct list_head filters_head;
+
+void filters_init(void);
+void filters_exit(void);
+
+/* parse filter and expand sub filters */
+struct expr *parse_filter(const char *val);
+
+/* add filter to filter list (replaces old filter with same name)
+ *
+ * @keyval "name=value" where value is filter
+ */
+void filters_set_filter(const char *keyval);
+
+/* set throwaway filter (not saved to the filter list)
+ *
+ * @val filter or NULL to disable filtering
+ */
+void filters_set_anonymous(const char *val);
+
+/* set live filter (not saved to the filter list)
+ *
+ * @val filter or NULL to disable filtering
+ */
+void filters_set_live(const char *val);
+
+void filters_activate_names(const char *str);
+
+void filters_activate(void);
+void filters_toggle_filter(void);
+void filters_delete_filter(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "comment.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "utils.h"
+
+#include <FLAC/export.h>
+
+#ifdef FLAC_API_VERSION_CURRENT
+/* flac 1.1.3 */
+#define FLAC_NEW_API 1
+#endif
+
+#ifdef FLAC_NEW_API
+#include <FLAC/stream_decoder.h>
+#else
+#include <FLAC/seekable_stream_decoder.h>
+#endif
+
+#include <FLAC/metadata.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifndef UINT64_MAX
+#define UINT64_MAX ((uint64_t)-1)
+#endif
+
+/* Reduce typing. Namespaces are nice but FLAC API is fscking ridiculous. */
+
+/* functions, types, enums */
+#ifdef FLAC_NEW_API
+#define F(s) FLAC__stream_decoder_ ## s
+#define T(s) FLAC__StreamDecoder ## s
+#define Dec FLAC__StreamDecoder
+#define E(s) FLAC__STREAM_DECODER_ ## s
+#else
+#define F(s) FLAC__seekable_stream_decoder_ ## s
+#define T(s) FLAC__SeekableStreamDecoder ## s
+#define Dec FLAC__SeekableStreamDecoder
+#define E(s) FLAC__SEEKABLE_STREAM_DECODER_ ## s
+#endif
+
+struct flac_private {
+ /* file/stream position and length */
+ uint64_t pos;
+ uint64_t len;
+
+ Dec *dec;
+
+ /* PCM data */
+ char *buf;
+ unsigned int buf_size;
+ unsigned int buf_wpos;
+ unsigned int buf_rpos;
+
+ struct keyval *comments;
+ double duration;
+ long bitrate;
+
+ unsigned int ignore_next_write : 1;
+};
+
+#ifdef FLAC_NEW_API
+static T(ReadStatus) read_cb(const Dec *dec, unsigned char *buf, size_t *size, void *data)
+#else
+static T(ReadStatus) read_cb(const Dec *dec, unsigned char *buf, unsigned *size, void *data)
+#endif
+{
+ struct input_plugin_data *ip_data = data;
+ struct flac_private *priv = ip_data->private;
+ int rc;
+
+ if (priv->pos == priv->len) {
+ *size = 0;
+#ifdef FLAC_NEW_API
+ return E(READ_STATUS_END_OF_STREAM);
+#else
+ return E(READ_STATUS_OK);
+#endif
+ }
+ if (*size == 0)
+#ifdef FLAC_NEW_API
+ return E(READ_STATUS_CONTINUE);
+#else
+ return E(READ_STATUS_OK);
+#endif
+
+ rc = read(ip_data->fd, buf, *size);
+ if (rc == -1) {
+ *size = 0;
+ if (errno == EINTR || errno == EAGAIN) {
+ /* FIXME: not sure how the flac decoder handles this */
+ d_print("interrupted\n");
+#ifdef FLAC_NEW_API
+ return E(READ_STATUS_CONTINUE);
+#else
+ return E(READ_STATUS_OK);
+#endif
+ }
+#ifdef FLAC_NEW_API
+ return E(READ_STATUS_ABORT);
+#else
+ return E(READ_STATUS_ERROR);
+#endif
+ }
+
+ priv->pos += rc;
+ *size = rc;
+ if (rc == 0) {
+ /* should not happen */
+#ifdef FLAC_NEW_API
+ return E(READ_STATUS_END_OF_STREAM);
+#else
+ return E(READ_STATUS_OK);
+#endif
+ }
+#ifdef FLAC_NEW_API
+ return E(READ_STATUS_CONTINUE);
+#else
+ return E(READ_STATUS_OK);
+#endif
+}
+
+static T(SeekStatus) seek_cb(const Dec *dec, uint64_t offset, void *data)
+{
+ struct input_plugin_data *ip_data = data;
+ struct flac_private *priv = ip_data->private;
+ off_t off;
+
+ if (priv->len == UINT64_MAX)
+ return E(SEEK_STATUS_ERROR);
+ off = lseek(ip_data->fd, offset, SEEK_SET);
+ if (off == -1) {
+ return E(SEEK_STATUS_ERROR);
+ }
+ priv->pos = off;
+ return E(SEEK_STATUS_OK);
+}
+
+static T(TellStatus) tell_cb(const Dec *dec, uint64_t *offset, void *data)
+{
+ struct input_plugin_data *ip_data = data;
+ struct flac_private *priv = ip_data->private;
+
+ d_print("\n");
+ *offset = priv->pos;
+ return E(TELL_STATUS_OK);
+}
+
+static T(LengthStatus) length_cb(const Dec *dec, uint64_t *len, void *data)
+{
+ struct input_plugin_data *ip_data = data;
+ struct flac_private *priv = ip_data->private;
+
+ d_print("\n");
+ if (ip_data->remote) {
+ return E(LENGTH_STATUS_ERROR);
+ }
+ *len = priv->len;
+ return E(LENGTH_STATUS_OK);
+}
+
+static int eof_cb(const Dec *dec, void *data)
+{
+ struct input_plugin_data *ip_data = data;
+ struct flac_private *priv = ip_data->private;
+
+ return priv->pos == priv->len;;
+}
+
+#if defined(WORDS_BIGENDIAN)
+
+#define LE16(x) swap_uint16(x)
+#define LE32(x) swap_uint32(x)
+
+#else
+
+#define LE16(x) (x)
+#define LE32(x) (x)
+
+#endif
+
+static FLAC__StreamDecoderWriteStatus write_cb(const Dec *dec, const FLAC__Frame *frame,
+ const int32_t * const *buf, void *data)
+{
+ struct input_plugin_data *ip_data = data;
+ struct flac_private *priv = ip_data->private;
+ int frames, bytes, size, channels, bits, depth;
+ int ch, nch, i, j = 0;
+
+ if (ip_data->sf == 0) {
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+ }
+
+ if (priv->ignore_next_write) {
+ priv->ignore_next_write = 0;
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+ }
+
+ frames = frame->header.blocksize;
+ channels = sf_get_channels(ip_data->sf);
+ bits = sf_get_bits(ip_data->sf);
+ bytes = frames * bits / 8 * channels;
+ size = priv->buf_size;
+
+ if (size - priv->buf_wpos < bytes) {
+ if (size < bytes)
+ size = bytes;
+ size *= 2;
+ priv->buf = xrenew(char, priv->buf, size);
+ priv->buf_size = size;
+ }
+
+ depth = frame->header.bits_per_sample;
+ if (!depth)
+ depth = bits;
+ nch = frame->header.channels;
+ if (depth == 8) {
+ char *b = priv->buf + priv->buf_wpos;
+
+ for (i = 0; i < frames; i++) {
+ for (ch = 0; ch < channels; ch++)
+ b[j++] = buf[ch % nch][i];
+ }
+ } else if (depth == 16) {
+ int16_t *b = (int16_t *)(priv->buf + priv->buf_wpos);
+
+ for (i = 0; i < frames; i++) {
+ for (ch = 0; ch < channels; ch++)
+ b[j++] = LE16(buf[ch % nch][i]);
+ }
+ } else if (depth == 32) {
+ int32_t *b = (int32_t *)(priv->buf + priv->buf_wpos);
+
+ for (i = 0; i < frames; i++) {
+ for (ch = 0; ch < channels; ch++)
+ b[j++] = LE32(buf[ch % nch][i]);
+ }
+ } else if (depth == 12) { /* -> 16 */
+ int16_t *b = (int16_t *)(priv->buf + priv->buf_wpos);
+
+ for (i = 0; i < frames; i++) {
+ for (ch = 0; ch < channels; ch++)
+ b[j++] = LE16(buf[ch % nch][i] << 4);
+ }
+ } else if (depth == 20) { /* -> 32 */
+ int32_t *b = (int32_t *)(priv->buf + priv->buf_wpos);
+
+ for (i = 0; i < frames; i++) {
+ for (ch = 0; ch < channels; ch++)
+ b[j++] = LE32(buf[ch % nch][i] << 12);
+ }
+ } else if (depth == 24) { /* -> 32 */
+ int32_t *b = (int32_t *)(priv->buf + priv->buf_wpos);
+
+ for (i = 0; i < frames; i++) {
+ for (ch = 0; ch < channels; ch++)
+ b[j++] = LE32(buf[ch % nch][i] << 8);
+ }
+ } else {
+ d_print("bits per sample changed to %d\n", depth);
+ }
+
+ priv->buf_wpos += bytes;
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+/* You should make a copy of metadata with FLAC__metadata_object_clone() if you will
+ * need it elsewhere. Since metadata blocks can potentially be large, by
+ * default the decoder only calls the metadata callback for the STREAMINFO
+ * block; you can instruct the decoder to pass or filter other blocks with
+ * FLAC__stream_decoder_set_metadata_*() calls.
+ */
+static void metadata_cb(const Dec *dec, const FLAC__StreamMetadata *metadata, void *data)
+{
+ struct input_plugin_data *ip_data = data;
+ struct flac_private *priv = ip_data->private;
+
+ switch (metadata->type) {
+ case FLAC__METADATA_TYPE_STREAMINFO:
+ {
+ const FLAC__StreamMetadata_StreamInfo *si = &metadata->data.stream_info;
+ int bits = 0;
+
+ switch (si->bits_per_sample) {
+ case 8:
+ case 16:
+ case 32:
+ bits = si->bits_per_sample;
+ break;
+ case 12:
+ bits = 16;
+ break;
+ case 20:
+ case 24:
+ bits = 32;
+ break;
+ }
+
+ ip_data->sf = sf_rate(si->sample_rate) |
+ sf_bits(bits) |
+ sf_signed(1) |
+ sf_channels(si->channels);
+ if (!ip_data->remote && si->total_samples) {
+ priv->duration = (double) si->total_samples / si->sample_rate;
+ if (priv->duration >= 1 && priv->len >= 1)
+ priv->bitrate = priv->len * 8 / priv->duration;
+ }
+ }
+ break;
+ case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+ d_print("VORBISCOMMENT\n");
+ if (priv->comments) {
+ d_print("Ignoring\n");
+ } else {
+ GROWING_KEYVALS(c);
+ int i, nr;
+
+ nr = metadata->data.vorbis_comment.num_comments;
+ for (i = 0; i < nr; i++) {
+ const char *str = (const char *)metadata->data.vorbis_comment.comments[i].entry;
+ char *key, *val;
+
+ val = strchr(str, '=');
+ if (!val)
+ continue;
+ key = xstrndup(str, val - str);
+ val = xstrdup(val + 1);
+ comments_add(&c, key, val);
+ free(key);
+ }
+ keyvals_terminate(&c);
+ priv->comments = c.keyvals;
+ }
+ break;
+ default:
+ d_print("something else\n");
+ break;
+ }
+}
+
+static void error_cb(const Dec *dec, FLAC__StreamDecoderErrorStatus status, void *data)
+{
+ d_print("FLAC error: %s\n", FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+static void free_priv(struct input_plugin_data *ip_data)
+{
+ struct flac_private *priv = ip_data->private;
+ int save = errno;
+
+ F(finish)(priv->dec);
+ F(delete)(priv->dec);
+ if (priv->comments)
+ keyvals_free(priv->comments);
+ free(priv->buf);
+ free(priv);
+ ip_data->private = NULL;
+ errno = save;
+}
+
+/* http://flac.sourceforge.net/format.html#frame_header */
+static void channel_map_init_flac(int channels, channel_position_t *map)
+{
+ unsigned int mask = 0;
+ if (channels == 4)
+ mask = 0x33; // 0b110011, without center and lfe
+ else if (channels == 5)
+ mask = 0x37; // 0b110111, without lfe
+ channel_map_init_waveex(channels, mask, map);
+}
+
+static int flac_open(struct input_plugin_data *ip_data)
+{
+ struct flac_private *priv;
+
+ Dec *dec = F(new)();
+
+ const struct flac_private priv_init = {
+ .dec = dec,
+ .duration = -1,
+ .bitrate = -1
+ };
+
+ if (!dec)
+ return -IP_ERROR_INTERNAL;
+
+ priv = xnew(struct flac_private, 1);
+ *priv = priv_init;
+ if (ip_data->remote) {
+ priv->len = UINT64_MAX;
+ } else {
+ off_t off = lseek(ip_data->fd, 0, SEEK_END);
+
+ if (off == -1 || lseek(ip_data->fd, 0, SEEK_SET) == -1) {
+ int save = errno;
+
+ F(delete)(dec);
+ free(priv);
+ errno = save;
+ return -IP_ERROR_ERRNO;
+ }
+ priv->len = off;
+ }
+ ip_data->private = priv;
+
+#ifndef FLAC_NEW_API
+ F(set_read_callback)(dec, read_cb);
+ F(set_seek_callback)(dec, seek_cb);
+ F(set_tell_callback)(dec, tell_cb);
+ F(set_length_callback)(dec, length_cb);
+ F(set_eof_callback)(dec, eof_cb);
+ F(set_write_callback)(dec, write_cb);
+ F(set_metadata_callback)(dec, metadata_cb);
+ F(set_error_callback)(dec, error_cb);
+ F(set_client_data)(dec, ip_data);
+#endif
+
+#ifdef FLAC_NEW_API
+ FLAC__stream_decoder_set_metadata_respond_all(dec);
+ if (FLAC__stream_decoder_init_stream(dec, read_cb, seek_cb, tell_cb,
+ length_cb, eof_cb, write_cb, metadata_cb,
+ error_cb, ip_data) != E(INIT_STATUS_OK)) {
+#else
+ /* FLAC__METADATA_TYPE_STREAMINFO already accepted */
+ F(set_metadata_respond)(dec, FLAC__METADATA_TYPE_VORBIS_COMMENT);
+
+ if (F(init)(dec) != E(OK)) {
+#endif
+ int save = errno;
+
+ d_print("init failed\n");
+ F(delete)(priv->dec);
+ free(priv);
+ ip_data->private = NULL;
+ errno = save;
+ return -IP_ERROR_ERRNO;
+ }
+
+ ip_data->sf = 0;
+ while (priv->buf_wpos == 0 && priv->pos < priv->len) {
+ if (!F(process_single)(priv->dec)) {
+ free_priv(ip_data);
+ return -IP_ERROR_ERRNO;
+ }
+ }
+
+ if (!ip_data->sf) {
+ free_priv(ip_data);
+ return -IP_ERROR_FILE_FORMAT;
+ }
+ if (!sf_get_bits(ip_data->sf)) {
+ free_priv(ip_data);
+ return -IP_ERROR_SAMPLE_FORMAT;
+ }
+
+ channel_map_init_flac(sf_get_channels(ip_data->sf), ip_data->channel_map);
+ d_print("sr: %d, ch: %d, bits: %d\n",
+ sf_get_rate(ip_data->sf),
+ sf_get_channels(ip_data->sf),
+ sf_get_bits(ip_data->sf));
+ return 0;
+}
+
+static int flac_close(struct input_plugin_data *ip_data)
+{
+ free_priv(ip_data);
+ return 0;
+}
+
+static int flac_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct flac_private *priv = ip_data->private;
+ int avail;
+
+ while (1) {
+ avail = priv->buf_wpos - priv->buf_rpos;
+ BUG_ON(avail < 0);
+ if (avail > 0)
+ break;
+ if (priv->pos == priv->len)
+ return 0;
+ if (!F(process_single)(priv->dec)) {
+ d_print("process_single failed\n");
+ return -1;
+ }
+ }
+ if (count > avail)
+ count = avail;
+ memcpy(buffer, priv->buf + priv->buf_rpos, count);
+ priv->buf_rpos += count;
+ BUG_ON(priv->buf_rpos > priv->buf_wpos);
+ if (priv->buf_rpos == priv->buf_wpos) {
+ priv->buf_rpos = 0;
+ priv->buf_wpos = 0;
+ }
+ return count;
+}
+
+/* Flush the input and seek to an absolute sample. Decoding will resume at the
+ * given sample. Note that because of this, the next write callback may contain
+ * a partial block.
+ */
+static int flac_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct flac_private *priv = ip_data->private;
+ uint64_t sample;
+
+ sample = (uint64_t)(offset * (double)sf_get_rate(ip_data->sf) + 0.5);
+ if (!F(seek_absolute)(priv->dec, sample)) {
+ return -IP_ERROR_ERRNO;
+ }
+ priv->ignore_next_write = 1;
+ priv->buf_rpos = 0;
+ priv->buf_wpos = 0;
+ return 0;
+}
+
+static int flac_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
+{
+ struct flac_private *priv = ip_data->private;
+
+ if (priv->comments) {
+ *comments = keyvals_dup(priv->comments);
+ } else {
+ *comments = keyvals_new(0);
+ }
+ return 0;
+}
+
+static int flac_duration(struct input_plugin_data *ip_data)
+{
+ struct flac_private *priv = ip_data->private;
+
+ return priv->duration;
+}
+
+static long flac_bitrate(struct input_plugin_data *ip_data)
+{
+ struct flac_private *priv = ip_data->private;
+ return priv->bitrate;
+}
+
+static char *flac_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("flac");
+}
+
+static char *flac_codec_profile(struct input_plugin_data *ip_data)
+{
+ /* maybe identify compression-level over min/max blocksize/framesize */
+ return NULL;
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = flac_open,
+ .close = flac_close,
+ .read = flac_read,
+ .seek = flac_seek,
+ .read_comments = flac_read_comments,
+ .duration = flac_duration,
+ .bitrate = flac_bitrate,
+ .bitrate_current = flac_bitrate,
+ .codec = flac_codec,
+ .codec_profile = flac_codec_profile
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = { "flac", "fla", NULL };
+const char * const ip_mime_types[] = { NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "format_print.h"
+#include "uchar.h"
+#include "xmalloc.h"
+#include "debug.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+static int width;
+static int align_left;
+static int pad;
+
+static int numlen(int num)
+{
+ int digits;
+
+ if (num < 0)
+ return 1; /* '?' */
+ digits = 0;
+ do {
+ num /= 10;
+ digits++;
+ } while (num);
+ return digits;
+}
+
+static int stack_print(char *buf, char *stack, int stack_len)
+{
+ int i = 0;
+
+ if (width) {
+ if (align_left) {
+ while (i < width && stack_len)
+ buf[i++] = stack[--stack_len];
+ while (i < width)
+ buf[i++] = pad;
+ } else {
+ int pad_len;
+
+ if (stack_len > width)
+ stack_len = width;
+ pad_len = width - stack_len;
+ while (i < pad_len)
+ buf[i++] = pad;
+ while (i < width)
+ buf[i++] = stack[--stack_len];
+ }
+ } else {
+ while (stack_len)
+ buf[i++] = stack[--stack_len];
+ }
+ return i;
+}
+
+static int print_num(char *buf, int num)
+{
+ char stack[20];
+ int i, p;
+
+ if (num < 0) {
+ if (width == 0)
+ width = 1;
+ for (i = 0; i < width; i++)
+ buf[i] = '?';
+ return width;
+ }
+ p = 0;
+ do {
+ stack[p++] = num % 10 + '0';
+ num /= 10;
+ } while (num);
+
+ return stack_print(buf, stack, p);
+}
+
+#define DBL_MAX_LEN (20)
+
+static int format_double(char *buf, int buflen, double num)
+{
+ int len = snprintf(buf, buflen, "%f", num);
+ /* skip trailing zeros */
+ while (len > 0 && buf[len-1] == '0')
+ len--;
+ return len;
+}
+
+static int double_len(double num)
+{
+ char buf[DBL_MAX_LEN];
+ return format_double(buf, DBL_MAX_LEN, num);
+}
+
+static int print_double(char *buf, double num)
+{
+ char stack[DBL_MAX_LEN], b[DBL_MAX_LEN];
+ int i, p = 0;
+
+ i = format_double(b, DBL_MAX_LEN, num) - 1;
+ while (i >= 0) {
+ stack[p++] = b[i];
+ i--;
+ }
+ return stack_print(buf, stack, p);
+}
+
+/* print '{,-}{h:,}mm:ss' */
+static int print_time(char *buf, int t)
+{
+ int h, m, s;
+ char stack[32];
+ int neg = 0;
+ int p = 0;
+
+ if (t < 0) {
+ neg = 1;
+ t *= -1;
+ }
+ h = t / 3600;
+ t = t % 3600;
+ m = t / 60;
+ s = t % 60;
+
+ /* put all chars to stack in reverse order ;) */
+ stack[p++] = s % 10 + '0';
+ stack[p++] = s / 10 + '0';
+ stack[p++] = ':';
+ stack[p++] = m % 10 + '0';
+ stack[p++] = m / 10 + '0';
+ if (h) {
+ stack[p++] = ':';
+ do {
+ stack[p++] = h % 10 + '0';
+ h /= 10;
+ } while (h);
+ }
+ if (neg)
+ stack[p++] = '-';
+
+ return stack_print(buf, stack, p);
+}
+
+static void print_str(char *buf, int *idx, const char *str)
+{
+ int d = *idx;
+
+ if (width) {
+ int ws_len;
+ int i = 0;
+
+ if (align_left) {
+ i = width;
+ d += u_copy_chars(buf + d, str, &i);
+
+ ws_len = width - i;
+ memset(buf + d, ' ', ws_len);
+ d += ws_len;
+ } else {
+ int s = 0;
+
+ ws_len = width - u_str_width(str);
+
+ if (ws_len > 0) {
+ memset(buf + d, ' ', ws_len);
+ d += ws_len;
+ i += ws_len;
+ }
+
+ if (ws_len < 0) {
+ int w, c = -ws_len;
+ uchar u;
+
+ while (c > 0) {
+ u = u_get_char(str, &s);
+ w = u_char_width(u);
+ c -= w;
+ }
+ if (c < 0) {
+ /* gaah, skipped too much */
+ if (u_char_width(u) == 2) {
+ /* double-byte */
+ buf[d++] = ' ';
+ } else {
+ /* <xx> */
+ if (c == -3)
+ buf[d++] = hex_tab[(u >> 4) & 0xf];
+ if (c <= -2)
+ buf[d++] = hex_tab[u & 0xf];
+ buf[d++] = '>';
+ }
+ }
+ }
+
+ if (width - i > 0) {
+ int w = width - i;
+
+ d += u_copy_chars(buf + d, str + s, &w);
+ }
+ }
+ } else {
+ int s = 0;
+ uchar u;
+
+ while (1) {
+ u = u_get_char(str, &s);
+ if (u == 0)
+ break;
+ u_set_char(buf, &d, u);
+ }
+ }
+ *idx = d;
+}
+
+static inline int strnequal(const char *a, const char *b, size_t b_len)
+{
+ return a && (strlen(a) == b_len) && (memcmp(a, b, b_len) == 0);
+}
+
+static void print(char *str, int str_width, const char *format, const struct format_option *fopts)
+{
+ /* format and str indices */
+ int s = 0, d = 0;
+
+ while (format[s]) {
+ const struct format_option *fo;
+ int long_len = 0;
+ const char *long_begin = NULL;
+ uchar u;
+
+ u = u_get_char(format, &s);
+ if (u != '%') {
+ u_set_char(str, &d, u);
+ continue;
+ }
+ u = u_get_char(format, &s);
+ if (u == '%') {
+ u_set_char(str, &d, u);
+ continue;
+ }
+
+ if (u == '=') {
+ break;
+ }
+
+ align_left = 0;
+ if (u == '-') {
+ align_left = 1;
+ u = u_get_char(format, &s);
+ }
+ pad = ' ';
+ if (u == '0') {
+ pad = '0';
+ u = u_get_char(format, &s);
+ }
+ width = 0;
+ while (isdigit(u)) {
+ width *= 10;
+ width += u - '0';
+ u = u_get_char(format, &s);
+ }
+ if (u == '%') {
+ width = (width * str_width) / 100.0 + 0.5;
+ u = u_get_char(format, &s);
+ }
+ if (u == '{') {
+ long_begin = format + s;
+ while (1) {
+ u = u_get_char(format, &s);
+ BUG_ON(u == 0);
+ if (u == '}')
+ break;
+ long_len++;
+ }
+ }
+ for (fo = fopts; fo->type; fo++) {
+ if (long_len ? strnequal(fo->str, long_begin, long_len)
+ : (fo->ch == u)) {
+ int type = fo->type;
+
+ if (fo->empty) {
+ memset(str + d, ' ', width);
+ d += width;
+ } else if (type == FO_STR) {
+ print_str(str, &d, fo->fo_str);
+ } else if (type == FO_INT) {
+ d += print_num(str + d, fo->fo_int);
+ } else if (type == FO_TIME) {
+ d += print_time(str + d, fo->fo_time);
+ } else if (type == FO_DOUBLE) {
+ d += print_double(str + d, fo->fo_double);
+ }
+ break;
+ }
+ }
+ }
+ str[d] = 0;
+}
+
+static char *l_str = NULL;
+static char *r_str = NULL;
+/* sizes in bytes. not counting the terminating 0! */
+static int l_str_size = -1;
+static int r_str_size = -1;
+
+int format_print(char *str, int str_width, const char *format, const struct format_option *fopts)
+{
+ /* lengths of left and right aligned texts */
+ int llen = 0;
+ int rlen = 0;
+ int *len = &llen;
+ int lsize, rsize;
+ int eq_pos = -1;
+ int s = 0;
+
+ while (format[s]) {
+ const struct format_option *fo;
+ int nlen, long_len = 0;
+ const char *long_begin = NULL;
+ uchar u;
+
+ u = u_get_char(format, &s);
+ if (u != '%') {
+ (*len) += u_char_width(u);
+ continue;
+ }
+ u = u_get_char(format, &s);
+ if (u == '%') {
+ (*len)++;
+ continue;
+ }
+ if (u == '=') {
+ /* right aligned text starts */
+ len = &rlen;
+ eq_pos = s - 1;
+ continue;
+ }
+ if (u == '-')
+ u = u_get_char(format, &s);
+ nlen = 0;
+ while (isdigit(u)) {
+ /* minimum length of this field */
+ nlen *= 10;
+ nlen += u - '0';
+ u = u_get_char(format, &s);
+ }
+ if (u == '%') {
+ nlen = (nlen * str_width) / 100.0 + 0.5;
+ u = u_get_char(format, &s);
+ }
+ if (u == '{') {
+ long_begin = format + s;
+ while (1) {
+ u = u_get_char(format, &s);
+ BUG_ON(u == 0);
+ if (u == '}')
+ break;
+ long_len++;
+ }
+ }
+ for (fo = fopts; ; fo++) {
+ BUG_ON(fo->type == 0);
+ if (long_len ? strnequal(fo->str, long_begin, long_len)
+ : (fo->ch == u)) {
+ int type = fo->type;
+ int l = 0;
+
+ if (fo->empty) {
+ /* nothing */
+ } else if (type == FO_STR) {
+ l = u_str_width(fo->fo_str);
+ } else if (type == FO_INT) {
+ l = numlen(fo->fo_int);
+ } else if (type == FO_TIME) {
+ int t = fo->fo_time;
+
+ if (t < 0) {
+ t *= -1;
+ l++;
+ }
+ if (t >= 3600) {
+ l += numlen(t / 3600) + 6;
+ } else {
+ l += 5;
+ }
+ } else if (type == FO_DOUBLE) {
+ l = double_len(fo->fo_double);
+ }
+ if (nlen) {
+ *len += nlen;
+ } else {
+ *len += l;
+ }
+ break;
+ }
+ }
+ }
+
+ /* max utf-8 char len is 4 */
+ lsize = llen * 4;
+ rsize = rlen * 4;
+
+ if (l_str_size < lsize) {
+ free(l_str);
+ l_str_size = lsize;
+ l_str = xnew(char, l_str_size + 1);
+ l_str[l_str_size] = 0;
+ }
+ if (r_str_size < rsize) {
+ free(r_str);
+ r_str_size = rsize;
+ r_str = xnew(char, r_str_size + 1);
+ r_str[r_str_size] = 0;
+ }
+ l_str[0] = 0;
+ r_str[0] = 0;
+
+ if (lsize > 0) {
+ print(l_str, str_width, format, fopts);
+#if DEBUG > 1
+ {
+ int ul = u_str_width(l_str);
+ if (ul != llen)
+ d_print("L %d != %d: size=%d '%s'\n", ul, llen, lsize, l_str);
+ }
+#endif
+ }
+ if (rsize > 0) {
+ print(r_str, str_width, format + eq_pos + 1, fopts);
+#if DEBUG > 1
+ {
+ int ul = u_str_width(r_str);
+ if (ul != rlen)
+ d_print("R %d != %d: size=%d '%s'\n", ul, rlen, rsize, r_str);
+ }
+#endif
+ }
+
+ /* NOTE: any invalid UTF-8 bytes have already been converted to <xx>
+ * (ASCII) where x is hex digit
+ */
+
+ if (llen + rlen <= str_width) {
+ /* both fit */
+ int ws_len = str_width - llen - rlen;
+ int pos = 0;
+
+ /* I would use strcpy if it returned anything useful */
+ while (l_str[pos]) {
+ str[pos] = l_str[pos];
+ pos++;
+ }
+ memset(str + pos, ' ', ws_len);
+ strcpy(str + pos + ws_len, r_str);
+ } else {
+ int l_space = str_width - rlen;
+ int pos = 0;
+ int idx = 0;
+
+ if (l_space > 0)
+ pos = u_copy_chars(str, l_str, &l_space);
+ if (l_space < 0) {
+ int w = -l_space;
+
+ idx = u_skip_chars(r_str, &w);
+ if (w != -l_space)
+ str[pos++] = ' ';
+ }
+ strcpy(str + pos, r_str + idx);
+ }
+ return 0;
+}
+
+int format_valid(const char *format, const struct format_option *fopts)
+{
+ int s = 0;
+
+ while (format[s]) {
+ uchar u;
+
+ u = u_get_char(format, &s);
+ if (u == '%') {
+ int pad_zero = 0, long_len = 0;
+ const struct format_option *fo;
+ const char *long_begin = NULL;
+
+ u = u_get_char(format, &s);
+ if (u == '%' || u == '=')
+ continue;
+ if (u == '-')
+ u = u_get_char(format, &s);
+ if (u == '0') {
+ pad_zero = 1;
+ u = u_get_char(format, &s);
+ }
+ while (isdigit(u))
+ u = u_get_char(format, &s);
+ if (u == '%')
+ u = u_get_char(format, &s);
+ if (u == '{') {
+ long_begin = format + s;
+ while (1) {
+ u = u_get_char(format, &s);
+ if (!u)
+ return 0;
+ if (u == '}')
+ break;
+ long_len++;
+ }
+ }
+ for (fo = fopts; fo->type; fo++) {
+ if (long_len ? strnequal(fo->str, long_begin, long_len)
+ : (fo->ch == u)) {
+ if (pad_zero && !fo->pad_zero)
+ return 0;
+ break;
+ }
+ }
+ if (! fo->type)
+ return 0;
+ }
+ }
+ return 1;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _FORMAT_PRINT_H
+#define _FORMAT_PRINT_H
+
+struct format_option {
+ union {
+ /* NULL is treated like "" */
+ const char *fo_str;
+ int fo_int;
+ /* [h:]mm:ss. can be negative */
+ int fo_time;
+ double fo_double;
+ };
+ /* set to 1 if you want to disable printing */
+ unsigned int empty : 1;
+ /* set to 1 if zero padding is allowed */
+ unsigned int pad_zero : 1;
+ enum { FO_STR = 1, FO_INT, FO_TIME, FO_DOUBLE } type;
+ char ch;
+ const char *str;
+};
+
+/* gcc < 4.6 and icc < 12.0 can't properly initialize anonymous unions */
+#if (defined(__GNUC__) && defined(__GNUC_MINOR__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) || \
+ (defined(__INTEL_COMPILER) && __INTEL_COMPILER < 1200)
+#define UNION_INIT(f, v) { .f = v }
+#else
+#define UNION_INIT(f, v) .f = v
+#endif
+
+#define DEF_FO_STR(c, s, z) { UNION_INIT(fo_str, NULL), .type = FO_STR, .pad_zero = z, .ch = c, .str = s }
+#define DEF_FO_INT(c, s, z) { UNION_INIT(fo_int, 0), .type = FO_INT, .pad_zero = z, .ch = c, .str = s }
+#define DEF_FO_TIME(c, s, z) { UNION_INIT(fo_time, 0), .type = FO_TIME, .pad_zero = z, .ch = c, .str = s }
+#define DEF_FO_DOUBLE(c, s, z) { UNION_INIT(fo_double, 0.), .type = FO_DOUBLE, .pad_zero = z, .ch = c, .str = s }
+#define DEF_FO_END { .type = 0 }
+
+int format_print(char *str, int width, const char *format, const struct format_option *fopts);
+int format_valid(const char *format, const struct format_option *fopts);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2008 Timo Hirvonen
+ *
+ * This code is largely based on strbuf in the GIT version control system.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gbuf.h"
+#include "xmalloc.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+
+char gbuf_empty_buffer[1];
+
+static inline void gbuf_init(struct gbuf *buf)
+{
+ buf->buffer = gbuf_empty_buffer;
+ buf->alloc = 0;
+ buf->len = 0;
+}
+
+void gbuf_grow(struct gbuf *buf, size_t more)
+{
+ size_t align = 16 - 1;
+ size_t alloc = (buf->len + more + 1 + align) & ~align;
+
+ if (alloc > buf->alloc) {
+ if (!buf->alloc)
+ buf->buffer = NULL;
+ buf->alloc = alloc;
+ buf->buffer = xrealloc(buf->buffer, buf->alloc);
+ // gbuf is not NUL terminated if this was first alloc
+ buf->buffer[buf->len] = 0;
+ }
+}
+
+void gbuf_free(struct gbuf *buf)
+{
+ if (buf->alloc)
+ free(buf->buffer);
+ gbuf_init(buf);
+}
+
+void gbuf_add_ch(struct gbuf *buf, char ch)
+{
+ gbuf_grow(buf, 1);
+ buf->buffer[buf->len++] = ch;
+ buf->buffer[buf->len] = 0;
+}
+
+void gbuf_add_bytes(struct gbuf *buf, const void *data, size_t len)
+{
+ gbuf_grow(buf, len);
+ memcpy(buf->buffer + buf->len, data, len);
+ buf->len += len;
+ buf->buffer[buf->len] = 0;
+}
+
+void gbuf_add_str(struct gbuf *buf, const char *str)
+{
+ int len = strlen(str);
+
+ if (!len)
+ return;
+ gbuf_grow(buf, len);
+ memcpy(buf->buffer + buf->len, str, len + 1);
+ buf->len += len;
+}
+
+void gbuf_addf(struct gbuf *buf, const char *fmt, ...)
+{
+ va_list ap;
+ int slen;
+
+ va_start(ap, fmt);
+ slen = vsnprintf(buf->buffer + buf->len, buf->alloc - buf->len, fmt, ap);
+ va_end(ap);
+
+ if (slen > gbuf_avail(buf)) {
+ gbuf_grow(buf, slen + 1);
+
+ va_start(ap, fmt);
+ slen = vsnprintf(buf->buffer + buf->len, buf->alloc - buf->len, fmt, ap);
+ va_end(ap);
+ }
+
+ buf->len += slen;
+}
+
+void gbuf_set(struct gbuf *buf, int c, size_t count)
+{
+ gbuf_grow(buf, count);
+ memset(buf->buffer + buf->len, c, count);
+ buf->len += count;
+ buf->buffer[buf->len] = 0;
+}
+
+char *gbuf_steal(struct gbuf *buf)
+{
+ char *b = buf->buffer;
+ if (!buf->alloc)
+ b = xnew0(char, 1);
+ gbuf_init(buf);
+ return b;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2008 Timo Hirvonen
+ *
+ * This code is largely based on strbuf in the GIT version control system.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GBUF_H
+#define GBUF_H
+
+#include "compiler.h"
+
+#include <stddef.h> /* size_t */
+
+struct gbuf {
+ char *buffer;
+ size_t alloc;
+ size_t len;
+};
+
+extern char gbuf_empty_buffer[];
+
+#define GBUF(name) struct gbuf name = { gbuf_empty_buffer, 0, 0 }
+
+static inline void gbuf_clear(struct gbuf *buf)
+{
+ buf->len = 0;
+ buf->buffer[0] = 0;
+}
+
+static inline size_t gbuf_avail(struct gbuf *buf)
+{
+ if (buf->alloc)
+ return buf->alloc - buf->len - 1;
+ return 0;
+}
+
+void gbuf_grow(struct gbuf *buf, size_t more);
+void gbuf_free(struct gbuf *buf);
+void gbuf_add_ch(struct gbuf *buf, char ch);
+void gbuf_add_bytes(struct gbuf *buf, const void *data, size_t len);
+void gbuf_add_str(struct gbuf *buf, const char *str);
+void gbuf_addf(struct gbuf *buf, const char *fmt, ...) __FORMAT(2, 3);
+void gbuf_set(struct gbuf *buf, int c, size_t count);
+char *gbuf_steal(struct gbuf *buf);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "glob.h"
+#include "uchar.h"
+#include "list.h"
+#include "xmalloc.h"
+#include "debug.h"
+
+#include <string.h>
+
+struct glob_item {
+ struct list_head node;
+ enum {
+ GLOB_STAR,
+ GLOB_QMARK,
+ GLOB_TEXT
+ } type;
+ char text[];
+};
+
+/* simplification:
+ *
+ * ??*? => ???*
+ * *?* => ?*
+ * *? => ?*
+ * ...
+ */
+static void simplify(struct list_head *head)
+{
+ struct list_head *item;
+
+ item = head->next;
+ while (item != head) {
+ struct list_head *i, *next;
+ int qcount = 0;
+ int scount = 0;
+
+ i = item;
+ do {
+ struct glob_item *gi;
+
+ gi = container_of(i, struct glob_item, node);
+ if (gi->type == GLOB_STAR) {
+ scount++;
+ } else if (gi->type == GLOB_QMARK) {
+ qcount++;
+ } else {
+ i = i->next;
+ break;
+ }
+ i = i->next;
+ } while (i != head);
+
+ next = i;
+
+ if (scount) {
+ /* move all qmarks to front and
+ * if there are >1 stars remove all but the last */
+ struct list_head *insert_after = item->prev;
+
+ i = item;
+ while (qcount) {
+ struct glob_item *gi;
+
+ gi = container_of(i, struct glob_item, node);
+ i = i->next;
+ if (gi->type == GLOB_QMARK) {
+ list_del(&gi->node);
+ list_add(&gi->node, insert_after);
+ qcount--;
+ }
+ }
+
+ i = item;
+ while (scount > 1) {
+ struct glob_item *gi;
+
+ gi = container_of(i, struct glob_item, node);
+ i = i->next;
+ if (gi->type == GLOB_STAR) {
+ list_del(&gi->node);
+ free(gi);
+ scount--;
+ }
+ }
+ }
+
+ item = next;
+ }
+}
+
+void glob_compile(struct list_head *head, const char *pattern)
+{
+ int i = 0;
+
+ list_init(head);
+ while (pattern[i]) {
+ struct glob_item *item;
+
+ if (pattern[i] == '*') {
+ item = xnew(struct glob_item, 1);
+ item->type = GLOB_STAR;
+ i++;
+ } else if (pattern[i] == '?') {
+ item = xnew(struct glob_item, 1);
+ item->type = GLOB_QMARK;
+ i++;
+ } else {
+ int start, len, j;
+ char *str;
+
+ start = i;
+ len = 0;
+ while (pattern[i]) {
+ if (pattern[i] == '\\') {
+ i++;
+ len++;
+ if (pattern[i])
+ i++;
+ } else if (pattern[i] == '*') {
+ break;
+ } else if (pattern[i] == '?') {
+ break;
+ } else {
+ i++;
+ len++;
+ }
+ }
+
+ item = xmalloc(sizeof(struct glob_item) + len + 1);
+ item->type = GLOB_TEXT;
+
+ str = item->text;
+ i = start;
+ j = 0;
+ while (j < len) {
+ if (pattern[i] == '\\') {
+ i++;
+ if (pattern[i]) {
+ str[j++] = pattern[i++];
+ } else {
+ str[j++] = '\\';
+ }
+ } else {
+ str[j++] = pattern[i++];
+ }
+ }
+ str[j] = 0;
+ }
+ list_add_tail(&item->node, head);
+ }
+ simplify(head);
+}
+
+void glob_free(struct list_head *head)
+{
+ struct list_head *item = head->next;
+
+ while (item != head) {
+ struct glob_item *gi;
+ struct list_head *next = item->next;
+
+ gi = container_of(item, struct glob_item, node);
+ free(gi);
+ item = next;
+ }
+}
+
+static int do_glob_match(struct list_head *head, struct list_head *first, const char *text)
+{
+ struct list_head *item = first;
+
+ while (item != head) {
+ struct glob_item *gitem;
+
+ gitem = container_of(item, struct glob_item, node);
+ if (gitem->type == GLOB_TEXT) {
+ int len = u_strlen(gitem->text);
+
+ if (!u_strncase_equal_base(gitem->text, text, len))
+ return 0;
+ text += strlen(gitem->text);
+ } else if (gitem->type == GLOB_QMARK) {
+ uchar u;
+ int idx = 0;
+
+ u = u_get_char(text, &idx);
+ if (u == 0)
+ return 0;
+ text += idx;
+ } else if (gitem->type == GLOB_STAR) {
+ /* after star there MUST be normal text (or nothing),
+ * question marks have been moved before this star and
+ * other stars have been sripped (see simplify)
+ */
+ struct list_head *next;
+ struct glob_item *next_gi;
+ const char *t;
+ int tlen;
+
+ next = item->next;
+ if (next == head) {
+ /* this star was the last item => matched */
+ return 1;
+ }
+ next_gi = container_of(next, struct glob_item, node);
+ BUG_ON(next_gi->type != GLOB_TEXT);
+ t = next_gi->text;
+ tlen = strlen(t);
+ while (1) {
+ const char *pos;
+
+ pos = u_strcasestr_base(text, t);
+ if (pos == NULL)
+ return 0;
+ if (do_glob_match(head, next->next, pos + tlen))
+ return 1;
+ text = pos + 1;
+ }
+ }
+ item = item->next;
+ }
+ return text[0] == 0;
+}
+
+int glob_match(struct list_head *head, const char *text)
+{
+ return do_glob_match(head, head->next, text);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GLOB_H
+#define GLOB_H
+
+#include "list.h"
+
+void glob_compile(struct list_head *head, const char *pattern);
+void glob_free(struct list_head *head);
+int glob_match(struct list_head *head, const char *text);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 <ft@bewatermyfriend.org>
+ *
+ * heavily based on filters.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "help.h"
+#include "window.h"
+#include "search.h"
+#include "misc.h"
+#include "xmalloc.h"
+#include "keys.h"
+#include "command_mode.h"
+#include "ui_curses.h"
+#include "options.h"
+#include "cmdline.h"
+
+#include <stdio.h>
+
+struct window *help_win;
+struct searchable *help_searchable;
+
+static LIST_HEAD(help_head);
+static struct list_head *bound_head;
+static struct list_head *bound_tail;
+static struct list_head *unbound_head;
+static struct list_head *unbound_tail;
+
+static inline void help_entry_to_iter(struct help_entry *e, struct iter *iter)
+{
+ iter->data0 = &help_head;
+ iter->data1 = e;
+ iter->data2 = NULL;
+}
+
+static GENERIC_ITER_PREV(help_get_prev, struct help_entry, node)
+static GENERIC_ITER_NEXT(help_get_next, struct help_entry, node)
+
+static int help_search_get_current(void *data, struct iter *iter)
+{
+ return window_get_sel(help_win, iter);
+}
+
+static int help_search_matches(void *data, struct iter *iter, const char *text)
+{
+ int matched = 0;
+ char **words = get_words(text);
+
+ if (words[0] != NULL) {
+ struct help_entry *ent;
+ int i;
+
+ ent = iter_to_help_entry(iter);
+ for (i = 0; ; i++) {
+ if (words[i] == NULL) {
+ window_set_sel(help_win, iter);
+ matched = 1;
+ break;
+ }
+ if (ent->type == HE_TEXT) {
+ if (!u_strcasestr(ent->text, words[i]))
+ break;
+ } else if (ent->type == HE_BOUND) {
+ if (!u_strcasestr(ent->binding->cmd, words[i]) &&
+ !u_strcasestr(ent->binding->key->name, words[i]))
+ break;
+ } else if (ent->type == HE_UNBOUND) {
+ if (!u_strcasestr(ent->command->name, words[i]))
+ break;
+ } else if (ent->type == HE_OPTION) {
+ if (!u_strcasestr(ent->option->name, words[i]))
+ break;
+ }
+ }
+ }
+ free_str_array(words);
+ return matched;
+}
+
+static const struct searchable_ops help_search_ops = {
+ .get_prev = help_get_prev,
+ .get_next = help_get_next,
+ .get_current = help_search_get_current,
+ .matches = help_search_matches
+};
+
+static void help_add_text(const char *s)
+{
+ struct help_entry *ent;
+ ent = xnew(struct help_entry, 1);
+ ent->type = HE_TEXT;
+ ent->text = s;
+ list_add_tail(&ent->node, &help_head);
+}
+
+static void help_add_defaults(void)
+{
+ struct cmus_opt *opt;
+
+ help_add_text("Keybindings");
+ help_add_text("-----------");
+ bound_head = help_head.prev;
+ help_add_text("");
+ help_add_text("Unbound Commands");
+ help_add_text("----------------");
+ unbound_head = help_head.prev;
+ help_add_text("");
+ help_add_text("Options");
+ help_add_text("-------");
+
+ list_for_each_entry(opt, &option_head, node) {
+ struct help_entry *ent = xnew(struct help_entry, 1);
+
+ ent->type = HE_OPTION;
+ ent->option = opt;
+ list_add_tail(&ent->node, &help_head);
+ }
+
+ bound_tail = bound_head->next;
+ unbound_tail = unbound_head->next;
+}
+
+void help_remove_unbound(struct command *cmd)
+{
+ struct help_entry *ent;
+ struct iter i;
+ list_for_each_entry(ent, &help_head, node) {
+ if (ent->type != HE_UNBOUND)
+ continue;
+ if (ent->command == cmd) {
+ help_entry_to_iter(ent, &i);
+ window_row_vanishes(help_win, &i);
+ list_del(&ent->node);
+ free(ent);
+ return;
+ }
+ }
+}
+
+static void list_add_sorted(struct list_head *new, struct list_head *head,
+ struct list_head *tail,
+ int (*cmp)(struct list_head *, struct list_head *))
+{
+ struct list_head *item = tail->prev;
+
+ while (item != head) {
+ if (cmp(new, item) >= 0)
+ break;
+ item = item->prev;
+ }
+ /* add after item */
+ list_add(new, item);
+}
+
+static int bound_cmp(struct list_head *ai, struct list_head *bi)
+{
+ struct help_entry *a = container_of(ai, struct help_entry, node);
+ struct help_entry *b = container_of(bi, struct help_entry, node);
+ int ret = a->binding->ctx - b->binding->ctx;
+
+ if (!ret)
+ ret = strcmp(a->binding->key->name, b->binding->key->name);
+ return ret;
+}
+
+static int unbound_cmp(struct list_head *ai, struct list_head *bi)
+{
+ struct help_entry *a = container_of(ai, struct help_entry, node);
+ struct help_entry *b = container_of(bi, struct help_entry, node);
+
+ return strcmp(a->command->name, b->command->name);
+}
+
+void help_add_unbound(struct command *cmd)
+{
+ struct help_entry *ent;
+
+ ent = xnew(struct help_entry, 1);
+ ent->type = HE_UNBOUND;
+ ent->command = cmd;
+ list_add_sorted(&ent->node, unbound_head, unbound_tail, unbound_cmp);
+}
+
+void help_add_all_unbound(void)
+{
+ int i;
+ for (i = 0; commands[i].name; ++i)
+ if (!commands[i].bc)
+ help_add_unbound(&commands[i]);
+}
+
+void help_select(void)
+{
+ struct iter sel;
+ struct help_entry *ent;
+ char buf[512];
+
+ if (!window_get_sel(help_win, &sel))
+ return;
+
+ ent = iter_to_help_entry(&sel);
+ switch (ent->type) {
+ case HE_BOUND:
+ snprintf(buf, sizeof(buf), "bind -f %s %s %s",
+ key_context_names[ent->binding->ctx],
+ ent->binding->key->name,
+ ent->binding->cmd);
+ cmdline_set_text(buf);
+ enter_command_mode();
+ break;
+ case HE_UNBOUND:
+ snprintf(buf, sizeof(buf), "bind common <key> %s",
+ ent->command->name);
+ cmdline_set_text(buf);
+ enter_command_mode();
+ break;
+ case HE_OPTION:
+ snprintf(buf, sizeof(buf), "set %s=", ent->option->name);
+ ent->option->get(ent->option->id, buf + strlen(buf));
+ cmdline_set_text(buf);
+ enter_command_mode();
+ break;
+ default:
+ break;
+ }
+}
+
+void help_toggle(void)
+{
+ struct iter sel;
+ struct help_entry *ent;
+
+ if (!window_get_sel(help_win, &sel))
+ return;
+
+ ent = iter_to_help_entry(&sel);
+ switch (ent->type) {
+ case HE_OPTION:
+ if (ent->option->toggle) {
+ ent->option->toggle(ent->option->id);
+ help_win->changed = 1;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void help_remove(void)
+{
+ struct iter sel;
+ struct help_entry *ent;
+
+ if (!window_get_sel(help_win, &sel))
+ return;
+
+ ent = iter_to_help_entry(&sel);
+ switch (ent->type) {
+ case HE_BOUND:
+ if (yes_no_query("Remove selected binding? [y/N]"))
+ key_unbind(key_context_names[ent->binding->ctx],
+ ent->binding->key->name, 0);
+ break;
+ default:
+ break;
+ }
+}
+
+void help_add_bound(const struct binding *bind)
+{
+ struct help_entry *ent;
+ ent = xnew(struct help_entry, 1);
+ ent->type = HE_BOUND;
+ ent->binding = bind;
+ list_add_sorted(&ent->node, bound_head, bound_tail, bound_cmp);
+}
+
+void help_remove_bound(const struct binding *bind)
+{
+ struct help_entry *ent;
+ struct iter i;
+ list_for_each_entry(ent, &help_head, node) {
+ if (ent->binding == bind) {
+ help_entry_to_iter(ent, &i);
+ window_row_vanishes(help_win, &i);
+ list_del(&ent->node);
+ free(ent);
+ return;
+ }
+ }
+}
+
+void help_init(void)
+{
+ struct iter iter;
+
+ help_win = window_new(help_get_prev, help_get_next);
+ window_set_contents(help_win, &help_head);
+ window_changed(help_win);
+ help_add_defaults();
+
+ iter.data0 = &help_head;
+ iter.data1 = NULL;
+ iter.data2 = NULL;
+ help_searchable = searchable_new(NULL, &iter, &help_search_ops);
+}
+
+void help_exit(void)
+{
+ searchable_free(help_searchable);
+ window_free(help_win);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 <ft@bewatermyfriend.org>
+ *
+ * heavily based on filters.h
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef HELP_H
+#define HELP_H
+
+#include "list.h"
+#include "window.h"
+#include "search.h"
+#include "keys.h"
+
+struct help_entry {
+ struct list_head node;
+ enum {
+ HE_TEXT, /* text entries */
+ HE_BOUND, /* bound keys */
+ HE_UNBOUND, /* unbound commands */
+ HE_OPTION,
+ } type;
+ union {
+ const char *text; /* HE_TEXT */
+ const struct binding *binding; /* HE_BOUND */
+ const struct command *command; /* HE_UNBOUND */
+ const struct cmus_opt *option;
+ };
+};
+
+static inline struct help_entry *iter_to_help_entry(struct iter *iter)
+{
+ return iter->data1;
+}
+
+extern struct window *help_win;
+extern struct searchable *help_searchable;
+
+void help_select(void);
+void help_toggle(void);
+void help_remove(void);
+
+void help_add_bound(const struct binding *bind);
+void help_remove_bound(const struct binding *bind);
+void help_remove_unbound(struct command *cmd);
+void help_add_unbound(struct command *cmd);
+void help_add_all_unbound(void);
+
+void help_init(void);
+void help_exit(void);
+
+#endif /* HELP_H */
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "history.h"
+#include "xmalloc.h"
+#include "file.h"
+#include "uchar.h"
+#include "list.h"
+#include "prog.h"
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+
+struct history_entry {
+ struct list_head node;
+ char *text;
+};
+
+static struct history_entry *history_entry_new(const char *text)
+{
+ struct history_entry *new;
+ new = xnew(struct history_entry, 1);
+ new->text = xstrdup(text);
+ return new;
+}
+
+static void history_entry_free(struct history_entry *history)
+{
+ free(history->text);
+ free(history);
+}
+
+void history_free(struct history *history)
+{
+ struct list_head *item, *temp;
+ list_for_each_safe(item, temp, &history->head) {
+ struct history_entry *history_entry;
+ history_entry = list_entry(item, struct history_entry, node);
+ history_entry_free(history_entry);
+ }
+}
+
+static int history_add_tail(void *data, const char *line)
+{
+ struct history *history = data;
+
+ if (history->lines < history->max_lines) {
+ struct history_entry *new;
+
+ new = history_entry_new(line);
+ list_add_tail(&new->node, &history->head);
+ history->lines++;
+ }
+ return 0;
+}
+
+void history_load(struct history *history, char *filename, int max_lines)
+{
+ list_init(&history->head);
+ history->max_lines = max_lines;
+ history->lines = 0;
+ history->search_pos = NULL;
+ history->filename = filename;
+ file_for_each_line(filename, history_add_tail, history);
+}
+
+void history_save(struct history *history)
+{
+ char filename_tmp[512];
+ struct list_head *item;
+ int fd;
+ ssize_t rc;
+
+ snprintf(filename_tmp, sizeof(filename_tmp), "%s.tmp", history->filename);
+ fd = open(filename_tmp, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+ if (fd == -1)
+ return;
+ list_for_each(item, &history->head) {
+ struct history_entry *history_entry;
+ const char nl = '\n';
+
+ history_entry = list_entry(item, struct history_entry, node);
+
+ rc = write(fd, history_entry->text, strlen(history_entry->text));
+ if (rc == -1)
+ goto out;
+
+ rc = write(fd, &nl, 1);
+ if (rc == -1)
+ goto out;
+ }
+out:
+ close(fd);
+
+ rc = rename(filename_tmp, history->filename);
+ if (rc)
+ warn_errno("renaming %s to %s", filename_tmp, history->filename);
+}
+
+void history_add_line(struct history *history, const char *line)
+{
+ struct history_entry *new;
+ struct list_head *item;
+
+ new = history_entry_new(line);
+ list_add(&new->node, &history->head);
+ history->lines++;
+
+ /* remove identical */
+ item = history->head.next->next;
+ while (item != &history->head) {
+ struct list_head *next = item->next;
+ struct history_entry *hentry;
+
+ hentry = container_of(item, struct history_entry, node);
+ if (strcmp(hentry->text, new->text) == 0) {
+ list_del(item);
+ history_entry_free(hentry);
+ history->lines--;
+ }
+ item = next;
+ }
+
+ /* remove oldest if history is 'full' */
+ if (history->lines > history->max_lines) {
+ struct list_head *node;
+ struct history_entry *hentry;
+
+ node = history->head.prev;
+ list_del(node);
+ hentry = list_entry(node, struct history_entry, node);
+ history_entry_free(hentry);
+ history->lines--;
+ }
+}
+
+void history_reset_search(struct history *history)
+{
+ history->search_pos = NULL;
+}
+
+const char *history_search_forward(struct history *history, const char *text)
+{
+ struct list_head *item;
+ int search_len;
+
+ if (history->search_pos == NULL) {
+ /* first time to search. set search */
+ item = history->head.next;
+ } else {
+ item = history->search_pos->next;
+ }
+ search_len = strlen(text);
+ while (item != &history->head) {
+ struct history_entry *hentry;
+
+ hentry = list_entry(item, struct history_entry, node);
+ if (strncmp(text, hentry->text, search_len) == 0) {
+ history->search_pos = item;
+ return hentry->text;
+ }
+ item = item->next;
+ }
+ return NULL;
+}
+
+const char *history_search_backward(struct history *history, const char *text)
+{
+ struct list_head *item;
+ int search_len;
+
+ if (history->search_pos == NULL)
+ return NULL;
+ item = history->search_pos->prev;
+ search_len = strlen(text);
+ while (item != &history->head) {
+ struct history_entry *hentry;
+
+ hentry = list_entry(item, struct history_entry, node);
+ if (strncmp(text, hentry->text, search_len) == 0) {
+ history->search_pos = item;
+ return hentry->text;
+ }
+ item = item->prev;
+ }
+ history->search_pos = NULL;
+ return NULL;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _HISTORY_H
+#define _HISTORY_H
+
+#include "list.h"
+
+struct history {
+ struct list_head head;
+ struct list_head *search_pos;
+ char *filename;
+ int max_lines;
+ int lines;
+};
+
+void history_load(struct history *history, char *filename, int max_lines);
+void history_save(struct history *history);
+void history_free(struct history *history);
+void history_add_line(struct history *history, const char *line);
+void history_reset_search(struct history *history);
+const char *history_search_forward(struct history *history, const char *text);
+const char *history_search_backward(struct history *history, const char *text);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "http.h"
+#include "file.h"
+#include "debug.h"
+#include "xmalloc.h"
+#include "gbuf.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <errno.h>
+
+/*
+ * @uri is http://[user[:pass]@]host[:port][/path][?query]
+ *
+ * uri(7): If the URL supplies a user name but no password, and the remote
+ * server requests a password, the program interpreting the URL should request
+ * one from the user.
+ */
+int http_parse_uri(const char *uri, struct http_uri *u)
+{
+ const char *str, *colon, *at, *slash, *host_start;
+
+ /* initialize all fields */
+ u->uri = xstrdup(uri);
+ u->user = NULL;
+ u->pass = NULL;
+ u->host = NULL;
+ u->path = NULL;
+ u->port = 80;
+
+ if (strncmp(uri, "http://", 7))
+ return -1;
+ str = uri + 7;
+ host_start = str;
+
+ /* [/path] */
+ slash = strchr(str, '/');
+ if (slash) {
+ u->path = xstrdup(slash);
+ } else {
+ u->path = xstrdup("/");
+ }
+
+ /* [user[:pass]@] */
+ at = strchr(str, '@');
+ if (at) {
+ /* user[:pass]@ */
+ host_start = at + 1;
+ colon = strchr(str, ':');
+ if (colon == NULL || colon > at) {
+ /* user */
+ u->user = xstrndup(str, at - str);
+ } else {
+ /* user:pass */
+ u->user = xstrndup(str, colon - str);
+ u->pass = xstrndup(colon + 1, at - (colon + 1));
+ }
+ }
+
+ /* host[:port] */
+ colon = strchr(host_start, ':');
+ if (colon) {
+ /* host:port */
+ const char *start;
+ int port;
+
+ u->host = xstrndup(host_start, colon - host_start);
+ colon++;
+ start = colon;
+
+ port = 0;
+ while (*colon >= '0' && *colon <= '9') {
+ port *= 10;
+ port += *colon - '0';
+ colon++;
+ }
+ u->port = port;
+
+ if (colon == start || (*colon != 0 && *colon != '/')) {
+ http_free_uri(u);
+ return -1;
+ }
+ } else {
+ /* host */
+ if (slash) {
+ u->host = xstrndup(host_start, slash - host_start);
+ } else {
+ u->host = xstrdup(host_start);
+ }
+ }
+ return 0;
+}
+
+void http_free_uri(struct http_uri *u)
+{
+ free(u->uri);
+ free(u->user);
+ free(u->pass);
+ free(u->host);
+ free(u->path);
+
+ u->uri = NULL;
+ u->user = NULL;
+ u->pass = NULL;
+ u->host = NULL;
+ u->path = NULL;
+}
+
+int http_open(struct http_get *hg, int timeout_ms)
+{
+ const struct addrinfo hints = {
+ .ai_socktype = SOCK_STREAM
+ };
+ struct addrinfo *result;
+ union {
+ struct sockaddr sa;
+ struct sockaddr_storage sas;
+ } addr;
+ size_t addrlen;
+ struct timeval tv;
+ int save, flags, rc;
+ char port[16];
+
+ char *proxy = getenv("http_proxy");
+ if (proxy) {
+ hg->proxy = xnew(struct http_uri, 1);
+ if (http_parse_uri(proxy, hg->proxy)) {
+ d_print("Failed to parse HTTP proxy URI '%s'\n", proxy);
+ return -1;
+ }
+ } else {
+ hg->proxy = NULL;
+ }
+
+ snprintf(port, sizeof(port), "%d", hg->proxy ? hg->proxy->port : hg->uri.port);
+ rc = getaddrinfo(hg->proxy ? hg->proxy->host : hg->uri.host, port, &hints, &result);
+ if (rc != 0) {
+ d_print("getaddrinfo: %s\n", gai_strerror(rc));
+ return -1;
+ }
+ memcpy(&addr.sa, result->ai_addr, result->ai_addrlen);
+ addrlen = result->ai_addrlen;
+ freeaddrinfo(result);
+
+ hg->fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+ if (hg->fd == -1)
+ return -1;
+
+ flags = fcntl(hg->fd, F_GETFL);
+ if (fcntl(hg->fd, F_SETFL, O_NONBLOCK) == -1)
+ goto close_exit;
+
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = (timeout_ms % 1000) * 1000;
+ while (1) {
+ fd_set wfds;
+
+ d_print("connecting. timeout=%lld s %lld us\n", (long long)tv.tv_sec, (long long)tv.tv_usec);
+ if (connect(hg->fd, &addr.sa, addrlen) == 0)
+ break;
+ if (errno == EISCONN)
+ break;
+ if (errno != EAGAIN && errno != EINPROGRESS)
+ goto close_exit;
+
+ FD_ZERO(&wfds);
+ FD_SET(hg->fd, &wfds);
+ while (1) {
+ rc = select(hg->fd + 1, NULL, &wfds, NULL, &tv);
+ if (rc == -1) {
+ if (errno != EINTR)
+ goto close_exit;
+ /* signalled */
+ continue;
+ }
+ if (rc == 1) {
+ /* socket ready */
+ break;
+ }
+ if (tv.tv_sec == 0 && tv.tv_usec == 0) {
+ errno = ETIMEDOUT;
+ goto close_exit;
+ }
+ }
+ }
+
+ /* restore old flags */
+ if (fcntl(hg->fd, F_SETFL, flags) == -1)
+ goto close_exit;
+ return 0;
+close_exit:
+ save = errno;
+ close(hg->fd);
+ errno = save;
+ return -1;
+}
+
+static int http_write(int fd, const char *buf, int count, int timeout_ms)
+{
+ struct timeval tv;
+ int pos = 0;
+
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = (timeout_ms % 1000) * 1000;
+ while (1) {
+ fd_set wfds;
+ int rc;
+
+ d_print("timeout=%lld s %lld us\n", (long long)tv.tv_sec, (long long)tv.tv_usec);
+
+ FD_ZERO(&wfds);
+ FD_SET(fd, &wfds);
+ rc = select(fd + 1, NULL, &wfds, NULL, &tv);
+ if (rc == -1) {
+ if (errno != EINTR)
+ return -1;
+ /* signalled */
+ continue;
+ }
+ if (rc == 1) {
+ rc = write(fd, buf + pos, count - pos);
+ if (rc == -1) {
+ if (errno == EINTR || errno == EAGAIN)
+ continue;
+ return -1;
+ }
+ pos += rc;
+ if (pos == count)
+ return 0;
+ } else if (tv.tv_sec == 0 && tv.tv_usec == 0) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+ }
+}
+
+static int read_timeout(int fd, int timeout_ms)
+{
+ struct timeval tv;
+
+ tv.tv_sec = timeout_ms / 1000;
+ tv.tv_usec = (timeout_ms % 1000) * 1000;
+ while (1) {
+ fd_set rfds;
+ int rc;
+
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+ rc = select(fd + 1, &rfds, NULL, NULL, &tv);
+ if (rc == -1) {
+ if (errno != EINTR)
+ return -1;
+ /* signalled */
+ continue;
+ }
+ if (rc == 1)
+ return 0;
+ if (tv.tv_sec == 0 && tv.tv_usec == 0) {
+ errno = ETIMEDOUT;
+ return -1;
+ }
+ }
+}
+
+/* reads response, ignores fscking carriage returns */
+static int http_read_response(int fd, struct gbuf *buf, int timeout_ms)
+{
+ char prev = 0;
+
+ if (read_timeout(fd, timeout_ms))
+ return -1;
+ while (1) {
+ int rc;
+ char ch;
+
+ rc = read(fd, &ch, 1);
+ if (rc == -1) {
+ return -1;
+ }
+ if (rc == 0) {
+ return -2;
+ }
+ if (ch == '\r')
+ continue;
+ if (ch == '\n' && prev == '\n')
+ return 0;
+ gbuf_add_ch(buf, ch);
+ prev = ch;
+ }
+}
+
+static int http_parse_response(char *str, struct http_get *hg)
+{
+ /* str is 0 terminated buffer of lines
+ * every line ends with '\n'
+ * no carriage returns
+ * no empty lines
+ */
+ GROWING_KEYVALS(h);
+ char *end;
+
+ if (strncmp(str, "HTTP/", 5) == 0) {
+ str += 5;
+ while (*str != ' ') {
+ if (*str == '\n') {
+ return -2;
+ }
+ str++;
+ }
+ } else if (strncmp(str, "ICY", 3) == 0) {
+ str += 3;
+ } else {
+ return -2;
+ }
+ while (*str == ' ')
+ str++;
+
+ hg->code = 0;
+ while (*str >= '0' && *str <= '9') {
+ hg->code *= 10;
+ hg->code += *str - '0';
+ str++;
+ }
+ if (!hg->code)
+ return -2;
+ while (*str == ' ')
+ str++;
+
+ end = strchr(str, '\n');
+ hg->reason = xstrndup(str, end - str);
+ str = end + 1;
+
+ /* headers */
+ while (*str) {
+ char *ptr;
+
+ end = strchr(str, '\n');
+ ptr = strchr(str, ':');
+ if (ptr == NULL || ptr > end) {
+ free(hg->reason);
+ hg->reason = NULL;
+ keyvals_terminate(&h);
+ keyvals_free(h.keyvals);
+ return -2;
+ }
+
+ *ptr++ = 0;
+ while (*ptr == ' ')
+ ptr++;
+
+ keyvals_add(&h, str, xstrndup(ptr, end - ptr));
+ str = end + 1;
+ }
+ keyvals_terminate(&h);
+ hg->headers = h.keyvals;
+ return 0;
+}
+
+int http_get(struct http_get *hg, struct keyval *headers, int timeout_ms)
+{
+ GBUF(buf);
+ int i, rc, save;
+
+ gbuf_add_str(&buf, "GET ");
+ gbuf_add_str(&buf, hg->proxy ? hg->uri.uri : hg->uri.path);
+ gbuf_add_str(&buf, " HTTP/1.0\r\n");
+ for (i = 0; headers[i].key; i++) {
+ gbuf_add_str(&buf, headers[i].key);
+ gbuf_add_str(&buf, ": ");
+ gbuf_add_str(&buf, headers[i].val);
+ gbuf_add_str(&buf, "\r\n");
+ }
+ gbuf_add_str(&buf, "\r\n");
+
+ rc = http_write(hg->fd, buf.buffer, buf.len, timeout_ms);
+ if (rc)
+ goto out;
+
+ gbuf_clear(&buf);
+ rc = http_read_response(hg->fd, &buf, timeout_ms);
+ if (rc)
+ goto out;
+
+ rc = http_parse_response(buf.buffer, hg);
+out:
+ save = errno;
+ gbuf_free(&buf);
+ errno = save;
+ return rc;
+}
+
+char *http_read_body(int fd, size_t *size, int timeout_ms)
+{
+ GBUF(buf);
+
+ if (read_timeout(fd, timeout_ms))
+ return NULL;
+ while (1) {
+ int count = 1023;
+ int rc;
+
+ gbuf_grow(&buf, count);
+ rc = read_all(fd, buf.buffer + buf.len, count);
+ if (rc == -1) {
+ gbuf_free(&buf);
+ return NULL;
+ }
+ buf.len += rc;
+ if (rc == 0) {
+ *size = buf.len;
+ return gbuf_steal(&buf);
+ }
+ }
+}
+
+void http_get_free(struct http_get *hg)
+{
+ http_free_uri(&hg->uri);
+ if (hg->proxy) {
+ http_free_uri(hg->proxy);
+ free(hg->proxy);
+ }
+ if (hg->headers)
+ keyvals_free(hg->headers);
+ free(hg->reason);
+}
+
+char *base64_encode(const char *str)
+{
+ static const char t[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ int str_len, buf_len, i, s, d;
+ char *buf;
+ unsigned char b0, b1, b2;
+
+ str_len = strlen(str);
+ buf_len = (str_len + 2) / 3 * 4 + 1;
+ buf = xnew(char, buf_len);
+ s = 0;
+ d = 0;
+ for (i = 0; i < str_len / 3; i++) {
+ b0 = str[s++];
+ b1 = str[s++];
+ b2 = str[s++];
+
+ /* 6 ms bits of b0 */
+ buf[d++] = t[b0 >> 2];
+
+ /* 2 ls bits of b0 . 4 ms bits of b1 */
+ buf[d++] = t[((b0 << 4) | (b1 >> 4)) & 0x3f];
+
+ /* 4 ls bits of b1 . 2 ms bits of b2 */
+ buf[d++] = t[((b1 << 2) | (b2 >> 6)) & 0x3f];
+
+ /* 6 ls bits of b2 */
+ buf[d++] = t[b2 & 0x3f];
+ }
+ switch (str_len % 3) {
+ case 2:
+ b0 = str[s++];
+ b1 = str[s++];
+
+ /* 6 ms bits of b0 */
+ buf[d++] = t[b0 >> 2];
+
+ /* 2 ls bits of b0 . 4 ms bits of b1 */
+ buf[d++] = t[((b0 << 4) | (b1 >> 4)) & 0x3f];
+
+ /* 4 ls bits of b1 */
+ buf[d++] = t[(b1 << 2) & 0x3f];
+
+ buf[d++] = '=';
+ break;
+ case 1:
+ b0 = str[s++];
+
+ /* 6 ms bits of b0 */
+ buf[d++] = t[b0 >> 2];
+
+ /* 2 ls bits of b0 */
+ buf[d++] = t[(b0 << 4) & 0x3f];
+
+ buf[d++] = '=';
+ buf[d++] = '=';
+ break;
+ case 0:
+ break;
+ }
+ buf[d++] = 0;
+ return buf;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _HTTP_H
+#define _HTTP_H
+
+#include "keyval.h"
+
+#include <stddef.h> /* size_t */
+
+/*
+ * 1xx indicates an informational message only
+ * 2xx indicates success of some kind
+ * 3xx redirects the client to another URL
+ * 4xx indicates an error on the client's part
+ * 5xx indicates an error on the server's part
+ */
+
+struct http_uri {
+ char *uri;
+ char *user;
+ char *pass;
+ char *host;
+ char *path;
+ int port;
+};
+
+struct http_get {
+ struct http_uri uri;
+ struct http_uri *proxy;
+ int fd;
+ struct keyval *headers;
+ char *reason;
+ int code;
+};
+
+int http_parse_uri(const char *uri, struct http_uri *u);
+
+/* frees contents of @u, not @u itself */
+void http_free_uri(struct http_uri *u);
+
+int http_open(struct http_get *hg, int timeout_ms);
+
+/*
+ * returns: 0 success
+ * -1 check errno
+ * -2 parse error
+ */
+int http_get(struct http_get *hg, struct keyval *headers, int timeout_ms);
+void http_get_free(struct http_get *hg);
+
+char *http_read_body(int fd, size_t *size, int timeout_ms);
+char *base64_encode(const char *str);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "id3.h"
+#include "xmalloc.h"
+#include "convert.h"
+#include "uchar.h"
+#include "options.h"
+#include "debug.h"
+#include "utils.h"
+#include "file.h"
+
+#include <unistd.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <limits.h>
+
+enum {
+ ID3_ENCODING_ISO_8859_1 = 0x00,
+ ID3_ENCODING_UTF_16 = 0x01,
+ ID3_ENCODING_UTF_16_BE = 0x02,
+ ID3_ENCODING_UTF_8 = 0x03,
+
+ ID3_ENCODING_MAX = 0x03
+};
+
+/*
+ * position:
+ *
+ * 0 "ID3"
+ * -10 "3DI"
+ * -128 "TAG"
+ * -138 "3DI"
+ *
+ * if v2 is at beginning _and_ at end then there must be a seek tag at beginning
+ */
+
+struct v2_header {
+ unsigned char ver_major;
+ unsigned char ver_minor;
+ unsigned char flags;
+ uint32_t size;
+};
+
+struct v2_extended_header {
+ uint32_t size;
+};
+
+struct v2_frame_header {
+ char id[4];
+ uint32_t size;
+ uint16_t flags;
+};
+
+#define V2_HEADER_UNSYNC (1 << 7)
+#define V2_HEADER_EXTENDED (1 << 6)
+#define V2_HEADER_EXPERIMENTAL (1 << 5)
+#define V2_HEADER_FOOTER (1 << 4)
+
+#define V2_FRAME_COMPRESSED (1 << 3) /* great idea!!1 */
+#define V2_FRAME_ENCRYPTHED (1 << 2) /* wow, this is very neat! */
+#define V2_FRAME_UNSYNC (1 << 1)
+#define V2_FRAME_LEN_INDICATOR (1 << 0)
+
+#define NR_GENRES 148
+/* genres {{{ */
+static const char *genres[NR_GENRES] = {
+ "Blues",
+ "Classic Rock",
+ "Country",
+ "Dance",
+ "Disco",
+ "Funk",
+ "Grunge",
+ "Hip-Hop",
+ "Jazz",
+ "Metal",
+ "New Age",
+ "Oldies",
+ "Other",
+ "Pop",
+ "R&B",
+ "Rap",
+ "Reggae",
+ "Rock",
+ "Techno",
+ "Industrial",
+ "Alternative",
+ "Ska",
+ "Death Metal",
+ "Pranks",
+ "Soundtrack",
+ "Euro-Techno",
+ "Ambient",
+ "Trip-Hop",
+ "Vocal",
+ "Jazz+Funk",
+ "Fusion",
+ "Trance",
+ "Classical",
+ "Instrumental",
+ "Acid",
+ "House",
+ "Game",
+ "Sound Clip",
+ "Gospel",
+ "Noise",
+ "Alt",
+ "Bass",
+ "Soul",
+ "Punk",
+ "Space",
+ "Meditative",
+ "Instrumental Pop",
+ "Instrumental Rock",
+ "Ethnic",
+ "Gothic",
+ "Darkwave",
+ "Techno-Industrial",
+ "Electronic",
+ "Pop-Folk",
+ "Eurodance",
+ "Dream",
+ "Southern Rock",
+ "Comedy",
+ "Cult",
+ "Gangsta Rap",
+ "Top 40",
+ "Christian Rap",
+ "Pop/Funk",
+ "Jungle",
+ "Native American",
+ "Cabaret",
+ "New Wave",
+ "Psychedelic",
+ "Rave",
+ "Showtunes",
+ "Trailer",
+ "Lo-Fi",
+ "Tribal",
+ "Acid Punk",
+ "Acid Jazz",
+ "Polka",
+ "Retro",
+ "Musical",
+ "Rock & Roll",
+ "Hard Rock",
+ "Folk",
+ "Folk/Rock",
+ "National Folk",
+ "Swing",
+ "Fast-Fusion",
+ "Bebob",
+ "Latin",
+ "Revival",
+ "Celtic",
+ "Bluegrass",
+ "Avantgarde",
+ "Gothic Rock",
+ "Progressive Rock",
+ "Psychedelic Rock",
+ "Symphonic Rock",
+ "Slow Rock",
+ "Big Band",
+ "Chorus",
+ "Easy Listening",
+ "Acoustic",
+ "Humour",
+ "Speech",
+ "Chanson",
+ "Opera",
+ "Chamber Music",
+ "Sonata",
+ "Symphony",
+ "Booty Bass",
+ "Primus",
+ "Porn Groove",
+ "Satire",
+ "Slow Jam",
+ "Club",
+ "Tango",
+ "Samba",
+ "Folklore",
+ "Ballad",
+ "Power Ballad",
+ "Rhythmic Soul",
+ "Freestyle",
+ "Duet",
+ "Punk Rock",
+ "Drum Solo",
+ "A Cappella",
+ "Euro-House",
+ "Dance Hall",
+ "Goa",
+ "Drum & Bass",
+ "Club-House",
+ "Hardcore",
+ "Terror",
+ "Indie",
+ "BritPop",
+ "Negerpunk",
+ "Polsk Punk",
+ "Beat",
+ "Christian Gangsta Rap",
+ "Heavy Metal",
+ "Black Metal",
+ "Crossover",
+ "Contemporary Christian",
+ "Christian Rock",
+ "Merengue",
+ "Salsa",
+ "Thrash Metal",
+ "Anime",
+ "JPop",
+ "Synthpop"
+};
+/* }}} */
+
+#define id3_debug(...) d_print(__VA_ARGS__)
+
+const char * const id3_key_names[NUM_ID3_KEYS] = {
+ "artist",
+ "album",
+ "title",
+ "date",
+ "originaldate",
+ "genre",
+ "discnumber",
+ "tracknumber",
+ "albumartist",
+ "artistsort",
+ "albumartistsort",
+ "albumsort",
+ "compilation",
+ "replaygain_track_gain",
+ "replaygain_track_peak",
+ "replaygain_album_gain",
+ "replaygain_album_peak",
+ "composer",
+ "conductor",
+ "lyricist",
+ "remixer",
+ "label",
+ "publisher",
+ "subtitle",
+ "comment",
+ "musicbrainz_trackid",
+ "media",
+};
+
+static int utf16_is_lsurrogate(uchar uch)
+{
+ return 0xdc00 <= uch && 0xdfff >= uch;
+}
+
+static int utf16_is_hsurrogate(uchar uch)
+{
+ return 0xd800 <= uch && 0xdbff >= uch;
+}
+
+static int utf16_is_bom(uchar uch)
+{
+ return uch == 0xfeff;
+}
+
+static int utf16_is_special(uchar uch)
+{
+ return utf16_is_hsurrogate(uch) || utf16_is_lsurrogate(uch) || utf16_is_bom(uch);
+}
+
+static char *utf16_to_utf8(const unsigned char *buf, int buf_size)
+{
+ char *out;
+ int i, idx;
+ int little_endian = 0;
+
+ if (buf_size < 2)
+ return NULL;
+
+ if (buf[0] == 0xff && buf[1] == 0xfe)
+ little_endian = 1;
+
+ out = xnew(char, (buf_size / 2) * 4 + 1);
+ i = idx = 0;
+
+ while (buf_size - i >= 2) {
+ uchar u;
+
+ if (little_endian)
+ u = buf[i] + (buf[i + 1] << 8);
+ else
+ u = buf[i + 1] + (buf[i] << 8);
+
+ if (u_is_unicode(u)) {
+ if (!utf16_is_special(u))
+ u_set_char(out, &idx, u);
+ } else {
+ free(out);
+ return NULL;
+ }
+
+ if (u == 0)
+ return out;
+
+ i += 2;
+ }
+
+ u_set_char(out, &idx, 0);
+ return out;
+}
+
+static int is_v1(const char *buf)
+{
+ return buf[0] == 'T' && buf[1] == 'A' && buf[2] == 'G';
+}
+
+static int u32_unsync(const unsigned char *buf, uint32_t *up)
+{
+ uint32_t b, u = 0;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ b = buf[i];
+ if (b >= 0x80)
+ return 0;
+ u <<= 7;
+ u |= b;
+ }
+ *up = u;
+ return 1;
+}
+
+static void get_u32(const unsigned char *buf, uint32_t *up)
+{
+ uint32_t b, u = 0;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ b = buf[i];
+ u <<= 8;
+ u |= b;
+ }
+ *up = u;
+}
+
+static void get_u24(const unsigned char *buf, uint32_t *up)
+{
+ uint32_t b, u = 0;
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ b = buf[i];
+ u <<= 8;
+ u |= b;
+ }
+ *up = u;
+}
+
+static void get_i16(const unsigned char *buf, int16_t *ip)
+{
+ uint16_t b, u = 0;
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ b = buf[i];
+ u <<= 8;
+ u |= b;
+ }
+ *ip = u;
+}
+
+static int v2_header_footer_parse(struct v2_header *header, const char *buf)
+{
+ const unsigned char *b = (const unsigned char *)buf;
+
+ header->ver_major = b[3];
+ header->ver_minor = b[4];
+ header->flags = b[5];
+ if (header->ver_major == 0xff || header->ver_minor == 0xff)
+ return 0;
+ return u32_unsync(b + 6, &header->size);
+}
+
+static int v2_header_parse(struct v2_header *header, const char *buf)
+{
+ if (buf[0] != 'I' || buf[1] != 'D' || buf[2] != '3')
+ return 0;
+ return v2_header_footer_parse(header, buf);
+}
+
+static int v2_footer_parse(struct v2_header *header, const char *buf)
+{
+ if (buf[0] != '3' || buf[1] != 'D' || buf[2] != 'I')
+ return 0;
+ return v2_header_footer_parse(header, buf);
+}
+
+static int v2_extended_header_parse(struct v2_extended_header *header, const char *buf)
+{
+ return u32_unsync((const unsigned char *)buf, &header->size);
+}
+
+static int is_frame_id_char(char ch)
+{
+ return (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9');
+}
+
+/* XXXYYY
+ *
+ * X = [A-Z0-9]
+ * Y = byte
+ *
+ * XXX is frame
+ * YYY is frame size excluding this 6 byte header
+ */
+static int v2_2_0_frame_header_parse(struct v2_frame_header *header, const char *buf)
+{
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (!is_frame_id_char(buf[i]))
+ return 0;
+ header->id[i] = buf[i];
+ }
+ header->id[3] = 0;
+ get_u24((const unsigned char *)(buf + 3), &header->size);
+ header->flags = 0;
+ if (header->size == 0)
+ return 0;
+ id3_debug("%c%c%c %d\n", header->id[0], header->id[1], header->id[2], header->size);
+ return 1;
+}
+
+/* XXXXYYYYZZ
+ *
+ * X = [A-Z0-9]
+ * Y = byte
+ * Z = byte
+ *
+ * XXXX is frame
+ * YYYY is frame size excluding this 10 byte header
+ * ZZ is flags
+ */
+static int v2_3_0_frame_header_parse(struct v2_frame_header *header, const char *buf)
+{
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ if (!is_frame_id_char(buf[i]))
+ return 0;
+ header->id[i] = buf[i];
+ }
+ get_u32((const unsigned char *)(buf + 4), &header->size);
+ header->flags = (buf[8] << 8) | buf[9];
+ if (header->size == 0)
+ return 0;
+ id3_debug("%c%c%c%c %d\n", header->id[0], header->id[1], header->id[2],
+ header->id[3], header->size);
+ return 1;
+}
+
+/* same as 2.3 but header size is sync safe */
+static int v2_4_0_frame_header_parse(struct v2_frame_header *header, const char *buf)
+{
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ if (!is_frame_id_char(buf[i]))
+ return 0;
+ header->id[i] = buf[i];
+ }
+ if (!u32_unsync((const unsigned char *)(buf + 4), &header->size))
+ return 0;
+ header->flags = (buf[8] << 8) | buf[9];
+ if (header->size == 0)
+ return 0;
+ id3_debug("%c%c%c%c %d\n", header->id[0], header->id[1], header->id[2],
+ header->id[3], header->size);
+ return 1;
+}
+
+static char *parse_genre(const char *str)
+{
+ int parenthesis = 0;
+ long int idx;
+ char *end;
+
+ if (strncasecmp(str, "(RX", 3) == 0)
+ return xstrdup("Remix");
+
+ if (strncasecmp(str, "(CR", 3) == 0)
+ return xstrdup("Cover");
+
+ if (*str == '(') {
+ parenthesis = 1;
+ str++;
+ }
+
+ idx = strtol(str, &end, 10);
+ if (str != end) {
+ /* Number parsed but there may be some crap after the number.
+ * I don't care, ID3v2 by definition contains crap.
+ */
+ if (idx >= 0 && idx < NR_GENRES)
+ return xstrdup(genres[idx]);
+ }
+
+ if (parenthesis) {
+ const char *ptr = strchr(str, ')');
+
+ if (ptr && ptr[1]) {
+ /* genre name after random crap in parenthesis,
+ * return the genre name */
+ return xstrdup(ptr + 1);
+ }
+ str--;
+ }
+
+ /* random crap, just return it and wait for a bug report */
+ return xstrdup(str);
+}
+
+/* http://www.id3.org/id3v2.4.0-structure.txt */
+static struct {
+ const char name[8];
+ enum id3_key key;
+} frame_tab[] = {
+ /* 2.4.0 */
+ { "TDRC", ID3_DATE }, // recording date
+ { "TDRL", ID3_DATE }, // release date
+ { "TDOR", ID3_ORIGINALDATE }, // original release date
+ { "TSOP", ID3_ARTISTSORT },
+ { "TSOA", ID3_ALBUMSORT },
+
+ /* >= 2.3.0 */
+ { "TPE1", ID3_ARTIST },
+ { "TALB", ID3_ALBUM },
+ { "TIT2", ID3_TITLE },
+ { "TYER", ID3_DATE },
+ { "TCON", ID3_GENRE },
+ { "TPOS", ID3_DISC },
+ { "TRCK", ID3_TRACK },
+ { "TPE2", ID3_ALBUMARTIST },
+ { "TSO2", ID3_ALBUMARTISTSORT },
+ { "XSOP", ID3_ARTISTSORT }, // obsolete
+ { "XSOA", ID3_ALBUMSORT }, // obsolete
+ { "TCMP", ID3_COMPILATION },
+ { "TORY", ID3_ORIGINALDATE },
+ { "TCOM", ID3_COMPOSER },
+ { "TPE3", ID3_CONDUCTOR },
+ { "TEXT", ID3_LYRICIST },
+ { "TPE4", ID3_REMIXER },
+ { "TPUB", ID3_PUBLISHER }, // TPUB can be both publisher or label
+ { "TIT3", ID3_SUBTITLE },
+ { "TMED", ID3_MEDIA },
+
+ /* obsolete frames (2.2.0) */
+ { "TP1", ID3_ARTIST },
+ { "TP2", ID3_ALBUMARTIST },
+ { "TAL", ID3_ALBUM },
+ { "TT2", ID3_TITLE },
+ { "TYE", ID3_DATE },
+ { "TCO", ID3_GENRE },
+ { "TPA", ID3_DISC },
+ { "TRK", ID3_TRACK },
+ { "TSP", ID3_ARTISTSORT },
+ { "TS2", ID3_ALBUMARTISTSORT },
+ { "TSA", ID3_ALBUMSORT },
+ { "TCP", ID3_COMPILATION },
+
+ { "", -1 }
+};
+
+static int frame_tab_index(const char *id)
+{
+ int i;
+
+ for (i = 0; frame_tab[i].key != -1; i++) {
+ if (!strncmp(id, frame_tab[i].name, 4))
+ return i;
+ }
+ return -1;
+}
+
+static int check_date_format(const char *buf)
+{
+ int i, ch;
+
+ /* year */
+ for (i = 0; i < 4; i++) {
+ ch = *buf++;
+ if (ch < '0' || ch > '9')
+ return 0;
+ }
+ ch = *buf++;
+ if (!ch)
+ return 4;
+ if (ch != '-')
+ return 0;
+
+ /* month */
+ for (i = 0; i < 2; i++) {
+ ch = *buf++;
+ if (ch < '0' || ch > '9')
+ return 0;
+ }
+ ch = *buf++;
+ if (!ch)
+ return 7;
+ if (ch != '-')
+ return 0;
+
+ /* day */
+ for (i = 0; i < 2; i++) {
+ ch = *buf++;
+ if (ch < '0' || ch > '9')
+ return 0;
+ }
+ ch = *buf;
+ if (!ch || (ch >= '0' && ch <= '9'))
+ return 10;
+ return 0;
+}
+
+static void fix_date(char *buf)
+{
+ const char *ptr = buf;
+ int ch, len = 0;
+
+ do {
+ ch = *ptr++;
+ if (ch >= '0' && ch <= '9') {
+ len++;
+ continue;
+ }
+ if (len == 4) {
+ // number which length is 4, must be year
+ memmove(buf, ptr - 5, 4);
+ buf[4] = 0;
+ return;
+ }
+ len = 0;
+ } while (ch);
+ *buf = 0;
+}
+
+static char *decode_str(const char *buf, int len, int encoding)
+{
+ char *in, *out = NULL;
+
+ switch (encoding) {
+ case ID3_ENCODING_ISO_8859_1:
+ in = xstrndup(buf, len);
+ utf8_encode(in, id3_default_charset, &out);
+ free(in);
+ break;
+ case ID3_ENCODING_UTF_8:
+ in = xstrndup(buf, len);
+ if (u_is_valid(in)) {
+ out = in;
+ } else {
+ utf8_encode(in, id3_default_charset, &out);
+ free(in);
+ }
+ break;
+ case ID3_ENCODING_UTF_16:
+ case ID3_ENCODING_UTF_16_BE:
+ out = utf16_to_utf8((const unsigned char *)buf, len);
+ break;
+ }
+ return out;
+}
+
+static void add_v2(struct id3tag *id3, enum id3_key key, char *value)
+{
+ free(id3->v2[key]);
+ id3->v2[key] = value;
+ id3->has_v2 = 1;
+}
+
+static void decode_normal(struct id3tag *id3, const char *buf, int len, int encoding, enum id3_key key)
+{
+ char *out = decode_str(buf, len, encoding);
+
+ if (!out)
+ return;
+
+ if (key == ID3_GENRE) {
+ char *tmp;
+
+ id3_debug("genre before: '%s'\n", out);
+ tmp = parse_genre(out);
+ free(out);
+ out = tmp;
+ } else if (key == ID3_DATE || key == ID3_ORIGINALDATE) {
+ int date_len = check_date_format(out);
+ id3_debug("date before: '%s'\n", out);
+ if (date_len)
+ out[date_len] = '\0';
+ else
+ fix_date(out);
+ if (!*out) {
+ id3_debug("date parsing failed\n");
+ free(out);
+ return;
+ }
+ } else if (key == ID3_ALBUMARTIST) {
+ /*
+ * This must be TPE2 frame; ignore it if ID3_ALBUMARTIST is
+ * already present
+ */
+ if (id3->v2[key])
+ return;
+ } else if (key == ID3_PUBLISHER) {
+ add_v2(id3, ID3_LABEL, strdup(out));
+ }
+
+ add_v2(id3, key, out);
+}
+
+static size_t id3_skiplen(const char *buf, size_t len, int encoding)
+{
+ if (encoding == ID3_ENCODING_ISO_8859_1 || encoding == ID3_ENCODING_UTF_8) {
+ return strlen(buf) + 1;
+ } else {
+ int i = 0;
+ while (i + 1 < len) {
+ if (buf[i] == '\0' && buf[i + 1] == '\0')
+ return i + 2;
+
+ /* Assume every character is exactly 2 bytes */
+ i += 2;
+ }
+
+ BUG_ON(1);
+ }
+}
+
+static void decode_txxx(struct id3tag *id3, const char *buf, int len, int encoding)
+{
+ const char ql_prefix[] = "QuodLibet::";
+ enum id3_key key = NUM_ID3_KEYS;
+ int size;
+ char *out, *out_mem;
+
+ out = decode_str(buf, len, encoding);
+ if (!out)
+ return;
+
+ id3_debug("TXXX, key = '%s'\n", out);
+
+ out_mem = out;
+
+ /* skip braindead QuodLibet TXXX frame prefix */
+ if (!strncmp(out, ql_prefix, sizeof(ql_prefix) - 1))
+ out += sizeof(ql_prefix) - 1;
+
+ if (!strcasecmp(out, "replaygain_track_gain"))
+ key = ID3_RG_TRACK_GAIN;
+ else if (!strcasecmp(out, "replaygain_track_peak"))
+ key = ID3_RG_TRACK_PEAK;
+ else if (!strcasecmp(out, "replaygain_album_gain"))
+ key = ID3_RG_ALBUM_GAIN;
+ else if (!strcasecmp(out, "replaygain_album_peak"))
+ key = ID3_RG_ALBUM_PEAK;
+ else if (!strcasecmp(out, "album artist"))
+ key = ID3_ALBUMARTIST;
+ else if (!strcasecmp(out, "albumartist"))
+ key = ID3_ALBUMARTIST;
+ else if (!strcasecmp(out, "albumartistsort"))
+ key = ID3_ALBUMARTISTSORT;
+ else if (!strcasecmp(out, "albumsort"))
+ key = ID3_ALBUMSORT;
+ else if (!strcasecmp(out, "compilation"))
+ key = ID3_COMPILATION;
+
+ size = id3_skiplen(buf, len, encoding);
+ free(out_mem);
+
+ if (key == NUM_ID3_KEYS)
+ return;
+
+ buf += size;
+ len -= size;
+ if (len <= 0)
+ return;
+
+ out = decode_str(buf, len, encoding);
+ if (!out)
+ return;
+
+ add_v2(id3, key, out);
+}
+
+static void decode_comment(struct id3tag *id3, const char *buf, int len, int encoding)
+{
+ int slen;
+ char *out;
+ int valid_description;
+
+ if (len <= 3)
+ return;
+
+ /* skip language */
+ buf += 3;
+ len -= 3;
+
+ /* "Short content description" part of COMM frame */
+ out = decode_str(buf, len, encoding);
+ if (!out)
+ return;
+
+ valid_description = strcmp(out, "") == 0 || strcmp(out, "description") == 0;
+ free(out);
+
+ if (!valid_description)
+ return;
+
+ slen = id3_skiplen(buf, len, encoding);
+ if (slen >= len)
+ return;
+
+ buf += slen;
+ len -= slen;
+
+ out = decode_str(buf, len, encoding);
+ if (!out)
+ return;
+
+ add_v2(id3, ID3_COMMENT, out);
+}
+
+/*
+ * From http://id3.org/id3v2.4.0-frames:
+ *
+ * The volume adjustment is encoded as a fixed point decibel value, 16 bit signed
+ * integer representing (adjustment*512), giving +/- 64 dB with a precision of
+ * 0.001953125 dB. E.g. +2 dB is stored as $04 00 and -2 dB is $FC 00. There may
+ * be more than one "RVA2" frame in each tag, but only one with the same
+ * identification string.
+ *
+ * <Header for 'Relative volume adjustment (2)', ID: "RVA2">
+ * Identification <text string> $00
+ *
+ * The 'identification' string is used to identify the situation and/or device
+ * where this adjustment should apply. The following is then repeated for every
+ * channel
+ *
+ * Type of channel $xx
+ * Volume adjustment $xx xx
+ * Bits representing peak $xx
+ * Peak volume $xx (xx ...)
+ *
+ * Type of channel: $00 Other
+ * $01 Master volume
+ * $02 Front right
+ * $03 Front left
+ * $04 Back right
+ * $05 Back left
+ * $06 Front centre
+ * $07 Back centre
+ * $08 Subwoofer
+ *
+ * Bits representing peak can be any number between 0 and 255. 0 means that there
+ * is no peak volume field. The peak volume field is always padded to whole
+ * bytes, setting the most significant bits to zero.
+ */
+static void decode_rva2(struct id3tag *id3, const char *buf, int len)
+{
+ const int rva2_min_len = 6 + 1 + 2 + 1;
+
+ int audiophile_rg = 0;
+ int channel = 0;
+ int16_t volume_adj = 0;
+ int peak_bits = 0;
+ int peak_bytes = 0;
+ int peak_shift = 0;
+ uint32_t peak = 0;
+
+ char *gain_str = NULL;
+ char *peak_str = NULL;
+
+ int i;
+
+ if (len < rva2_min_len) {
+ id3_debug("frame length %d too small\n", len);
+ return;
+ }
+
+ if (!strcasecmp(buf, "album")) {
+ audiophile_rg = 1;
+ } else if (strcasecmp(buf, "track")) {
+ id3_debug("unsupported identifier: %s\n", buf);
+ return;
+ }
+
+ buf += 6;
+
+ channel = *buf++;
+ if (channel != 0x1) {
+ id3_debug("unsupported channel: %d\n", channel);
+ return;
+ }
+
+ get_i16((unsigned char *)buf, &volume_adj);
+ buf += 2;
+
+ peak_bits = *buf++;
+
+ if (peak_bits == 0)
+ id3_debug("no peak data\n");
+
+ /*
+ * This crazy code comes from Mutagen
+ */
+ peak_bytes = min(4, (peak_bits + 7) >> 3);
+ peak_shift = ((8 - (peak_bits & 7)) & 7) + (4 - peak_bytes) * 8;
+
+ if (len < rva2_min_len + peak_bytes) {
+ id3_debug("peak data %d does not fit frame with length %d\n", peak_bytes, len);
+ return;
+ }
+
+ for (i = 0; i < peak_bytes; ++i) {
+ peak <<= 8;
+ peak |= (unsigned char)*buf++;
+ }
+
+ gain_str = xnew(char, 32);
+ snprintf(gain_str, 32, "%lf dB", volume_adj / 512.0);
+
+ add_v2(id3, audiophile_rg ? ID3_RG_ALBUM_GAIN : ID3_RG_TRACK_GAIN, gain_str);
+
+ if (peak_bytes) {
+ peak_str = xnew(char, 32);
+ snprintf(peak_str, 32, "%lf", ((double)peak * (1 << peak_shift)) / INT_MAX);
+
+ add_v2(id3, audiophile_rg ? ID3_RG_ALBUM_PEAK : ID3_RG_TRACK_PEAK, peak_str);
+ }
+
+ id3_debug("gain %s, peak %s\n", gain_str, peak_str ? peak_str : "none");
+}
+
+static void decode_ufid(struct id3tag *id3, const char *buf, int len)
+{
+ char *ufid;
+ int ufid_len = len - 22 - 1;
+
+ if (ufid_len < 0 || strcmp(buf, "http://musicbrainz.org") != 0)
+ return;
+
+ ufid = xnew(char, ufid_len + 1);
+ memcpy(ufid, buf + len - ufid_len, ufid_len);
+ ufid[ufid_len] = '\0';
+
+ id3_debug("%s: %s\n", buf, ufid);
+ add_v2(id3, ID3_MUSICBRAINZ_TRACKID, ufid);
+}
+
+
+static void v2_add_frame(struct id3tag *id3, struct v2_frame_header *fh, const char *buf)
+{
+ int encoding;
+ int len;
+ int idx;
+
+ if (!strncmp(fh->id, "RVA2", 4)) {
+ decode_rva2(id3, buf, fh->size);
+ return;
+ } else if (!strncmp(fh->id, "UFID", 4)) {
+ decode_ufid(id3, buf, fh->size);
+ return;
+ }
+
+ encoding = *buf++;
+ len = fh->size - 1;
+
+ if (encoding > ID3_ENCODING_MAX)
+ return;
+
+ idx = frame_tab_index(fh->id);
+ if (idx >= 0) {
+ decode_normal(id3, buf, len, encoding, frame_tab[idx].key);
+ } else if (!strncmp(fh->id, "TXXX", 4)) {
+ decode_txxx(id3, buf, len, encoding);
+ } else if (!strncmp(fh->id, "COMM", 4)) {
+ decode_comment(id3, buf, len, encoding);
+ } else if (!strncmp(fh->id, "COM", 3)) {
+ decode_comment(id3, buf, len, encoding);
+ }
+}
+
+static void unsync(unsigned char *buf, int *lenp)
+{
+ int len = *lenp;
+ int s, d;
+
+ s = d = 0;
+ while (s < len - 1) {
+ if (buf[s] == 0xff && buf[s + 1] == 0x00) {
+ /* 0xff 0x00 -> 0xff */
+ buf[d++] = 0xff;
+ s += 2;
+
+ if (s < len - 2 && buf[s] == 0x00) {
+ /* 0xff 0x00 0x00 -> 0xff 0x00 */
+ buf[d++] = 0x00;
+ s++;
+ }
+ continue;
+ }
+ buf[d++] = buf[s++];
+ }
+ if (s < len)
+ buf[d++] = buf[s++];
+
+ d_print("unsyncronization removed %d bytes\n", s - d);
+ *lenp = d;
+}
+
+static int v2_read(struct id3tag *id3, int fd, const struct v2_header *header)
+{
+ char *buf;
+ int rc, buf_size;
+ int frame_start, i;
+ int frame_header_size;
+
+ buf_size = header->size;
+ buf = xnew(char, buf_size);
+ rc = read_all(fd, buf, buf_size);
+ if (rc == -1) {
+ free(buf);
+ return rc;
+ }
+
+ frame_start = 0;
+ if (header->flags & V2_HEADER_EXTENDED) {
+ struct v2_extended_header ext;
+
+ if (!v2_extended_header_parse(&ext, buf) || ext.size > buf_size) {
+ id3_debug("extended header corrupted\n");
+ free(buf);
+ return -2;
+ }
+ frame_start = ext.size;
+ /* should check if update flag is set */
+ }
+
+ frame_header_size = 10;
+ if (header->ver_major == 2)
+ frame_header_size = 6;
+
+ i = frame_start;
+ while (i < buf_size - frame_header_size) {
+ struct v2_frame_header fh;
+ int len_unsync;
+
+ if (header->ver_major == 2) {
+ if (!v2_2_0_frame_header_parse(&fh, buf + i))
+ break;
+ } else if (header->ver_major == 3) {
+ if (!v2_3_0_frame_header_parse(&fh, buf + i))
+ break;
+ } else {
+ /* assume v2.4 */
+ if (!v2_4_0_frame_header_parse(&fh, buf + i))
+ break;
+ }
+
+ i += frame_header_size;
+
+ if (fh.size > buf_size - i) {
+ id3_debug("frame too big\n");
+ break;
+ }
+
+ if (fh.flags & V2_FRAME_LEN_INDICATOR) {
+ /*
+ * Ignore the frame length 4-byte field
+ */
+ i += 4;
+ fh.size -= 4;
+ }
+
+ len_unsync = fh.size;
+
+ if ((fh.flags & V2_FRAME_UNSYNC) || (header->flags & V2_HEADER_UNSYNC))
+ unsync((unsigned char *)(buf + i), (int *)&fh.size);
+
+ v2_add_frame(id3, &fh, buf + i);
+
+ i += len_unsync;
+ }
+
+ free(buf);
+ return 0;
+}
+
+int id3_tag_size(const char *buf, int buf_size)
+{
+ struct v2_header header;
+
+ if (buf_size < 10)
+ return 0;
+ if (v2_header_parse(&header, buf)) {
+ if (header.flags & V2_HEADER_FOOTER) {
+ /* header + data + footer */
+ id3_debug("v2.%d.%d with footer\n", header.ver_major, header.ver_minor);
+ return 10 + header.size + 10;
+ }
+ /* header */
+ id3_debug("v2.%d.%d\n", header.ver_major, header.ver_minor);
+ return 10 + header.size;
+ }
+ if (buf_size >= 3 && is_v1(buf)) {
+ id3_debug("v1\n");
+ return 128;
+ }
+ return 0;
+}
+
+void id3_init(struct id3tag *id3)
+{
+ const struct id3tag t = { .has_v1 = 0, .has_v2 = 0 };
+ *id3 = t;
+}
+
+void id3_free(struct id3tag *id3)
+{
+ int i;
+
+ for (i = 0; i < NUM_ID3_KEYS; i++)
+ free(id3->v2[i]);
+}
+
+int id3_read_tags(struct id3tag *id3, int fd, unsigned int flags)
+{
+ off_t off;
+ int rc;
+
+ if (flags & ID3_V2) {
+ struct v2_header header;
+ char buf[138];
+
+ rc = read_all(fd, buf, 10);
+ if (rc == -1)
+ goto rc_error;
+ if (v2_header_parse(&header, buf)) {
+ rc = v2_read(id3, fd, &header);
+ if (rc)
+ goto rc_error;
+ /* get v1 if needed */
+ } else {
+ /* get v2 from end and optionally v1 */
+
+ off = lseek(fd, -138, SEEK_END);
+ if (off == -1)
+ goto error;
+ rc = read_all(fd, buf, 138);
+ if (rc == -1)
+ goto rc_error;
+
+ if (is_v1(buf + 10)) {
+ if (flags & ID3_V1) {
+ memcpy(id3->v1, buf + 10, 128);
+ id3->has_v1 = 1;
+ }
+ if (v2_footer_parse(&header, buf)) {
+ /* footer at end of file - 128 */
+ off = lseek(fd, -((off_t) header.size + 138), SEEK_END);
+ if (off == -1)
+ goto error;
+ rc = v2_read(id3, fd, &header);
+ if (rc)
+ goto rc_error;
+ }
+ } else if (v2_footer_parse(&header, buf + 128)) {
+ /* footer at end of file */
+ off = lseek(fd, -((off_t) header.size + 10), SEEK_END);
+ if (off == -1)
+ goto error;
+ rc = v2_read(id3, fd, &header);
+ if (rc)
+ goto rc_error;
+ }
+ return 0;
+ }
+ }
+ if (flags & ID3_V1) {
+ off = lseek(fd, -128, SEEK_END);
+ if (off == -1)
+ goto error;
+ rc = read_all(fd, id3->v1, 128);
+ if (rc == -1)
+ goto rc_error;
+ id3->has_v1 = is_v1(id3->v1);
+ }
+ return 0;
+error:
+ rc = -1;
+rc_error:
+ return rc;
+}
+
+static char *v1_get_str(const char *buf, int len)
+{
+ char in[32];
+ char *out;
+ int i;
+
+ for (i = len - 1; i >= 0; i--) {
+ if (buf[i] != 0 && buf[i] != ' ')
+ break;
+ }
+ if (i == -1)
+ return NULL;
+ memcpy(in, buf, i + 1);
+ in[i + 1] = 0;
+ if (u_is_valid(in))
+ return xstrdup(in);
+ if (utf8_encode(in, id3_default_charset, &out))
+ return NULL;
+ return out;
+}
+
+char *id3_get_comment(struct id3tag *id3, enum id3_key key)
+{
+ if (id3->has_v2) {
+ if (id3->v2[key])
+ return xstrdup(id3->v2[key]);
+ }
+ if (id3->has_v1) {
+ switch (key) {
+ case ID3_ARTIST:
+ return v1_get_str(id3->v1 + 33, 30);
+ case ID3_ALBUM:
+ return v1_get_str(id3->v1 + 63, 30);
+ case ID3_TITLE:
+ return v1_get_str(id3->v1 + 3, 30);
+ case ID3_DATE:
+ return v1_get_str(id3->v1 + 93, 4);
+ case ID3_GENRE:
+ {
+ unsigned char idx = id3->v1[127];
+
+ if (idx >= NR_GENRES)
+ return NULL;
+ return xstrdup(genres[idx]);
+ }
+ case ID3_TRACK:
+ {
+ char *t;
+
+ if (id3->v1[125] != 0)
+ return NULL;
+ t = xnew(char, 4);
+ snprintf(t, 4, "%d", ((unsigned char *)id3->v1)[126]);
+ return t;
+ }
+ default:
+ return NULL;
+ }
+ }
+ return NULL;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _ID3_H
+#define _ID3_H
+
+/* flags for id3_read_tags */
+#define ID3_V1 (1 << 0)
+#define ID3_V2 (1 << 1)
+
+enum id3_key {
+ ID3_ARTIST,
+ ID3_ALBUM,
+ ID3_TITLE,
+ ID3_DATE,
+ ID3_ORIGINALDATE,
+ ID3_GENRE,
+ ID3_DISC,
+ ID3_TRACK,
+ ID3_ALBUMARTIST,
+ ID3_ARTISTSORT,
+ ID3_ALBUMARTISTSORT,
+ ID3_ALBUMSORT,
+ ID3_COMPILATION,
+ ID3_RG_TRACK_GAIN,
+ ID3_RG_TRACK_PEAK,
+ ID3_RG_ALBUM_GAIN,
+ ID3_RG_ALBUM_PEAK,
+ ID3_COMPOSER,
+ ID3_CONDUCTOR,
+ ID3_LYRICIST,
+ ID3_REMIXER,
+ ID3_LABEL,
+ ID3_PUBLISHER,
+ ID3_SUBTITLE,
+ ID3_COMMENT,
+ ID3_MUSICBRAINZ_TRACKID,
+ ID3_MEDIA,
+
+ NUM_ID3_KEYS
+};
+
+struct id3tag {
+ char v1[128];
+ char *v2[NUM_ID3_KEYS];
+
+ unsigned int has_v1 : 1;
+ unsigned int has_v2 : 1;
+};
+
+extern const char * const id3_key_names[NUM_ID3_KEYS];
+
+int id3_tag_size(const char *buf, int buf_size);
+
+void id3_init(struct id3tag *id3);
+void id3_free(struct id3tag *id3);
+
+int id3_read_tags(struct id3tag *id3, int fd, unsigned int flags);
+char *id3_get_comment(struct id3tag *id3, enum id3_key key);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "convert.h"
+#include "input.h"
+#include "ip.h"
+#include "pcm.h"
+#include "http.h"
+#include "xmalloc.h"
+#include "file.h"
+#include "path.h"
+#include "utils.h"
+#include "cmus.h"
+#include "options.h"
+#include "list.h"
+#include "mergesort.h"
+#include "misc.h"
+#include "debug.h"
+#include "ui_curses.h"
+#ifdef HAVE_CONFIG
+#include "config/libdir.h"
+#endif
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <strings.h>
+
+struct input_plugin {
+ const struct input_plugin_ops *ops;
+ struct input_plugin_data data;
+ unsigned int open : 1;
+ unsigned int eof : 1;
+ int http_code;
+ char *http_reason;
+
+ /* cached duration, -1 = unset */
+ int duration;
+ /* cached bitrate, -1 = unset */
+ long bitrate;
+ /* cached codec, NULL = unset */
+ char *codec;
+ /* cached codec_profile, NULL = unset */
+ char *codec_profile;
+
+ /*
+ * pcm is converted to 16-bit signed little-endian stereo
+ * NOTE: no conversion is done if channels > 2 or bits > 16
+ */
+ void (*pcm_convert)(char *, const char *, int);
+ void (*pcm_convert_in_place)(char *, int);
+ /*
+ * 4 if 8-bit mono
+ * 2 if 8-bit stereo or 16-bit mono
+ * 1 otherwise
+ */
+ int pcm_convert_scale;
+};
+
+struct ip {
+ struct list_head node;
+ char *name;
+ void *handle;
+
+ int priority;
+ const char * const *extensions;
+ const char * const *mime_types;
+ const struct input_plugin_ops *ops;
+ const char * const *options;
+};
+
+static const char * const plugin_dir = LIBDIR "/cmus/ip";
+static LIST_HEAD(ip_head);
+
+/* timeouts (ms) */
+static int http_connection_timeout = 5e3;
+static int http_read_timeout = 5e3;
+
+static const char *pl_mime_types[] = {
+ "audio/m3u",
+ "audio/x-scpls",
+ "audio/x-mpegurl"
+};
+
+static const struct input_plugin_ops *get_ops_by_extension(const char *ext, struct list_head **headp)
+{
+ struct list_head *node = *headp;
+
+ for (node = node->next; node != &ip_head; node = node->next) {
+ struct ip *ip = list_entry(node, struct ip, node);
+ const char * const *exts = ip->extensions;
+ int i;
+
+ for (i = 0; exts[i]; i++) {
+ if (strcasecmp(ext, exts[i]) == 0 || strcmp("*", exts[i]) == 0) {
+ *headp = node;
+ return ip->ops;
+ }
+ }
+ }
+ return NULL;
+}
+
+static const struct input_plugin_ops *get_ops_by_mime_type(const char *mime_type)
+{
+ struct ip *ip;
+
+ list_for_each_entry(ip, &ip_head, node) {
+ const char * const *types = ip->mime_types;
+ int i;
+
+ for (i = 0; types[i]; i++) {
+ if (strcasecmp(mime_type, types[i]) == 0)
+ return ip->ops;
+ }
+ }
+ return NULL;
+}
+
+static void keyvals_add_basic_auth(struct growing_keyvals *c,
+ const char *user,
+ const char *pass,
+ const char *header)
+{
+ char buf[256];
+ char *encoded;
+
+ snprintf(buf, sizeof(buf), "%s:%s", user, pass);
+ encoded = base64_encode(buf);
+ if (encoded == NULL) {
+ d_print("couldn't base64 encode '%s'\n", buf);
+ } else {
+ snprintf(buf, sizeof(buf), "Basic %s", encoded);
+ free(encoded);
+ keyvals_add(c, header, xstrdup(buf));
+ }
+}
+
+static int do_http_get(struct http_get *hg, const char *uri, int redirections)
+{
+ GROWING_KEYVALS(h);
+ int i, rc;
+ const char *val;
+ char *redirloc;
+
+ d_print("%s\n", uri);
+
+ hg->headers = NULL;
+ hg->reason = NULL;
+ hg->code = -1;
+ hg->fd = -1;
+ if (http_parse_uri(uri, &hg->uri))
+ return -IP_ERROR_INVALID_URI;
+
+ if (http_open(hg, http_connection_timeout))
+ return -IP_ERROR_ERRNO;
+
+ keyvals_add(&h, "Host", xstrdup(hg->uri.host));
+ if (hg->proxy && hg->proxy->user && hg->proxy->pass)
+ keyvals_add_basic_auth(&h, hg->proxy->user, hg->proxy->pass, "Proxy-Authorization");
+ keyvals_add(&h, "User-Agent", xstrdup("cmus/" VERSION));
+ keyvals_add(&h, "Icy-MetaData", xstrdup("1"));
+ if (hg->uri.user && hg->uri.pass)
+ keyvals_add_basic_auth(&h, hg->uri.user, hg->uri.pass, "Authorization");
+ keyvals_terminate(&h);
+
+ rc = http_get(hg, h.keyvals, http_read_timeout);
+ keyvals_free(h.keyvals);
+ switch (rc) {
+ case -1:
+ return -IP_ERROR_ERRNO;
+ case -2:
+ return -IP_ERROR_HTTP_RESPONSE;
+ }
+
+ d_print("HTTP response: %d %s\n", hg->code, hg->reason);
+ for (i = 0; hg->headers[i].key != NULL; i++)
+ d_print(" %s: %s\n", hg->headers[i].key, hg->headers[i].val);
+
+ switch (hg->code) {
+ case 200: /* OK */
+ return 0;
+ /*
+ * 3xx Codes (Redirections)
+ * unhandled: 300 Multiple Choices
+ */
+ case 301: /* Moved Permanently */
+ case 302: /* Found */
+ case 303: /* See Other */
+ case 307: /* Temporary Redirect */
+ val = keyvals_get_val(hg->headers, "location");
+ if (!val)
+ return -IP_ERROR_HTTP_RESPONSE;
+
+ redirections++;
+ if (redirections > 2)
+ return -IP_ERROR_HTTP_REDIRECT_LIMIT;
+
+ redirloc = xstrdup(val);
+ http_get_free(hg);
+ close(hg->fd);
+
+ rc = do_http_get(hg, redirloc, redirections);
+
+ free(redirloc);
+ return rc;
+ default:
+ return -IP_ERROR_HTTP_STATUS;
+ }
+}
+
+static int setup_remote(struct input_plugin *ip, const struct keyval *headers, int sock)
+{
+ const char *val;
+
+ val = keyvals_get_val(headers, "Content-Type");
+ if (val) {
+ d_print("Content-Type: %s\n", val);
+ ip->ops = get_ops_by_mime_type(val);
+ if (ip->ops == NULL) {
+ d_print("unsupported content type: %s\n", val);
+ close(sock);
+ return -IP_ERROR_FILE_FORMAT;
+ }
+ } else {
+ const char *type = "audio/mpeg";
+
+ d_print("assuming %s content type\n", type);
+ ip->ops = get_ops_by_mime_type(type);
+ if (ip->ops == NULL) {
+ d_print("unsupported content type: %s\n", type);
+ close(sock);
+ return -IP_ERROR_FILE_FORMAT;
+ }
+ }
+
+ ip->data.fd = sock;
+ ip->data.metadata = xnew(char, 16 * 255 + 1);
+
+ val = keyvals_get_val(headers, "icy-metaint");
+ if (val) {
+ long int lint;
+
+ if (str_to_int(val, &lint) == 0 && lint >= 0) {
+ ip->data.metaint = lint;
+ d_print("metaint: %d\n", ip->data.metaint);
+ }
+ }
+
+ val = keyvals_get_val(headers, "icy-name");
+ if (val)
+ ip->data.icy_name = to_utf8(val, icecast_default_charset);
+
+ val = keyvals_get_val(headers, "icy-genre");
+ if (val)
+ ip->data.icy_genre = to_utf8(val, icecast_default_charset);
+
+ val = keyvals_get_val(headers, "icy-url");
+ if (val)
+ ip->data.icy_url = to_utf8(val, icecast_default_charset);
+
+ return 0;
+}
+
+struct read_playlist_data {
+ struct input_plugin *ip;
+ int rc;
+ int count;
+};
+
+static int handle_line(void *data, const char *uri)
+{
+ struct read_playlist_data *rpd = data;
+ struct http_get hg;
+
+ rpd->count++;
+ rpd->rc = do_http_get(&hg, uri, 0);
+ if (rpd->rc) {
+ rpd->ip->http_code = hg.code;
+ rpd->ip->http_reason = hg.reason;
+ if (hg.fd >= 0)
+ close(hg.fd);
+
+ hg.reason = NULL;
+ http_get_free(&hg);
+ return 0;
+ }
+
+ rpd->rc = setup_remote(rpd->ip, hg.headers, hg.fd);
+ http_get_free(&hg);
+ return 1;
+}
+
+static int read_playlist(struct input_plugin *ip, int sock)
+{
+ struct read_playlist_data rpd = { ip, 0, 0 };
+ char *body;
+ size_t size;
+
+ body = http_read_body(sock, &size, http_read_timeout);
+ close(sock);
+ if (!body)
+ return -IP_ERROR_ERRNO;
+
+ cmus_playlist_for_each(body, size, 0, handle_line, &rpd);
+ free(body);
+ if (!rpd.count) {
+ d_print("empty playlist\n");
+ rpd.rc = -IP_ERROR_HTTP_RESPONSE;
+ }
+ return rpd.rc;
+}
+
+static int open_remote(struct input_plugin *ip)
+{
+ struct input_plugin_data *d = &ip->data;
+ struct http_get hg;
+ const char *val;
+ int rc;
+
+ rc = do_http_get(&hg, d->filename, 0);
+ if (rc) {
+ ip->http_code = hg.code;
+ ip->http_reason = hg.reason;
+ hg.reason = NULL;
+ http_get_free(&hg);
+ return rc;
+ }
+
+ val = keyvals_get_val(hg.headers, "Content-Type");
+ if (val) {
+ int i;
+
+ for (i = 0; i < N_ELEMENTS(pl_mime_types); i++) {
+ if (!strcasecmp(val, pl_mime_types[i])) {
+ d_print("Content-Type: %s\n", val);
+ http_get_free(&hg);
+ return read_playlist(ip, hg.fd);
+ }
+ }
+ }
+
+ rc = setup_remote(ip, hg.headers, hg.fd);
+ http_get_free(&hg);
+ return rc;
+}
+
+static void ip_init(struct input_plugin *ip, char *filename)
+{
+ const struct input_plugin t = {
+ .http_code = -1,
+ .pcm_convert_scale = -1,
+ .duration = -1,
+ .bitrate = -1,
+ .data = {
+ .fd = -1,
+ .filename = filename,
+ .remote = is_http_url(filename),
+ .channel_map = CHANNEL_MAP_INIT
+ }
+ };
+ *ip = t;
+}
+
+static void ip_reset(struct input_plugin *ip, int close_fd)
+{
+ int fd = ip->data.fd;
+ free(ip->data.metadata);
+ ip_init(ip, ip->data.filename);
+ if (fd != -1) {
+ if (close_fd)
+ close(fd);
+ else {
+ lseek(fd, 0, SEEK_SET);
+ ip->data.fd = fd;
+ }
+ }
+}
+
+static int open_file(struct input_plugin *ip)
+{
+ const struct input_plugin_ops *ops;
+ struct list_head *head = &ip_head;
+ const char *ext;
+ int rc = 0;
+
+ ext = get_extension(ip->data.filename);
+ if (!ext)
+ return -IP_ERROR_UNRECOGNIZED_FILE_TYPE;
+
+ ops = get_ops_by_extension(ext, &head);
+ if (!ops)
+ return -IP_ERROR_UNRECOGNIZED_FILE_TYPE;
+
+ ip->data.fd = open(ip->data.filename, O_RDONLY);
+ if (ip->data.fd == -1)
+ return -IP_ERROR_ERRNO;
+
+ while (1) {
+ ip->ops = ops;
+ rc = ip->ops->open(&ip->data);
+ if (rc != -IP_ERROR_UNSUPPORTED_FILE_TYPE)
+ break;
+
+ ops = get_ops_by_extension(ext, &head);
+ if (!ops)
+ break;
+
+ ip_reset(ip, 0);
+ d_print("fallback: try next plugin for `%s'\n", ip->data.filename);
+ }
+
+ return rc;
+}
+
+static int sort_ip(const struct list_head *a_, const struct list_head *b_)
+{
+ const struct ip *a = list_entry(a_, struct ip, node);
+ const struct ip *b = list_entry(b_, struct ip, node);
+ return b->priority - a->priority;
+}
+
+void ip_load_plugins(void)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ dir = opendir(plugin_dir);
+ if (dir == NULL) {
+ error_msg("couldn't open directory `%s': %s", plugin_dir, strerror(errno));
+ return;
+ }
+ while ((d = (struct dirent *) readdir(dir)) != NULL) {
+ char filename[256];
+ struct ip *ip;
+ void *so;
+ char *ext;
+ const int *priority_ptr;
+
+ if (d->d_name[0] == '.')
+ continue;
+ ext = strrchr(d->d_name, '.');
+ if (ext == NULL)
+ continue;
+ if (strcmp(ext, ".so"))
+ continue;
+
+ snprintf(filename, sizeof(filename), "%s/%s", plugin_dir, d->d_name);
+
+ so = dlopen(filename, RTLD_NOW);
+ if (so == NULL) {
+ d_print("%s: %s\n", filename, dlerror());
+ continue;
+ }
+
+ ip = xnew(struct ip, 1);
+
+ priority_ptr = dlsym(so, "ip_priority");
+ ip->extensions = dlsym(so, "ip_extensions");
+ ip->mime_types = dlsym(so, "ip_mime_types");
+ ip->ops = dlsym(so, "ip_ops");
+ ip->options = dlsym(so, "ip_options");
+ if (!priority_ptr || !ip->extensions || !ip->mime_types || !ip->ops || !ip->options) {
+ error_msg("%s: missing symbol", filename);
+ free(ip);
+ dlclose(so);
+ continue;
+ }
+ ip->priority = *priority_ptr;
+
+ ip->name = xstrndup(d->d_name, ext - d->d_name);
+ ip->handle = so;
+
+ list_add_tail(&ip->node, &ip_head);
+ }
+ list_mergesort(&ip_head, sort_ip);
+ closedir(dir);
+}
+
+struct input_plugin *ip_new(const char *filename)
+{
+ struct input_plugin *ip = xnew(struct input_plugin, 1);
+
+ ip_init(ip, xstrdup(filename));
+ return ip;
+}
+
+void ip_delete(struct input_plugin *ip)
+{
+ if (ip->open)
+ ip_close(ip);
+ free(ip->data.filename);
+ free(ip);
+}
+
+int ip_open(struct input_plugin *ip)
+{
+ int rc;
+
+ BUG_ON(ip->open);
+
+ /* set fd and ops, call ops->open */
+ if (ip->data.remote) {
+ rc = open_remote(ip);
+ if (rc == 0)
+ rc = ip->ops->open(&ip->data);
+ } else {
+ if (is_cdda_url(ip->data.filename)) {
+ ip->ops = get_ops_by_mime_type("x-content/audio-cdda");
+ rc = ip->ops ? ip->ops->open(&ip->data) : 1;
+ } else if (is_cue_url(ip->data.filename)) {
+ ip->ops = get_ops_by_mime_type("application/x-cue");
+ rc = ip->ops ? ip->ops->open(&ip->data) : 1;
+ } else
+ rc = open_file(ip);
+ }
+
+ if (rc) {
+ d_print("opening `%s' failed: %d %s\n", ip->data.filename, rc,
+ rc == -1 ? strerror(errno) : "");
+ ip_reset(ip, 1);
+ return rc;
+ }
+ ip->open = 1;
+ return 0;
+}
+
+void ip_setup(struct input_plugin *ip)
+{
+ unsigned int bits, is_signed, channels;
+ sample_format_t sf = ip->data.sf;
+
+ bits = sf_get_bits(sf);
+ is_signed = sf_get_signed(sf);
+ channels = sf_get_channels(sf);
+
+ ip->pcm_convert_scale = 1;
+ ip->pcm_convert = NULL;
+ ip->pcm_convert_in_place = NULL;
+
+ if (bits <= 16 && channels <= 2) {
+ unsigned int mask = ((bits >> 2) & 4) | (is_signed << 1);
+
+ ip->pcm_convert = pcm_conv[mask | (channels - 1)];
+ ip->pcm_convert_in_place = pcm_conv_in_place[mask | sf_get_bigendian(sf)];
+
+ ip->pcm_convert_scale = (3 - channels) * (3 - bits / 8);
+ }
+
+ d_print("pcm convert: scale=%d convert=%d convert_in_place=%d\n",
+ ip->pcm_convert_scale,
+ ip->pcm_convert != NULL,
+ ip->pcm_convert_in_place != NULL);
+}
+
+int ip_close(struct input_plugin *ip)
+{
+ int rc;
+
+ rc = ip->ops->close(&ip->data);
+ BUG_ON(ip->data.private);
+ if (ip->data.fd != -1)
+ close(ip->data.fd);
+ free(ip->data.metadata);
+ free(ip->data.icy_name);
+ free(ip->data.icy_genre);
+ free(ip->data.icy_url);
+ free(ip->http_reason);
+
+ ip_init(ip, ip->data.filename);
+ return rc;
+}
+
+int ip_read(struct input_plugin *ip, char *buffer, int count)
+{
+ struct timeval tv;
+ fd_set readfds;
+ /* 4608 seems to be optimal for mp3s, 4096 for oggs */
+ char tmp[8 * 1024];
+ char *buf;
+ int sample_size;
+ int rc;
+
+ BUG_ON(count <= 0);
+
+ FD_ZERO(&readfds);
+ FD_SET(ip->data.fd, &readfds);
+ /* zero timeout -> return immediately */
+ tv.tv_sec = 0;
+ tv.tv_usec = 50e3;
+ rc = select(ip->data.fd + 1, &readfds, NULL, NULL, &tv);
+ if (rc == -1) {
+ if (errno == EINTR)
+ errno = EAGAIN;
+ return -1;
+ }
+ if (rc == 0) {
+ errno = EAGAIN;
+ return -1;
+ }
+
+ buf = buffer;
+ if (ip->pcm_convert_scale > 1) {
+ /* use tmp buffer for 16-bit mono and 8-bit */
+ buf = tmp;
+ count /= ip->pcm_convert_scale;
+ if (count > sizeof(tmp))
+ count = sizeof(tmp);
+ }
+
+ rc = ip->ops->read(&ip->data, buf, count);
+ if (rc == -1 && (errno == EAGAIN || errno == EINTR)) {
+ errno = EAGAIN;
+ return -1;
+ }
+ if (rc <= 0) {
+ ip->eof = 1;
+ return rc;
+ }
+
+ BUG_ON(rc % sf_get_frame_size(ip->data.sf) != 0);
+
+ sample_size = sf_get_sample_size(ip->data.sf);
+ if (ip->pcm_convert_in_place != NULL)
+ ip->pcm_convert_in_place(buf, rc / sample_size);
+ if (ip->pcm_convert != NULL)
+ ip->pcm_convert(buffer, tmp, rc / sample_size);
+ return rc * ip->pcm_convert_scale;
+}
+
+int ip_seek(struct input_plugin *ip, double offset)
+{
+ int rc;
+
+ if (ip->data.remote)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+ rc = ip->ops->seek(&ip->data, offset);
+ if (rc == 0)
+ ip->eof = 0;
+ return rc;
+}
+
+int ip_read_comments(struct input_plugin *ip, struct keyval **comments)
+{
+ struct keyval *kv = NULL;
+ int rc;
+
+ rc = ip->ops->read_comments(&ip->data, &kv);
+
+ if (ip->data.remote) {
+ GROWING_KEYVALS(c);
+
+ if (kv) {
+ keyvals_init(&c, kv);
+ keyvals_free(kv);
+ }
+
+ if (ip->data.icy_name && !keyvals_get_val_growing(&c, "title"))
+ keyvals_add(&c, "title", xstrdup(ip->data.icy_name));
+
+ if (ip->data.icy_genre && !keyvals_get_val_growing(&c, "genre"))
+ keyvals_add(&c, "genre", xstrdup(ip->data.icy_genre));
+
+ if (ip->data.icy_url && !keyvals_get_val_growing(&c, "comment"))
+ keyvals_add(&c, "comment", xstrdup(ip->data.icy_url));
+
+ keyvals_terminate(&c);
+
+ kv = c.keyvals;
+ }
+
+ *comments = kv;
+
+ return ip->data.remote ? 0 : rc;
+}
+
+int ip_duration(struct input_plugin *ip)
+{
+ if (ip->data.remote)
+ return -1;
+ if (ip->duration == -1)
+ ip->duration = ip->ops->duration(&ip->data);
+ if (ip->duration < 0)
+ return -1;
+ return ip->duration;
+}
+
+int ip_bitrate(struct input_plugin *ip)
+{
+ if (ip->data.remote)
+ return -1;
+ if (ip->bitrate == -1)
+ ip->bitrate = ip->ops->bitrate(&ip->data);
+ if (ip->bitrate < 0)
+ return -1;
+ return ip->bitrate;
+}
+
+int ip_current_bitrate(struct input_plugin *ip)
+{
+ return ip->ops->bitrate_current(&ip->data);
+}
+
+char *ip_codec(struct input_plugin *ip)
+{
+ if (ip->data.remote)
+ return NULL;
+ if (!ip->codec)
+ ip->codec = ip->ops->codec(&ip->data);
+ return ip->codec;
+}
+
+char *ip_codec_profile(struct input_plugin *ip)
+{
+ if (ip->data.remote)
+ return NULL;
+ if (!ip->codec_profile)
+ ip->codec_profile = ip->ops->codec_profile(&ip->data);
+ return ip->codec_profile;
+}
+
+sample_format_t ip_get_sf(struct input_plugin *ip)
+{
+ BUG_ON(!ip->open);
+ return ip->data.sf;
+}
+
+void ip_get_channel_map(struct input_plugin *ip, channel_position_t *channel_map)
+{
+ BUG_ON(!ip->open);
+ channel_map_copy(channel_map, ip->data.channel_map);
+}
+
+const char *ip_get_filename(struct input_plugin *ip)
+{
+ return ip->data.filename;
+}
+
+const char *ip_get_metadata(struct input_plugin *ip)
+{
+ BUG_ON(!ip->open);
+ return ip->data.metadata;
+}
+
+int ip_is_remote(struct input_plugin *ip)
+{
+ return ip->data.remote;
+}
+
+int ip_metadata_changed(struct input_plugin *ip)
+{
+ int ret = ip->data.metadata_changed;
+
+ BUG_ON(!ip->open);
+ ip->data.metadata_changed = 0;
+ return ret;
+}
+
+int ip_eof(struct input_plugin *ip)
+{
+ BUG_ON(!ip->open);
+ return ip->eof;
+}
+
+static struct ip *find_plugin(int idx)
+{
+ struct ip *ip;
+
+ list_for_each_entry(ip, &ip_head, node) {
+ if (idx == 0)
+ return ip;
+ idx--;
+ }
+ return NULL;
+}
+
+static void option_error(int rc)
+{
+ char *msg = ip_get_error_msg(NULL, rc, "setting option");
+ error_msg("%s", msg);
+ free(msg);
+}
+
+static void set_ip_option(unsigned int id, const char *val)
+{
+ const struct ip *ip = find_plugin(id >> 16);
+ int rc;
+
+ rc = ip->ops->set_option(id & 0xffff, val);
+ if (rc)
+ option_error(rc);
+}
+
+static void get_ip_option(unsigned int id, char *buf)
+{
+ const struct ip *ip = find_plugin(id >> 16);
+ char *val = NULL;
+
+ ip->ops->get_option(id & 0xffff, &val);
+ if (val) {
+ strcpy(buf, val);
+ free(val);
+ }
+}
+
+void ip_add_options(void)
+{
+ struct ip *ip;
+ unsigned int iid, pid = 0;
+ char key[64];
+
+ list_for_each_entry(ip, &ip_head, node) {
+ for (iid = 0; ip->options[iid]; iid++) {
+ snprintf(key, sizeof(key), "input.%s.%s",
+ ip->name,
+ ip->options[iid]);
+ option_add(xstrdup(key), (pid << 16) | iid, get_ip_option, set_ip_option, NULL, 0);
+ }
+ pid++;
+ }
+}
+
+char *ip_get_error_msg(struct input_plugin *ip, int rc, const char *arg)
+{
+ char buffer[1024];
+
+ switch (-rc) {
+ case IP_ERROR_ERRNO:
+ snprintf(buffer, sizeof(buffer), "%s: %s", arg, strerror(errno));
+ break;
+ case IP_ERROR_UNRECOGNIZED_FILE_TYPE:
+ snprintf(buffer, sizeof(buffer),
+ "%s: unrecognized filename extension", arg);
+ break;
+ case IP_ERROR_UNSUPPORTED_FILE_TYPE:
+ snprintf(buffer, sizeof(buffer),
+ "%s: unsupported file format", arg);
+ break;
+ case IP_ERROR_FUNCTION_NOT_SUPPORTED:
+ snprintf(buffer, sizeof(buffer),
+ "%s: function not supported", arg);
+ break;
+ case IP_ERROR_FILE_FORMAT:
+ snprintf(buffer, sizeof(buffer),
+ "%s: file format not supported or corrupted file",
+ arg);
+ break;
+ case IP_ERROR_INVALID_URI:
+ snprintf(buffer, sizeof(buffer), "%s: invalid URI", arg);
+ break;
+ case IP_ERROR_SAMPLE_FORMAT:
+ snprintf(buffer, sizeof(buffer),
+ "%s: input plugin doesn't support the sample format",
+ arg);
+ break;
+ case IP_ERROR_WRONG_DISC:
+ snprintf(buffer, sizeof(buffer), "%s: wrong disc inserted, aborting!", arg);
+ break;
+ case IP_ERROR_NO_DISC:
+ snprintf(buffer, sizeof(buffer), "%s: could not read disc", arg);
+ break;
+ case IP_ERROR_HTTP_RESPONSE:
+ snprintf(buffer, sizeof(buffer), "%s: invalid HTTP response", arg);
+ break;
+ case IP_ERROR_HTTP_STATUS:
+ snprintf(buffer, sizeof(buffer), "%s: %d %s", arg, ip->http_code, ip->http_reason);
+ free(ip->http_reason);
+ ip->http_reason = NULL;
+ ip->http_code = -1;
+ break;
+ case IP_ERROR_HTTP_REDIRECT_LIMIT:
+ snprintf(buffer, sizeof(buffer), "%s: too many HTTP redirections", arg);
+ break;
+ case IP_ERROR_NOT_OPTION:
+ snprintf(buffer, sizeof(buffer),
+ "%s: no such option", arg);
+ break;
+ case IP_ERROR_INTERNAL:
+ snprintf(buffer, sizeof(buffer), "%s: internal error", arg);
+ break;
+ case IP_ERROR_SUCCESS:
+ default:
+ snprintf(buffer, sizeof(buffer),
+ "%s: this is not an error (%d), this is a bug",
+ arg, rc);
+ break;
+ }
+ return xstrdup(buffer);
+}
+
+char **ip_get_supported_extensions(void)
+{
+ struct ip *ip;
+ char **exts;
+ int i, size;
+ int count = 0;
+
+ size = 8;
+ exts = xnew(char *, size);
+ list_for_each_entry(ip, &ip_head, node) {
+ const char * const *e = ip->extensions;
+
+ for (i = 0; e[i]; i++) {
+ if (count == size - 1) {
+ size *= 2;
+ exts = xrenew(char *, exts, size);
+ }
+ exts[count++] = xstrdup(e[i]);
+ }
+ }
+ exts[count] = NULL;
+ qsort(exts, count, sizeof(char *), strptrcmp);
+ return exts;
+}
+
+void ip_dump_plugins(void)
+{
+ struct ip *ip;
+ int i;
+
+ printf("Input Plugins: %s\n", plugin_dir);
+ list_for_each_entry(ip, &ip_head, node) {
+ printf(" %s:\n Priority: %d\n File Types:", ip->name, ip->priority);
+ for (i = 0; ip->extensions[i]; i++)
+ printf(" %s", ip->extensions[i]);
+ printf("\n MIME Types:");
+ for (i = 0; ip->mime_types[i]; i++)
+ printf(" %s", ip->mime_types[i]);
+ printf("\n");
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _INPUT_H
+#define _INPUT_H
+
+#include "keyval.h"
+#include "sf.h"
+#include "channelmap.h"
+
+struct input_plugin;
+
+void ip_load_plugins(void);
+
+/*
+ * allocates new struct input_plugin.
+ * never fails. does not check if the file is really playable
+ */
+struct input_plugin *ip_new(const char *filename);
+
+/*
+ * frees struct input_plugin closing it first if necessary
+ */
+void ip_delete(struct input_plugin *ip);
+
+/*
+ * errors: IP_ERROR_{ERRNO, FILE_FORMAT, SAMPLE_FORMAT}
+ */
+int ip_open(struct input_plugin *ip);
+
+void ip_setup(struct input_plugin *ip);
+
+/*
+ * errors: none?
+ */
+int ip_close(struct input_plugin *ip);
+
+/*
+ * errors: IP_ERROR_{ERRNO, FILE_FORMAT}
+ */
+int ip_read(struct input_plugin *ip, char *buffer, int count);
+
+/*
+ * errors: IP_ERROR_{FUNCTION_NOT_SUPPORTED}
+ */
+int ip_seek(struct input_plugin *ip, double offset);
+
+/*
+ * errors: IP_ERROR_{ERRNO}
+ */
+int ip_read_comments(struct input_plugin *ip, struct keyval **comments);
+
+int ip_duration(struct input_plugin *ip);
+int ip_bitrate(struct input_plugin *ip);
+int ip_current_bitrate(struct input_plugin *ip);
+char *ip_codec(struct input_plugin *ip);
+char *ip_codec_profile(struct input_plugin *ip);
+
+sample_format_t ip_get_sf(struct input_plugin *ip);
+void ip_get_channel_map(struct input_plugin *ip, channel_position_t *channel_map);
+const char *ip_get_filename(struct input_plugin *ip);
+const char *ip_get_metadata(struct input_plugin *ip);
+int ip_is_remote(struct input_plugin *ip);
+int ip_metadata_changed(struct input_plugin *ip);
+int ip_eof(struct input_plugin *ip);
+void ip_add_options(void);
+char *ip_get_error_msg(struct input_plugin *ip, int rc, const char *arg);
+char **ip_get_supported_extensions(void);
+void ip_dump_plugins(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _IP_H
+#define _IP_H
+
+#include "keyval.h"
+#include "sf.h"
+#include "channelmap.h"
+
+#ifndef __GNUC__
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
+enum {
+ /* no error */
+ IP_ERROR_SUCCESS,
+ /* system error (error code in errno) */
+ IP_ERROR_ERRNO,
+ /* file type not recognized */
+ IP_ERROR_UNRECOGNIZED_FILE_TYPE,
+ /* file type recognized, but not supported */
+ IP_ERROR_UNSUPPORTED_FILE_TYPE,
+ /* function not supported (usually seek) */
+ IP_ERROR_FUNCTION_NOT_SUPPORTED,
+ /* input plugin detected corrupted file */
+ IP_ERROR_FILE_FORMAT,
+ /* malformed uri */
+ IP_ERROR_INVALID_URI,
+ /* sample format not supported */
+ IP_ERROR_SAMPLE_FORMAT,
+ /* wrong disc inserted */
+ IP_ERROR_WRONG_DISC,
+ /* could not read disc */
+ IP_ERROR_NO_DISC,
+ /* error parsing response line / headers */
+ IP_ERROR_HTTP_RESPONSE,
+ /* usually 404 */
+ IP_ERROR_HTTP_STATUS,
+ /* too many redirections */
+ IP_ERROR_HTTP_REDIRECT_LIMIT,
+ /* plugin does not have this option */
+ IP_ERROR_NOT_OPTION,
+ /* */
+ IP_ERROR_INTERNAL
+};
+
+struct input_plugin_data {
+ /* filled by ip-layer */
+ char *filename;
+ int fd;
+
+ unsigned int remote : 1;
+ unsigned int metadata_changed : 1;
+
+ /* shoutcast */
+ int counter;
+ int metaint;
+ char *metadata;
+ char *icy_name;
+ char *icy_genre;
+ char *icy_url;
+
+ /* filled by plugin */
+ sample_format_t sf;
+ channel_position_t channel_map[CHANNELS_MAX];
+ void *private;
+};
+
+struct input_plugin_ops {
+ int (*open)(struct input_plugin_data *ip_data);
+ int (*close)(struct input_plugin_data *ip_data);
+ int (*read)(struct input_plugin_data *ip_data, char *buffer, int count);
+ int (*seek)(struct input_plugin_data *ip_data, double offset);
+ int (*read_comments)(struct input_plugin_data *ip_data,
+ struct keyval **comments);
+ int (*duration)(struct input_plugin_data *ip_data);
+ long (*bitrate)(struct input_plugin_data *ip_data);
+ long (*bitrate_current)(struct input_plugin_data *ip_data);
+ char *(*codec)(struct input_plugin_data *ip_data);
+ char *(*codec_profile)(struct input_plugin_data *ip_data);
+ int (*set_option)(int key, const char *val);
+ int (*get_option)(int key, char **val);
+};
+
+/* symbols exported by plugin */
+extern const struct input_plugin_ops ip_ops;
+extern const int ip_priority;
+extern const char * const ip_extensions[];
+extern const char * const ip_mime_types[];
+extern const char * const ip_options[];
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _ITER_H
+#define _ITER_H
+
+#include <stddef.h> /* NULL */
+
+struct iter {
+ /* this usually points to the list head */
+ void *data0;
+
+ /* these point to the list item, for simple lists data2 is usually NULL */
+ void *data1;
+ void *data2;
+};
+
+static inline void iter_init(struct iter *iter)
+{
+ iter->data0 = NULL;
+ iter->data1 = NULL;
+ iter->data2 = NULL;
+}
+
+static inline void iter_head(struct iter *iter)
+{
+ iter->data1 = NULL;
+ iter->data2 = NULL;
+}
+
+static inline int iters_equal(struct iter *a, struct iter *b)
+{
+ return a->data0 == b->data0 &&
+ a->data1 == b->data1 &&
+ a->data2 == b->data2;
+}
+
+static inline int iter_is_head(struct iter *iter)
+{
+ return iter->data0 != NULL &&
+ iter->data1 == NULL &&
+ iter->data2 == NULL;
+}
+
+static inline int iter_is_null(struct iter *iter)
+{
+ return iter->data0 == NULL &&
+ iter->data1 == NULL &&
+ iter->data2 == NULL;
+}
+
+static inline int iter_is_empty(struct iter *iter)
+{
+ return iter->data0 == NULL || (iter->data1 == NULL && iter->data2 == NULL);
+}
+
+#define GENERIC_ITER_PREV(FUNC, TYPE, MEMBER) \
+int FUNC(struct iter *iter) \
+{ \
+ struct list_head *head = iter->data0; \
+ TYPE *e = iter->data1; \
+ \
+ if (head == NULL) \
+ return 0; \
+ if (e == NULL) { \
+ /* head, get last */ \
+ if (head->prev == head) { \
+ /* empty, iter points to the head already */ \
+ return 0; \
+ } \
+ iter->data1 = container_of(head->prev, TYPE, MEMBER); \
+ return 1; \
+ } \
+ if (e->MEMBER.prev == head) { \
+ iter->data1 = NULL; \
+ return 0; \
+ } \
+ iter->data1 = container_of(e->MEMBER.prev, TYPE, MEMBER); \
+ return 1; \
+}
+
+#define GENERIC_ITER_NEXT(FUNC, TYPE, MEMBER) \
+int FUNC(struct iter *iter) \
+{ \
+ struct list_head *head = iter->data0; \
+ TYPE *e = iter->data1; \
+ \
+ if (head == NULL) \
+ return 0; \
+ if (e == NULL) { \
+ /* head, get first */ \
+ if (head->next == head) { \
+ /* empty, iter points to the head already */ \
+ return 0; \
+ } \
+ iter->data1 = container_of(head->next, TYPE, MEMBER); \
+ return 1; \
+ } \
+ if (e->MEMBER.next == head) { \
+ iter->data1 = NULL; \
+ return 0; \
+ } \
+ iter->data1 = container_of(e->MEMBER.next, TYPE, MEMBER); \
+ return 1; \
+}
+
+#define GENERIC_TREE_ITER_PREV(FUNC, TYPE, MEMBER) \
+int FUNC(struct iter *iter) \
+{ \
+ struct rb_root *root = iter->data0; \
+ TYPE *e = iter->data1; \
+ \
+ if (root == NULL) \
+ return 0; \
+ if (e == NULL) { \
+ /* head, get last */ \
+ if (rb_root_empty(root)) { \
+ /* empty, iter points to the head already */ \
+ return 0; \
+ } \
+ iter->data1 = container_of(rb_last(root), TYPE, MEMBER); \
+ return 1; \
+ } \
+ if (rb_prev(&e->MEMBER) == NULL) { \
+ iter->data1 = NULL; \
+ return 0; \
+ } \
+ iter->data1 = container_of(rb_prev(&e->MEMBER), TYPE, MEMBER); \
+ return 1; \
+}
+
+#define GENERIC_TREE_ITER_NEXT(FUNC, TYPE, MEMBER) \
+int FUNC(struct iter *iter) \
+{ \
+ struct rb_root *root = iter->data0; \
+ TYPE *e = iter->data1; \
+ \
+ if (root == NULL) \
+ return 0; \
+ if (e == NULL) { \
+ /* head, get first */ \
+ if (rb_root_empty(root)) { \
+ /* empty, iter points to the head already */ \
+ return 0; \
+ } \
+ iter->data1 = container_of(rb_first(root), TYPE, MEMBER); \
+ return 1; \
+ } \
+ if (rb_next(&e->MEMBER) == NULL) { \
+ iter->data1 = NULL; \
+ return 0; \
+ } \
+ iter->data1 = container_of(rb_next(&e->MEMBER), TYPE, MEMBER); \
+ return 1; \
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2008 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "job.h"
+#include "worker.h"
+#include "cache.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "load_dir.h"
+#include "path.h"
+#include "editable.h"
+#include "pl.h"
+#include "play_queue.h"
+#include "lib.h"
+#include "utils.h"
+#include "file.h"
+#include "cache.h"
+#include "player.h"
+#include "discid.h"
+#include "xstrjoin.h"
+#ifdef HAVE_CONFIG
+#include "config/cue.h"
+#endif
+#ifdef CONFIG_CUE
+#include "cue_utils.h"
+#endif
+
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+static struct track_info *ti_buffer[32];
+static int ti_buffer_fill;
+static struct add_data *jd;
+
+static void flush_ti_buffer(void)
+{
+ int i;
+
+ editable_lock();
+ for (i = 0; i < ti_buffer_fill; i++) {
+ jd->add(ti_buffer[i]);
+ track_info_unref(ti_buffer[i]);
+ }
+ editable_unlock();
+ ti_buffer_fill = 0;
+}
+
+static void add_ti(struct track_info *ti)
+{
+ if (ti_buffer_fill == N_ELEMENTS(ti_buffer))
+ flush_ti_buffer();
+ ti_buffer[ti_buffer_fill++] = ti;
+}
+
+#ifdef CONFIG_CUE
+static int add_file_cue(const char *filename);
+#endif
+
+static void add_file(const char *filename, int force)
+{
+ struct track_info *ti;
+
+#ifdef CONFIG_CUE
+ if (!is_cue_url(filename)) {
+ if (force || lookup_cache_entry(filename, hash_str(filename)) == NULL) {
+ int done = add_file_cue(filename);
+ if (done)
+ return;
+ }
+ }
+#endif
+
+ cache_lock();
+ ti = cache_get_ti(filename, force);
+ cache_unlock();
+
+ if (ti)
+ add_ti(ti);
+}
+
+#ifdef CONFIG_CUE
+static int add_file_cue(const char *filename)
+{
+ int n_tracks;
+ char *url;
+ char *cue_filename;
+
+ cue_filename = associated_cue(filename);
+ if (cue_filename == NULL)
+ return 0;
+
+ n_tracks = cue_get_ntracks(cue_filename);
+ if (n_tracks <= 0) {
+ free(cue_filename);
+ return 0;
+ }
+
+ for (int i = 1; i <= n_tracks; ++i) {
+ url = construct_cue_url(cue_filename, i);
+ add_file(url, 0);
+ free(url);
+ }
+
+ free(cue_filename);
+ return 1;
+}
+#endif
+
+static void add_url(const char *url)
+{
+ add_file(url, 0);
+}
+
+static void add_cdda(const char *url)
+{
+ char *disc_id = NULL;
+ int start_track = 1, end_track = -1;
+
+ parse_cdda_url(url, &disc_id, &start_track, &end_track);
+
+ if (end_track != -1) {
+ int i;
+ for (i = start_track; i <= end_track; i++) {
+ char *new_url = gen_cdda_url(disc_id, i, -1);
+ add_file(new_url, 0);
+ free(new_url);
+ }
+ } else
+ add_file(url, 0);
+ free(disc_id);
+}
+
+static int dir_entry_cmp(const void *ap, const void *bp)
+{
+ struct dir_entry *a = *(struct dir_entry **)ap;
+ struct dir_entry *b = *(struct dir_entry **)bp;
+
+ return strcmp(a->name, b->name);
+}
+
+static int dir_entry_cmp_reverse(const void *ap, const void *bp)
+{
+ struct dir_entry *a = *(struct dir_entry **)ap;
+ struct dir_entry *b = *(struct dir_entry **)bp;
+
+ return strcmp(b->name, a->name);
+}
+
+static int points_within(const char *target, const char *root)
+{
+ int tlen = strlen(target);
+ int rlen = strlen(root);
+
+ if (rlen > tlen)
+ return 0;
+ if (strncmp(target, root, rlen))
+ return 0;
+ return target[rlen] == '/' || !target[rlen];
+}
+
+static void add_dir(const char *dirname, const char *root)
+{
+ struct directory dir;
+ struct dir_entry **ents;
+ const char *name;
+ PTR_ARRAY(array);
+ int i;
+
+ if (dir_open(&dir, dirname)) {
+ d_print("error: opening %s: %s\n", dirname, strerror(errno));
+ return;
+ }
+ while ((name = dir_read(&dir))) {
+ struct dir_entry *ent;
+ int size;
+
+ if (name[0] == '.')
+ continue;
+
+ if (dir.is_link) {
+ char buf[1024];
+ char *target;
+ int rc = readlink(dir.path, buf, sizeof(buf));
+
+ if (rc < 0 || rc == sizeof(buf))
+ continue;
+ buf[rc] = 0;
+ target = path_absolute_cwd(buf, dirname);
+ if (points_within(target, root)) {
+ /* symlink points withing the root */
+ d_print("%s -> %s points within %s. ignoring\n",
+ dir.path, target, root);
+ free(target);
+ continue;
+ }
+ free(target);
+ }
+
+ size = strlen(name) + 1;
+ ent = xmalloc(sizeof(struct dir_entry) + size);
+ ent->mode = dir.st.st_mode;
+ memcpy(ent->name, name, size);
+ ptr_array_add(&array, ent);
+ }
+ dir_close(&dir);
+
+ if (jd->add == play_queue_prepend) {
+ ptr_array_sort(&array, dir_entry_cmp_reverse);
+ } else {
+ ptr_array_sort(&array, dir_entry_cmp);
+ }
+ ents = array.ptrs;
+ for (i = 0; i < array.count; i++) {
+ if (!worker_cancelling()) {
+ /* abuse dir.path because
+ * - it already contains dirname + '/'
+ * - it is guaranteed to be large enough
+ */
+ int len = strlen(ents[i]->name);
+
+ memcpy(dir.path + dir.len, ents[i]->name, len + 1);
+ if (S_ISDIR(ents[i]->mode)) {
+ add_dir(dir.path, root);
+ } else {
+ add_file(dir.path, 0);
+ }
+ }
+ free(ents[i]);
+ }
+ free(ents);
+}
+
+static int handle_line(void *data, const char *line)
+{
+ if (worker_cancelling())
+ return 1;
+
+ if (is_http_url(line) || is_cue_url(line)) {
+ add_url(line);
+ } else {
+ char *absolute = path_absolute_cwd(line, data);
+ add_file(absolute, 0);
+ free(absolute);
+ }
+
+ return 0;
+}
+
+static void add_pl(const char *filename)
+{
+ char *buf;
+ int size, reverse;
+
+ buf = mmap_file(filename, &size);
+ if (size == -1)
+ return;
+
+ if (buf) {
+ char *cwd = xstrjoin(filename, "/..");
+ /* beautiful hack */
+ reverse = jd->add == play_queue_prepend;
+
+ cmus_playlist_for_each(buf, size, reverse, handle_line, cwd);
+ free(cwd);
+ munmap(buf, size);
+ }
+}
+
+void do_add_job(void *data)
+{
+ jd = data;
+ switch (jd->type) {
+ case FILE_TYPE_URL:
+ add_url(jd->name);
+ break;
+ case FILE_TYPE_CDDA:
+ add_cdda(jd->name);
+ break;
+ case FILE_TYPE_PL:
+ add_pl(jd->name);
+ break;
+ case FILE_TYPE_DIR:
+ add_dir(jd->name, jd->name);
+ break;
+ case FILE_TYPE_FILE:
+ add_file(jd->name, jd->force);
+ break;
+ case FILE_TYPE_INVALID:
+ break;
+ }
+ if (ti_buffer_fill)
+ flush_ti_buffer();
+ jd = NULL;
+}
+
+void free_add_job(void *data)
+{
+ struct add_data *d = data;
+ free(d->name);
+ free(d);
+}
+
+void do_update_job(void *data)
+{
+ struct update_data *d = data;
+ int i;
+
+ for (i = 0; i < d->used; i++) {
+ struct track_info *ti = d->ti[i];
+ struct stat s;
+ int rc;
+
+ /* stat follows symlinks, lstat does not */
+ rc = stat(ti->filename, &s);
+ if (rc || d->force || ti->mtime != s.st_mtime || ti->duration == 0) {
+ int force = ti->duration == 0;
+ editable_lock();
+ lib_remove(ti);
+ editable_unlock();
+
+ cache_lock();
+ cache_remove_ti(ti);
+ cache_unlock();
+
+ if (!is_cue_url(ti->filename) && !is_http_url(ti->filename) && rc) {
+ d_print("removing dead file %s\n", ti->filename);
+ } else {
+ if (ti->mtime != s.st_mtime)
+ d_print("mtime changed: %s\n", ti->filename);
+ cmus_add(lib_add_track, ti->filename, FILE_TYPE_FILE, JOB_TYPE_LIB, force);
+ }
+ }
+ track_info_unref(ti);
+ }
+}
+
+void free_update_job(void *data)
+{
+ struct update_data *d = data;
+
+ free(d->ti);
+ free(d);
+}
+
+void do_update_cache_job(void *data)
+{
+ struct update_cache_data *d = data;
+ struct track_info **tis;
+ int i, count;
+
+ cache_lock();
+ tis = cache_refresh(&count, d->force);
+ editable_lock();
+ player_info_lock();
+ for (i = 0; i < count; i++) {
+ struct track_info *new, *old = tis[i];
+
+ if (!old)
+ continue;
+
+ new = old->next;
+ if (lib_remove(old) && new)
+ lib_add_track(new);
+ editable_update_track(&pl_editable, old, new);
+ editable_update_track(&pq_editable, old, new);
+ if (player_info.ti == old && new) {
+ track_info_ref(new);
+ player_file_changed(new);
+ }
+
+ track_info_unref(old);
+ if (new)
+ track_info_unref(new);
+ }
+ player_info_unlock();
+ editable_unlock();
+ cache_unlock();
+ free(tis);
+}
+
+void free_update_cache_job(void *data)
+{
+ free(data);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2008 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef JOB_H
+#define JOB_H
+
+#include "cmus.h"
+
+struct add_data {
+ enum file_type type;
+ char *name;
+ add_ti_cb add;
+ unsigned int force : 1;
+};
+
+struct update_data {
+ size_t size;
+ size_t used;
+ struct track_info **ti;
+ unsigned int force : 1;
+};
+
+struct update_cache_data {
+ unsigned int force : 1;
+};
+
+void do_add_job(void *data);
+void free_add_job(void *data);
+void do_update_job(void *data);
+void free_update_job(void *data);
+void do_update_cache_job(void *data);
+void free_update_cache_job(void *data);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * keys.[ch] by Frank Terbeck <ft@bewatermyfriend.org>
+ * heavily modified by Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "keys.h"
+#include "help.h"
+#include "ui_curses.h"
+#include "command_mode.h"
+#include "xmalloc.h"
+
+#if defined(__sun__)
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+const char * const key_context_names[NR_CTXS + 1] = {
+ "browser",
+ "common",
+ "filters",
+ "library",
+ "playlist",
+ "queue",
+ "settings",
+ NULL
+};
+
+struct binding *key_bindings[NR_CTXS] = { NULL, };
+
+static const enum key_context view_to_context[] = {
+ CTX_LIBRARY,
+ CTX_LIBRARY,
+ CTX_PLAYLIST,
+ CTX_QUEUE,
+ CTX_BROWSER,
+ CTX_FILTERS,
+ CTX_SETTINGS,
+};
+
+#define KEY_IS_CHAR -255
+
+/* key_table {{{
+ *
+ * key: KEY_IS_CHAR, not a key
+ * ch: 0, not a char
+ */
+const struct key key_table[] = {
+ { "!", KEY_IS_CHAR, 33 },
+ { "\"", KEY_IS_CHAR, 34 },
+ { "#", KEY_IS_CHAR, 35 },
+ { "$", KEY_IS_CHAR, 36 },
+ { "%", KEY_IS_CHAR, 37 },
+ { "&", KEY_IS_CHAR, 38 },
+ { "'", KEY_IS_CHAR, 39 },
+ { "(", KEY_IS_CHAR, 40 },
+ { ")", KEY_IS_CHAR, 41 },
+ { "*", KEY_IS_CHAR, 42 },
+ { "+", KEY_IS_CHAR, 43 },
+ { ",", KEY_IS_CHAR, 44 },
+ { "-", KEY_IS_CHAR, 45 },
+ { ".", KEY_IS_CHAR, 46 },
+ { "/", KEY_IS_CHAR, 47 },
+ { "0", KEY_IS_CHAR, 48 },
+ { "1", KEY_IS_CHAR, 49 },
+ { "2", KEY_IS_CHAR, 50 },
+ { "3", KEY_IS_CHAR, 51 },
+ { "4", KEY_IS_CHAR, 52 },
+ { "5", KEY_IS_CHAR, 53 },
+ { "6", KEY_IS_CHAR, 54 },
+ { "7", KEY_IS_CHAR, 55 },
+ { "8", KEY_IS_CHAR, 56 },
+ { "9", KEY_IS_CHAR, 57 },
+ { ":", KEY_IS_CHAR, 58 },
+ { ";", KEY_IS_CHAR, 59 },
+ { "<", KEY_IS_CHAR, 60 },
+ { "=", KEY_IS_CHAR, 61 },
+ { ">", KEY_IS_CHAR, 62 },
+ { "?", KEY_IS_CHAR, 63 },
+ { "@", KEY_IS_CHAR, 64 },
+ { "A", KEY_IS_CHAR, 65 },
+ { "B", KEY_IS_CHAR, 66 },
+ { "C", KEY_IS_CHAR, 67 },
+ { "D", KEY_IS_CHAR, 68 },
+ { "E", KEY_IS_CHAR, 69 },
+ { "F", KEY_IS_CHAR, 70 },
+ { "F1", KEY_F(1), 0 },
+ { "F10", KEY_F(10), 0 },
+ { "F11", KEY_F(11), 0 },
+ { "F12", KEY_F(12), 0 },
+ { "F2", KEY_F(2), 0 },
+ { "F3", KEY_F(3), 0 },
+ { "F4", KEY_F(4), 0 },
+ { "F5", KEY_F(5), 0 },
+ { "F6", KEY_F(6), 0 },
+ { "F7", KEY_F(7), 0 },
+ { "F8", KEY_F(8), 0 },
+ { "F9", KEY_F(9), 0 },
+ { "G", KEY_IS_CHAR, 71 },
+ { "H", KEY_IS_CHAR, 72 },
+ { "I", KEY_IS_CHAR, 73 },
+ { "J", KEY_IS_CHAR, 74 },
+ { "K", KEY_IS_CHAR, 75 },
+ { "KP_center", KEY_B2, 0 },
+ { "KP_lower_left", KEY_C1, 0 },
+ { "KP_lower_right", KEY_C3, 0 },
+ { "KP_upper_left", KEY_A1, 0 },
+ { "KP_upper_right", KEY_A3, 0 },
+ { "L", KEY_IS_CHAR, 76 },
+ { "M", KEY_IS_CHAR, 77 },
+ { "M-!", KEY_IS_CHAR, 161 },
+ { "M-\"", KEY_IS_CHAR, 162 },
+ { "M-#", KEY_IS_CHAR, 163 },
+ { "M-$", KEY_IS_CHAR, 164 },
+ { "M-%", KEY_IS_CHAR, 165 },
+ { "M-&", KEY_IS_CHAR, 166 },
+ { "M-'", KEY_IS_CHAR, 167 },
+ { "M-(", KEY_IS_CHAR, 168 },
+ { "M-)", KEY_IS_CHAR, 169 },
+ { "M-*", KEY_IS_CHAR, 170 },
+ { "M-+", KEY_IS_CHAR, 171 },
+ { "M-,", KEY_IS_CHAR, 172 },
+ { "M--", KEY_IS_CHAR, 173 },
+ { "M-.", KEY_IS_CHAR, 174 },
+ { "M-/", KEY_IS_CHAR, 175 },
+ { "M-0", KEY_IS_CHAR, 176 },
+ { "M-1", KEY_IS_CHAR, 177 },
+ { "M-2", KEY_IS_CHAR, 178 },
+ { "M-3", KEY_IS_CHAR, 179 },
+ { "M-4", KEY_IS_CHAR, 180 },
+ { "M-5", KEY_IS_CHAR, 181 },
+ { "M-6", KEY_IS_CHAR, 182 },
+ { "M-7", KEY_IS_CHAR, 183 },
+ { "M-8", KEY_IS_CHAR, 184 },
+ { "M-9", KEY_IS_CHAR, 185 },
+ { "M-:", KEY_IS_CHAR, 186 },
+ { "M-;", KEY_IS_CHAR, 187 },
+ { "M-<", KEY_IS_CHAR, 188 },
+ { "M-=", KEY_IS_CHAR, 189 },
+ { "M->", KEY_IS_CHAR, 190 },
+ { "M-?", KEY_IS_CHAR, 191 },
+ { "M-@", KEY_IS_CHAR, 192 },
+ { "M-A", KEY_IS_CHAR, 193 },
+ { "M-B", KEY_IS_CHAR, 194 },
+ { "M-C", KEY_IS_CHAR, 195 },
+ { "M-D", KEY_IS_CHAR, 196 },
+ { "M-E", KEY_IS_CHAR, 197 },
+ { "M-F", KEY_IS_CHAR, 198 },
+ { "M-G", KEY_IS_CHAR, 199 },
+ { "M-H", KEY_IS_CHAR, 200 },
+ { "M-I", KEY_IS_CHAR, 201 },
+ { "M-J", KEY_IS_CHAR, 202 },
+ { "M-K", KEY_IS_CHAR, 203 },
+ { "M-L", KEY_IS_CHAR, 204 },
+ { "M-M", KEY_IS_CHAR, 205 },
+ { "M-N", KEY_IS_CHAR, 206 },
+ { "M-O", KEY_IS_CHAR, 207 },
+ { "M-P", KEY_IS_CHAR, 208 },
+ { "M-Q", KEY_IS_CHAR, 209 },
+ { "M-R", KEY_IS_CHAR, 210 },
+ { "M-S", KEY_IS_CHAR, 211 },
+ { "M-T", KEY_IS_CHAR, 212 },
+ { "M-U", KEY_IS_CHAR, 213 },
+ { "M-V", KEY_IS_CHAR, 214 },
+ { "M-W", KEY_IS_CHAR, 215 },
+ { "M-X", KEY_IS_CHAR, 216 },
+ { "M-Y", KEY_IS_CHAR, 217 },
+ { "M-Z", KEY_IS_CHAR, 218 },
+ { "M-[", KEY_IS_CHAR, 219 },
+ { "M-\\", KEY_IS_CHAR, 220 },
+ { "M-]", KEY_IS_CHAR, 221 },
+ { "M-^", KEY_IS_CHAR, 222 },
+ { "M-^?", KEY_IS_CHAR, 255 },
+ { "M-^@", KEY_IS_CHAR, 128 },
+ { "M-^A", KEY_IS_CHAR, 129 },
+ { "M-^B", KEY_IS_CHAR, 130 },
+ { "M-^C", KEY_IS_CHAR, 131 },
+ { "M-^D", KEY_IS_CHAR, 132 },
+ { "M-^E", KEY_IS_CHAR, 133 },
+ { "M-^F", KEY_IS_CHAR, 134 },
+ { "M-^G", KEY_IS_CHAR, 135 },
+ { "M-^H", KEY_IS_CHAR, 136 },
+ { "M-^I", KEY_IS_CHAR, 137 },
+ { "M-^J", KEY_IS_CHAR, 138 },
+ { "M-^K", KEY_IS_CHAR, 139 },
+ { "M-^L", KEY_IS_CHAR, 140 },
+ { "M-^M", KEY_IS_CHAR, 141 },
+ { "M-^N", KEY_IS_CHAR, 142 },
+ { "M-^O", KEY_IS_CHAR, 143 },
+ { "M-^P", KEY_IS_CHAR, 144 },
+ { "M-^Q", KEY_IS_CHAR, 145 },
+ { "M-^R", KEY_IS_CHAR, 146 },
+ { "M-^S", KEY_IS_CHAR, 147 },
+ { "M-^T", KEY_IS_CHAR, 148 },
+ { "M-^U", KEY_IS_CHAR, 149 },
+ { "M-^V", KEY_IS_CHAR, 150 },
+ { "M-^W", KEY_IS_CHAR, 151 },
+ { "M-^X", KEY_IS_CHAR, 152 },
+ { "M-^Y", KEY_IS_CHAR, 153 },
+ { "M-^Z", KEY_IS_CHAR, 154 },
+ { "M-^[", KEY_IS_CHAR, 155 },
+ { "M-^\\", KEY_IS_CHAR, 156 },
+ { "M-^]", KEY_IS_CHAR, 157 },
+ { "M-^^", KEY_IS_CHAR, 158 },
+ { "M-^_", KEY_IS_CHAR, 159 },
+ { "M-_", KEY_IS_CHAR, 223 },
+ { "M-`", KEY_IS_CHAR, 224 },
+ { "M-a", KEY_IS_CHAR, 225 },
+ { "M-b", KEY_IS_CHAR, 226 },
+ { "M-c", KEY_IS_CHAR, 227 },
+ { "M-d", KEY_IS_CHAR, 228 },
+ { "M-e", KEY_IS_CHAR, 229 },
+ { "M-f", KEY_IS_CHAR, 230 },
+ { "M-g", KEY_IS_CHAR, 231 },
+ { "M-h", KEY_IS_CHAR, 232 },
+ { "M-i", KEY_IS_CHAR, 233 },
+ { "M-j", KEY_IS_CHAR, 234 },
+ { "M-k", KEY_IS_CHAR, 235 },
+ { "M-l", KEY_IS_CHAR, 236 },
+ { "M-m", KEY_IS_CHAR, 237 },
+ { "M-n", KEY_IS_CHAR, 238 },
+ { "M-o", KEY_IS_CHAR, 239 },
+ { "M-p", KEY_IS_CHAR, 240 },
+ { "M-q", KEY_IS_CHAR, 241 },
+ { "M-r", KEY_IS_CHAR, 242 },
+ { "M-s", KEY_IS_CHAR, 243 },
+ { "M-space", KEY_IS_CHAR, 160 },
+ { "M-t", KEY_IS_CHAR, 244 },
+ { "M-u", KEY_IS_CHAR, 245 },
+ { "M-v", KEY_IS_CHAR, 246 },
+ { "M-w", KEY_IS_CHAR, 247 },
+ { "M-x", KEY_IS_CHAR, 248 },
+ { "M-y", KEY_IS_CHAR, 249 },
+ { "M-z", KEY_IS_CHAR, 250 },
+ { "M-{", KEY_IS_CHAR, 251 },
+ { "M-|", KEY_IS_CHAR, 252 },
+ { "M-}", KEY_IS_CHAR, 253 },
+ { "M-~", KEY_IS_CHAR, 254 },
+ { "N", KEY_IS_CHAR, 78 },
+ { "O", KEY_IS_CHAR, 79 },
+ { "P", KEY_IS_CHAR, 80 },
+ { "Q", KEY_IS_CHAR, 81 },
+ { "R", KEY_IS_CHAR, 82 },
+ { "S", KEY_IS_CHAR, 83 },
+ { "S-begin", KEY_SBEG, 0 },
+ { "S-cancel", KEY_SCANCEL, 0 },
+ { "S-command", KEY_SCOMMAND, 0 },
+ { "S-copy", KEY_SCOPY, 0 },
+ { "S-create", KEY_SCREATE, 0 },
+ { "S-del_line", KEY_SDL, 0 },
+ { "S-delete", KEY_SDC, 0 },
+ { "S-eol", KEY_SEOL, 0 },
+ { "S-exit", KEY_SEXIT, 0 },
+ { "S-find", KEY_SFIND, 0 },
+ { "S-help", KEY_SHELP, 0 },
+ { "S-home", KEY_SHOME, 0 },
+ { "S-insert", KEY_SIC, 0 },
+ { "S-left", KEY_SLEFT, 0 },
+ { "S-message", KEY_SMESSAGE, 0 },
+ { "S-move", KEY_SMOVE, 0 },
+ { "S-next", KEY_SNEXT, 0 },
+ { "S-options", KEY_SOPTIONS, 0 },
+ { "S-previous", KEY_SPREVIOUS, 0 },
+ { "S-print", KEY_SPRINT, 0 },
+ { "S-redo", KEY_SREDO, 0 },
+ { "S-replace", KEY_SREPLACE, 0 },
+ { "S-resume", KEY_SRSUME, 0 },
+ { "S-right", KEY_SRIGHT, 0 },
+ { "S-save", KEY_SSAVE, 0 },
+ { "S-suspend", KEY_SSUSPEND, 0 },
+ { "S-undo", KEY_SUNDO, 0 },
+ { "T", KEY_IS_CHAR, 84 },
+ { "U", KEY_IS_CHAR, 85 },
+ { "V", KEY_IS_CHAR, 86 },
+ { "W", KEY_IS_CHAR, 87 },
+ { "X", KEY_IS_CHAR, 88 },
+ { "Y", KEY_IS_CHAR, 89 },
+ { "Z", KEY_IS_CHAR, 90 },
+ { "[", KEY_IS_CHAR, 91 },
+ { "\\", KEY_IS_CHAR, 92 },
+ { "]", KEY_IS_CHAR, 93 },
+ { "^", KEY_IS_CHAR, 94 },
+ { "^A", KEY_IS_CHAR, 1 },
+ { "^B", KEY_IS_CHAR, 2 },
+ { "^C", KEY_IS_CHAR, 3 },
+ { "^D", KEY_IS_CHAR, 4 },
+ { "^E", KEY_IS_CHAR, 5 },
+ { "^F", KEY_IS_CHAR, 6 },
+ { "^G", KEY_IS_CHAR, 7 },
+ { "^H", KEY_IS_CHAR, 8 },
+ { "^K", KEY_IS_CHAR, 11 },
+ { "^L", KEY_IS_CHAR, 12 },
+ { "^M", KEY_IS_CHAR, 13 },
+ { "^N", KEY_IS_CHAR, 14 },
+ { "^O", KEY_IS_CHAR, 15 },
+ { "^P", KEY_IS_CHAR, 16 },
+ { "^Q", KEY_IS_CHAR, 17 },
+ { "^R", KEY_IS_CHAR, 18 },
+ { "^S", KEY_IS_CHAR, 19 },
+ { "^T", KEY_IS_CHAR, 20 },
+ { "^U", KEY_IS_CHAR, 21 },
+ { "^V", KEY_IS_CHAR, 22 },
+ { "^W", KEY_IS_CHAR, 23 },
+ { "^X", KEY_IS_CHAR, 24 },
+ { "^Y", KEY_IS_CHAR, 25 },
+ { "^Z", KEY_IS_CHAR, 26 },
+ { "^\\", KEY_IS_CHAR, 28 },
+ { "^]", KEY_IS_CHAR, 29 },
+ { "^^", KEY_IS_CHAR, 30 },
+ { "^_", KEY_IS_CHAR, 31 },
+ { "_", KEY_IS_CHAR, 95 },
+ { "`", KEY_IS_CHAR, 96 },
+ { "a", KEY_IS_CHAR, 97 },
+ { "b", KEY_IS_CHAR, 98 },
+ { "back_tab", KEY_BTAB, 0 },
+ { "backspace", KEY_BACKSPACE, 127 }, /* NOTE: both key and ch */
+ { "begin", KEY_BEG, 0 },
+ { "c", KEY_IS_CHAR, 99 },
+ { "cancel", KEY_CANCEL, 0 },
+ { "clear", KEY_CLEAR, 0 },
+ { "clear_all_tabs", KEY_CATAB, 0 },
+ { "clear_tab", KEY_CTAB, 0 },
+ { "close", KEY_CLOSE, 0 },
+ { "command", KEY_COMMAND, 0 },
+ { "copy", KEY_COPY, 0 },
+ { "create", KEY_CREATE, 0 },
+ { "d", KEY_IS_CHAR, 100 },
+ { "del_line", KEY_DL, 0 },
+ { "delete", KEY_DC, 0 },
+ { "down", KEY_DOWN, 0 },
+ { "e", KEY_IS_CHAR, 101 },
+ { "eic", KEY_EIC, 0 },
+ { "end", KEY_END, 0 },
+ { "enter", KEY_IS_CHAR, 10 },
+ { "eol", KEY_EOL, 0 },
+ { "eos", KEY_EOS, 0 },
+ { "exit", KEY_EXIT, 0 },
+ { "f", KEY_IS_CHAR, 102 },
+ { "find", KEY_FIND, 0 },
+ { "g", KEY_IS_CHAR, 103 },
+ { "h", KEY_IS_CHAR, 104 },
+ { "help", KEY_HELP, 0 },
+ { "home", KEY_HOME, 0 },
+ { "i", KEY_IS_CHAR, 105 },
+ { "ins_line", KEY_IL, 0 },
+ { "insert", KEY_IC, 0 },
+ { "j", KEY_IS_CHAR, 106 },
+ { "k", KEY_IS_CHAR, 107 },
+ { "l", KEY_IS_CHAR, 108 },
+ { "left", KEY_LEFT, 0 },
+ { "lower_left", KEY_LL, 0 },
+ { "m", KEY_IS_CHAR, 109 },
+ { "mark", KEY_MARK, 0 },
+ { "message", KEY_MESSAGE, 0 },
+ { "move", KEY_MOVE, 0 },
+ { "n", KEY_IS_CHAR, 110 },
+ { "next", KEY_NEXT, 0 },
+ { "o", KEY_IS_CHAR, 111 },
+ { "open", KEY_OPEN, 0 },
+ { "options", KEY_OPTIONS, 0 },
+ { "p", KEY_IS_CHAR, 112 },
+ { "page_down", KEY_NPAGE, 0 },
+ { "page_up", KEY_PPAGE, 0 },
+ { "previous", KEY_PREVIOUS, 0 },
+ { "print", KEY_PRINT, 0 },
+ { "q", KEY_IS_CHAR, 113 },
+ { "r", KEY_IS_CHAR, 114 },
+ { "redo", KEY_REDO, 0 },
+ { "reference", KEY_REFERENCE, 0 },
+ { "refresh", KEY_REFRESH, 0 },
+ { "replace", KEY_REPLACE, 0 },
+ { "restart", KEY_RESTART, 0 },
+ { "resume", KEY_RESUME, 0 },
+ { "right", KEY_RIGHT, 0 },
+ { "s", KEY_IS_CHAR, 115 },
+ { "save", KEY_SAVE, 0 },
+ { "scroll_b", KEY_SR, 0 },
+ { "scroll_f", KEY_SF, 0 },
+ { "select", KEY_SELECT, 0 },
+ { "send", KEY_SEND, 0 },
+ { "set_tab", KEY_STAB, 0 },
+ { "space", KEY_IS_CHAR, 32 },
+ { "suspend", KEY_SUSPEND, 0 },
+ { "t", KEY_IS_CHAR, 116 },
+ { "tab", KEY_IS_CHAR, 9 },
+ { "u", KEY_IS_CHAR, 117 },
+ { "undo", KEY_UNDO, 0 },
+ { "up", KEY_UP, 0 },
+ { "v", KEY_IS_CHAR, 118 },
+ { "w", KEY_IS_CHAR, 119 },
+ { "x", KEY_IS_CHAR, 120 },
+ { "y", KEY_IS_CHAR, 121 },
+ { "z", KEY_IS_CHAR, 122 },
+ { "{", KEY_IS_CHAR, 123 },
+ { "|", KEY_IS_CHAR, 124 },
+ { "}", KEY_IS_CHAR, 125 },
+ { "~", KEY_IS_CHAR, 126 },
+ { NULL, 0, 0 }
+};
+/* }}} */
+
+static int find_context(const char *name)
+{
+ int i;
+
+ for (i = 0; i < NR_CTXS; i++) {
+ if (strcmp(name, key_context_names[i]) == 0)
+ return i;
+ }
+ error_msg("invalid context '%s'", name);
+ return -1;
+}
+
+static const struct key *find_key(const char *name)
+{
+ int i;
+
+ for (i = 0; key_table[i].name; i++) {
+ if (strcmp(name, key_table[i].name) == 0)
+ return &key_table[i];
+ }
+ error_msg("invalid key '%s'", name);
+ return NULL;
+}
+
+static struct binding *find_binding(enum key_context c, const struct key *k)
+{
+ struct binding *b = key_bindings[c];
+
+ while (b) {
+ if (b->key == k)
+ break;
+ b = b->next;
+ }
+ return b;
+}
+
+void show_binding(const char *context, const char *key)
+{
+ const struct key *k;
+ const struct binding *b;
+ int c;
+
+ c = find_context(context);
+ if (c < 0)
+ return;
+
+ k = find_key(key);
+ if (k == NULL)
+ return;
+
+ b = find_binding(c, k);
+ if (b == NULL) {
+ info_msg("No such binding");
+ } else {
+ info_msg("bind %s %s %s", context, key, b->cmd);
+ }
+}
+
+int key_bind(const char *context, const char *key, const char *cmd, int force)
+{
+ const struct key *k;
+ struct binding *b, *ptr, *prev;
+ struct command *command;
+ int c, size;
+
+ c = find_context(context);
+ if (c < 0)
+ return -1;
+
+ k = find_key(key);
+ if (k == NULL)
+ return -1;
+
+ /* check if already bound */
+ b = find_binding(c, k);
+ if (b) {
+ if (!force)
+ goto bound;
+ key_unbind(context, key, 0);
+ }
+
+ if (*cmd == ':')
+ cmd++;
+ size = strlen(cmd) + 1;
+
+ b = xmalloc(sizeof(struct binding) + size);
+ b->key = k;
+ b->ctx = c;
+ memcpy(b->cmd, cmd, size);
+
+ /* insert keeping sorted by key */
+ prev = NULL;
+ ptr = key_bindings[c];
+ while (ptr) {
+ if (strcmp(b->key->name, ptr->key->name) < 0)
+ break;
+ prev = ptr;
+ ptr = ptr->next;
+ }
+ b->next = ptr;
+ if (prev) {
+ prev->next = b;
+ } else {
+ key_bindings[c] = b;
+ }
+ command = get_command(cmd);
+ if (command && !command->bc++)
+ help_remove_unbound(command);
+ help_add_bound(b);
+ return 0;
+bound:
+ error_msg("key %s already bound in context %s", key, key_context_names[c]);
+ return -1;
+}
+
+int key_unbind(const char *context, const char *key, int force)
+{
+ int c;
+ const struct key *k;
+ struct binding *b, *prev;
+ struct command *command;
+
+ c = find_context(context);
+ if (c < 0)
+ return -1;
+
+ k = find_key(key);
+ if (k == NULL)
+ return -1;
+
+ prev = NULL;
+ b = key_bindings[c];
+ while (b) {
+ if (b->key == k) {
+ if (prev) {
+ prev->next = b->next;
+ } else {
+ key_bindings[c] = b->next;
+ }
+ command = get_command(b->cmd);
+ if (command && !--command->bc)
+ help_add_unbound(command);
+ help_remove_bound(b);
+ free(b);
+ return 0;
+ }
+ prev = b;
+ b = b->next;
+ }
+ if (!force) {
+ error_msg("key %s not bound in context %s", key, context);
+ return -1;
+ }
+ return 0;
+}
+
+static int handle_key(const struct binding *b, const struct key *k)
+{
+ while (b) {
+ if (b->key == k) {
+ run_command(b->cmd);
+ return 1;
+ }
+ b = b->next;
+ }
+ return 0;
+}
+
+static const struct key *ch_to_key(uchar ch)
+{
+ int i;
+
+ for (i = 0; key_table[i].name; i++) {
+ if (key_table[i].ch == ch)
+ return &key_table[i];
+ }
+ return NULL;
+}
+
+static const struct key *keycode_to_key(int key)
+{
+ int i;
+
+ for (i = 0; key_table[i].name; i++) {
+ if (key_table[i].key != KEY_IS_CHAR && key_table[i].key == key)
+ return &key_table[i];
+ }
+ return NULL;
+}
+
+void normal_mode_ch(uchar ch)
+{
+ enum key_context c;
+ const struct key *k;
+
+ c = view_to_context[cur_view];
+ k = ch_to_key(ch);
+
+ if (k == NULL) {
+ return;
+ }
+
+ /* view-specific ch */
+ if (handle_key(key_bindings[c], k))
+ return;
+
+ /* common ch */
+ if (handle_key(key_bindings[CTX_COMMON], k))
+ return;
+
+ /* these can be overridden but have default magic */
+ switch (ch) {
+ case ':':
+ enter_command_mode();
+ return;
+ case '/':
+ enter_search_mode();
+ return;
+ case '?':
+ enter_search_backward_mode();
+ return;
+ }
+}
+
+void normal_mode_key(int key)
+{
+ enum key_context c = view_to_context[cur_view];
+ const struct key *k = keycode_to_key(key);
+
+ if (k == NULL) {
+ return;
+ }
+
+ /* view-specific key */
+ if (handle_key(key_bindings[c], k))
+ return;
+
+ /* common key */
+ handle_key(key_bindings[CTX_COMMON], k);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * keys.[ch] by Frank Terbeck <ft@bewatermyfriend.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEYS_H
+#define KEYS_H
+
+#include "uchar.h"
+
+enum key_context {
+ CTX_BROWSER,
+ CTX_COMMON,
+ CTX_FILTERS,
+ CTX_LIBRARY,
+ CTX_PLAYLIST,
+ CTX_QUEUE,
+ CTX_SETTINGS,
+};
+#define NR_CTXS (CTX_SETTINGS + 1)
+
+struct key {
+ const char *name;
+ int key;
+ uchar ch;
+};
+
+struct binding {
+ struct binding *next;
+ const struct key *key;
+ enum key_context ctx;
+ char cmd[];
+};
+
+extern const char * const key_context_names[NR_CTXS + 1];
+extern const struct key key_table[];
+extern struct binding *key_bindings[NR_CTXS];
+
+void show_binding(const char *context, const char *key);
+int key_bind(const char *context, const char *key, const char *cmd, int force);
+int key_unbind(const char *context, const char *key, int force);
+
+void normal_mode_ch(uchar ch);
+void normal_mode_key(int key);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2008 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "debug.h"
+#include "keyval.h"
+#include "xmalloc.h"
+
+#include <strings.h>
+
+struct keyval *keyvals_new(int num)
+{
+ struct keyval *c = xnew(struct keyval, num + 1);
+ int i;
+
+ for (i = 0; i <= num; i++) {
+ c[i].key = NULL;
+ c[i].val = NULL;
+ }
+ return c;
+}
+
+struct keyval *keyvals_dup(const struct keyval *keyvals)
+{
+ struct keyval *c;
+ int i;
+
+ for (i = 0; keyvals[i].key; i++)
+ ; /* nothing */
+ c = xnew(struct keyval, i + 1);
+ for (i = 0; keyvals[i].key; i++) {
+ c[i].key = xstrdup(keyvals[i].key);
+ c[i].val = xstrdup(keyvals[i].val);
+ }
+ c[i].key = NULL;
+ c[i].val = NULL;
+ return c;
+}
+
+void keyvals_free(struct keyval *keyvals)
+{
+ int i;
+
+ for (i = 0; keyvals[i].key; i++) {
+ free(keyvals[i].key);
+ free(keyvals[i].val);
+ }
+ free(keyvals);
+}
+
+const char *keyvals_get_val(const struct keyval *keyvals, const char *key)
+{
+ int i;
+
+ for (i = 0; keyvals[i].key; i++) {
+ if (strcasecmp(keyvals[i].key, key) == 0)
+ return keyvals[i].val;
+ }
+ return NULL;
+}
+
+void keyvals_init(struct growing_keyvals *c, const struct keyval *keyvals)
+{
+ int i;
+
+ BUG_ON(c->keyvals);
+
+ for (i = 0; keyvals[i].key; i++)
+ ; /* nothing */
+
+ c->keyvals = keyvals_dup(keyvals);
+ c->alloc = i;
+ c->count = i;
+}
+
+void keyvals_add(struct growing_keyvals *c, const char *key, char *val)
+{
+ int n = c->count + 1;
+
+ if (n > c->alloc) {
+ n = (n + 3) & ~3;
+ c->keyvals = xrenew(struct keyval, c->keyvals, n);
+ c->alloc = n;
+ }
+
+ c->keyvals[c->count].key = xstrdup(key);
+ c->keyvals[c->count].val = val;
+ c->count++;
+}
+
+const char *keyvals_get_val_growing(const struct growing_keyvals *c, const char *key)
+{
+ int i;
+
+ for (i = 0; i < c->count; ++i)
+ if (strcasecmp(c->keyvals[i].key, key) == 0)
+ return c->keyvals[i].val;
+
+ return NULL;
+}
+
+void keyvals_terminate(struct growing_keyvals *c)
+{
+ int alloc = c->count + 1;
+
+ if (alloc > c->alloc) {
+ c->keyvals = xrenew(struct keyval, c->keyvals, alloc);
+ c->alloc = alloc;
+ }
+ c->keyvals[c->count].key = NULL;
+ c->keyvals[c->count].val = NULL;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2008 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef KEYVAL_H
+#define KEYVAL_H
+
+struct keyval {
+ char *key;
+ char *val;
+};
+
+struct growing_keyvals {
+ struct keyval *keyvals;
+ int alloc;
+ int count;
+};
+
+#define GROWING_KEYVALS(name) struct growing_keyvals name = { NULL, 0, 0 }
+
+struct keyval *keyvals_new(int num);
+void keyvals_init(struct growing_keyvals *c, const struct keyval *keyvals);
+void keyvals_add(struct growing_keyvals *c, const char *key, char *val);
+const char *keyvals_get_val_growing(const struct growing_keyvals *c, const char *key);
+void keyvals_terminate(struct growing_keyvals *c);
+void keyvals_free(struct keyval *keyvals);
+struct keyval *keyvals_dup(const struct keyval *keyvals);
+const char *keyvals_get_val(const struct keyval *keyvals, const char *key);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "lib.h"
+#include "editable.h"
+#include "track_info.h"
+#include "options.h"
+#include "xmalloc.h"
+#include "rbtree.h"
+#include "debug.h"
+#include "utils.h"
+#include "ui_curses.h" /* cur_view */
+
+#include <pthread.h>
+#include <string.h>
+
+struct editable lib_editable;
+struct tree_track *lib_cur_track = NULL;
+unsigned int play_sorted = 0;
+enum aaa_mode aaa_mode = AAA_MODE_ALL;
+/* used in ui_curses.c for status display */
+char *lib_live_filter = NULL;
+
+static struct rb_root lib_shuffle_root;
+static struct expr *filter = NULL;
+static int remove_from_hash = 1;
+
+static struct expr *live_filter_expr = NULL;
+static struct track_info *cur_track_ti = NULL;
+static struct track_info *sel_track_ti = NULL;
+
+const char *artist_sort_name(const struct artist *a)
+{
+ if (a->sort_name)
+ return a->sort_name;
+
+ if (smart_artist_sort && a->auto_sort_name)
+ return a->auto_sort_name;
+
+ return a->name;
+}
+
+static inline struct tree_track *to_sorted(const struct list_head *item)
+{
+ return (struct tree_track *)container_of(item, struct simple_track, node);
+}
+
+static inline void sorted_track_to_iter(struct tree_track *track, struct iter *iter)
+{
+ iter->data0 = &lib_editable.head;
+ iter->data1 = track;
+ iter->data2 = NULL;
+}
+
+static void all_wins_changed(void)
+{
+ lib_tree_win->changed = 1;
+ lib_track_win->changed = 1;
+ lib_editable.win->changed = 1;
+}
+
+static void shuffle_add(struct tree_track *track)
+{
+ shuffle_list_add(&track->shuffle_track, &lib_shuffle_root);
+}
+
+static void views_add_track(struct track_info *ti)
+{
+ struct tree_track *track = xnew(struct tree_track, 1);
+
+ /* NOTE: does not ref ti */
+ simple_track_init((struct simple_track *)track, ti);
+
+ /* both the hash table and views have refs */
+ track_info_ref(ti);
+
+ tree_add_track(track);
+ shuffle_add(track);
+ editable_add(&lib_editable, (struct simple_track *)track);
+}
+
+struct fh_entry {
+ struct fh_entry *next;
+
+ /* ref count is increased when added to this hash */
+ struct track_info *ti;
+};
+
+#define FH_SIZE (1024)
+static struct fh_entry *ti_hash[FH_SIZE] = { NULL, };
+
+static int hash_insert(struct track_info *ti)
+{
+ const char *filename = ti->filename;
+ unsigned int pos = hash_str(filename) % FH_SIZE;
+ struct fh_entry **entryp;
+ struct fh_entry *e;
+
+ entryp = &ti_hash[pos];
+ e = *entryp;
+ while (e) {
+ if (strcmp(e->ti->filename, filename) == 0) {
+ /* found, don't insert */
+ return 0;
+ }
+ e = e->next;
+ }
+
+ e = xnew(struct fh_entry, 1);
+ track_info_ref(ti);
+ e->ti = ti;
+ e->next = *entryp;
+ *entryp = e;
+ return 1;
+}
+
+static void hash_remove(struct track_info *ti)
+{
+ const char *filename = ti->filename;
+ unsigned int pos = hash_str(filename) % FH_SIZE;
+ struct fh_entry **entryp;
+
+ entryp = &ti_hash[pos];
+ while (1) {
+ struct fh_entry *e = *entryp;
+
+ BUG_ON(e == NULL);
+ if (strcmp(e->ti->filename, filename) == 0) {
+ *entryp = e->next;
+ track_info_unref(e->ti);
+ free(e);
+ break;
+ }
+ entryp = &e->next;
+ }
+}
+
+static int is_filtered(struct track_info *ti)
+{
+ if (live_filter_expr && !expr_eval(live_filter_expr, ti))
+ return 1;
+ if (!live_filter_expr && lib_live_filter && !track_info_matches(ti, lib_live_filter, TI_MATCH_ALL))
+ return 1;
+ if (filter && !expr_eval(filter, ti))
+ return 1;
+ return 0;
+}
+
+void lib_add_track(struct track_info *ti)
+{
+ if (!hash_insert(ti)) {
+ /* duplicate files not allowed */
+ return;
+ }
+ if (!is_filtered(ti))
+ views_add_track(ti);
+}
+
+static struct tree_track *album_first_track(const struct album *album)
+{
+ return to_tree_track(rb_first(&album->track_root));
+}
+
+static struct tree_track *artist_first_track(const struct artist *artist)
+{
+ return album_first_track(to_album(rb_first(&artist->album_root)));
+}
+
+static struct tree_track *normal_get_first(void)
+{
+ return artist_first_track(to_artist(rb_first(&lib_artist_root)));
+}
+
+static struct tree_track *album_last_track(const struct album *album)
+{
+ return to_tree_track(rb_last(&album->track_root));
+}
+
+static struct tree_track *artist_last_track(const struct artist *artist)
+{
+ return album_last_track(to_album(rb_last(&artist->album_root)));
+}
+
+static int aaa_mode_filter(const struct simple_track *track)
+{
+ const struct album *album = ((struct tree_track *)track)->album;
+
+ if (aaa_mode == AAA_MODE_ALBUM)
+ return CUR_ALBUM == album;
+
+ if (aaa_mode == AAA_MODE_ARTIST)
+ return CUR_ARTIST == album->artist;
+
+ /* AAA_MODE_ALL */
+ return 1;
+}
+
+/* set next/prev (tree) {{{ */
+
+static struct tree_track *normal_get_next(void)
+{
+ if (lib_cur_track == NULL)
+ return normal_get_first();
+
+ /* not last track of the album? */
+ if (rb_next(&lib_cur_track->tree_node)) {
+ /* next track of the current album */
+ return to_tree_track(rb_next(&lib_cur_track->tree_node));
+ }
+
+ if (aaa_mode == AAA_MODE_ALBUM) {
+ if (!repeat)
+ return NULL;
+ /* first track of the current album */
+ return album_first_track(CUR_ALBUM);
+ }
+
+ /* not last album of the artist? */
+ if (rb_next(&CUR_ALBUM->tree_node) != NULL) {
+ /* first track of the next album */
+ return album_first_track(to_album(rb_next(&CUR_ALBUM->tree_node)));
+ }
+
+ if (aaa_mode == AAA_MODE_ARTIST) {
+ if (!repeat)
+ return NULL;
+ /* first track of the first album of the current artist */
+ return artist_first_track(CUR_ARTIST);
+ }
+
+ /* not last artist of the library? */
+ if (rb_next(&CUR_ARTIST->tree_node) != NULL) {
+ /* first track of the next artist */
+ return artist_first_track(to_artist(rb_next(&CUR_ARTIST->tree_node)));
+ }
+
+ if (!repeat)
+ return NULL;
+
+ /* first track */
+ return normal_get_first();
+}
+
+static struct tree_track *normal_get_prev(void)
+{
+ if (lib_cur_track == NULL)
+ return normal_get_first();
+
+ /* not first track of the album? */
+ if (rb_prev(&lib_cur_track->tree_node)) {
+ /* prev track of the album */
+ return to_tree_track(rb_prev(&lib_cur_track->tree_node));
+ }
+
+ if (aaa_mode == AAA_MODE_ALBUM) {
+ if (!repeat)
+ return NULL;
+ /* last track of the album */
+ return to_tree_track(rb_prev(&CUR_ALBUM->tree_node));
+ }
+
+ /* not first album of the artist? */
+ if (rb_prev(&CUR_ALBUM->tree_node) != NULL) {
+ /* last track of the prev album of the artist */
+ return album_last_track(to_album(rb_prev(&CUR_ALBUM->tree_node)));
+ }
+
+ if (aaa_mode == AAA_MODE_ARTIST) {
+ if (!repeat)
+ return NULL;
+ /* last track of the last album of the artist */
+ return album_last_track(to_album(rb_last(&CUR_ARTIST->album_root)));
+ }
+
+ /* not first artist of the library? */
+ if (rb_prev(&CUR_ARTIST->tree_node) != NULL) {
+ /* last track of the last album of the prev artist */
+ return artist_last_track(to_artist(rb_prev(&CUR_ARTIST->tree_node)));
+ }
+
+ if (!repeat)
+ return NULL;
+
+ /* last track */
+ return artist_last_track(to_artist(rb_last(&lib_artist_root)));
+}
+
+/* set next/prev (tree) }}} */
+
+void lib_reshuffle(void)
+{
+ shuffle_list_reshuffle(&lib_shuffle_root);
+}
+
+static void free_lib_track(struct list_head *item)
+{
+ struct tree_track *track = (struct tree_track *)to_simple_track(item);
+ struct track_info *ti = tree_track_info(track);
+
+ if (track == lib_cur_track)
+ lib_cur_track = NULL;
+
+ if (remove_from_hash)
+ hash_remove(ti);
+
+ rb_erase(&track->shuffle_track.tree_node, &lib_shuffle_root);
+ tree_remove(track);
+
+ track_info_unref(ti);
+ free(track);
+}
+
+void lib_init(void)
+{
+ editable_init(&lib_editable, free_lib_track);
+ tree_init();
+ srand(time(NULL));
+}
+
+struct track_info *lib_set_track(struct tree_track *track)
+{
+ struct track_info *ti = NULL;
+
+ if (track) {
+ lib_cur_track = track;
+ ti = tree_track_info(track);
+ track_info_ref(ti);
+ all_wins_changed();
+ }
+ return ti;
+}
+
+struct track_info *lib_set_next(void)
+{
+ struct tree_track *track;
+
+ if (rb_root_empty(&lib_artist_root)) {
+ BUG_ON(lib_cur_track != NULL);
+ return NULL;
+ }
+ if (shuffle) {
+ track = (struct tree_track *)shuffle_list_get_next(&lib_shuffle_root,
+ (struct shuffle_track *)lib_cur_track, aaa_mode_filter);
+ } else if (play_sorted) {
+ track = (struct tree_track *)simple_list_get_next(&lib_editable.head,
+ (struct simple_track *)lib_cur_track, aaa_mode_filter);
+ } else {
+ track = normal_get_next();
+ }
+ return lib_set_track(track);
+}
+
+struct track_info *lib_set_prev(void)
+{
+ struct tree_track *track;
+
+ if (rb_root_empty(&lib_artist_root)) {
+ BUG_ON(lib_cur_track != NULL);
+ return NULL;
+ }
+ if (shuffle) {
+ track = (struct tree_track *)shuffle_list_get_prev(&lib_shuffle_root,
+ (struct shuffle_track *)lib_cur_track, aaa_mode_filter);
+ } else if (play_sorted) {
+ track = (struct tree_track *)simple_list_get_prev(&lib_editable.head,
+ (struct simple_track *)lib_cur_track, aaa_mode_filter);
+ } else {
+ track = normal_get_prev();
+ }
+ return lib_set_track(track);
+}
+
+static struct tree_track *sorted_get_selected(void)
+{
+ struct iter sel;
+
+ if (list_empty(&lib_editable.head))
+ return NULL;
+
+ window_get_sel(lib_editable.win, &sel);
+ return iter_to_sorted_track(&sel);
+}
+
+struct track_info *sorted_set_selected(void)
+{
+ return lib_set_track(sorted_get_selected());
+}
+
+static void hash_add_to_views(void)
+{
+ int i;
+ for (i = 0; i < FH_SIZE; i++) {
+ struct fh_entry *e;
+
+ e = ti_hash[i];
+ while (e) {
+ struct track_info *ti = e->ti;
+
+ if (!is_filtered(ti))
+ views_add_track(ti);
+ e = e->next;
+ }
+ }
+}
+
+struct tree_track *lib_find_track(struct track_info *ti)
+{
+ struct simple_track *track;
+
+ list_for_each_entry(track, &lib_editable.head, node) {
+ if (strcmp(track->info->filename, ti->filename) == 0) {
+ struct tree_track *tt = (struct tree_track *)track;
+ return tt;
+ }
+ }
+ return NULL;
+}
+
+void lib_store_cur_track(struct track_info *ti)
+{
+ if (cur_track_ti)
+ track_info_unref(cur_track_ti);
+ cur_track_ti = ti;
+ track_info_ref(cur_track_ti);
+}
+
+struct track_info *lib_get_cur_stored_track(void)
+{
+ if (cur_track_ti && lib_find_track(cur_track_ti))
+ return cur_track_ti;
+ return NULL;
+}
+
+static void restore_cur_track(struct track_info *ti)
+{
+ struct tree_track *tt = lib_find_track(ti);
+ if (tt)
+ lib_cur_track = tt;
+}
+
+static int is_filtered_cb(void *data, struct track_info *ti)
+{
+ return is_filtered(ti);
+}
+
+static void do_lib_filter(int clear_before)
+{
+ /* try to save cur_track */
+ if (lib_cur_track)
+ lib_store_cur_track(tree_track_info(lib_cur_track));
+
+ if (clear_before)
+ d_print("filter results could grow, clear tracks and re-add (slow)\n");
+
+ remove_from_hash = 0;
+ if (clear_before) {
+ editable_clear(&lib_editable);
+ hash_add_to_views();
+ } else
+ editable_remove_matching_tracks(&lib_editable, is_filtered_cb, NULL);
+ remove_from_hash = 1;
+
+ window_changed(lib_editable.win);
+ window_goto_top(lib_editable.win);
+ lib_cur_win = lib_tree_win;
+ window_goto_top(lib_tree_win);
+
+ /* restore cur_track */
+ if (cur_track_ti && !lib_cur_track)
+ restore_cur_track(cur_track_ti);
+}
+
+static void unset_live_filter(void)
+{
+ free(lib_live_filter);
+ lib_live_filter = NULL;
+ free(live_filter_expr);
+ live_filter_expr = NULL;
+}
+
+void lib_set_filter(struct expr *expr)
+{
+ int clear_before = lib_live_filter || filter;
+ unset_live_filter();
+ if (filter)
+ expr_free(filter);
+ filter = expr;
+ do_lib_filter(clear_before);
+}
+
+static struct tree_track *get_sel_track(void)
+{
+ switch (cur_view) {
+ case TREE_VIEW:
+ return tree_get_selected();
+ case SORTED_VIEW:
+ return sorted_get_selected();
+ }
+ return NULL;
+}
+
+static void set_sel_track(struct tree_track *tt)
+{
+ struct iter iter;
+
+ switch (cur_view) {
+ case TREE_VIEW:
+ tree_sel_track(tt);
+ break;
+ case SORTED_VIEW:
+ sorted_track_to_iter(tt, &iter);
+ window_set_sel(lib_editable.win, &iter);
+ break;
+ }
+}
+
+static void store_sel_track(void)
+{
+ struct tree_track *tt = get_sel_track();
+ if (tt) {
+ sel_track_ti = tree_track_info(tt);
+ track_info_ref(sel_track_ti);
+ }
+}
+
+static void restore_sel_track(void)
+{
+ if (sel_track_ti) {
+ struct tree_track *tt = lib_find_track(sel_track_ti);
+ if (tt) {
+ set_sel_track(tt);
+ track_info_unref(sel_track_ti);
+ sel_track_ti = NULL;
+ }
+ }
+}
+
+/* determine if filter results could grow, in which case all tracks must be cleared and re-added */
+static int do_clear_before(const char *str, struct expr *expr)
+{
+ if (!lib_live_filter)
+ return 0;
+ if (!str)
+ return 1;
+ if ((!expr && live_filter_expr) || (expr && !live_filter_expr))
+ return 1;
+ if (!expr || expr_is_harmless(expr))
+ return !strstr(str, lib_live_filter);
+ return 1;
+}
+
+void lib_set_live_filter(const char *str)
+{
+ int clear_before;
+ struct expr *expr = NULL;
+
+ if (strcmp0(str, lib_live_filter) == 0)
+ return;
+
+ if (str && expr_is_short(str)) {
+ expr = expr_parse(str);
+ if (!expr)
+ return;
+ }
+
+ clear_before = do_clear_before(str, expr);
+
+ if (!str)
+ store_sel_track();
+
+ unset_live_filter();
+ lib_live_filter = str ? xstrdup(str) : NULL;
+ live_filter_expr = expr;
+ do_lib_filter(clear_before);
+
+ if (expr) {
+ unsigned int match_type = expr_get_match_type(expr);
+ if (match_type & TI_MATCH_ALBUM)
+ tree_expand_all();
+ if (match_type & TI_MATCH_TITLE)
+ tree_sel_first();
+ } else if (str)
+ tree_expand_matching(str);
+
+ if (!str)
+ restore_sel_track();
+}
+
+int lib_remove(struct track_info *ti)
+{
+ struct simple_track *track;
+
+ list_for_each_entry(track, &lib_editable.head, node) {
+ if (track->info == ti) {
+ editable_remove_track(&lib_editable, track);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void lib_clear_store(void)
+{
+ int i;
+
+ for (i = 0; i < FH_SIZE; i++) {
+ struct fh_entry *e, *next;
+
+ e = ti_hash[i];
+ while (e) {
+ next = e->next;
+ track_info_unref(e->ti);
+ free(e);
+ e = next;
+ }
+ ti_hash[i] = NULL;
+ }
+}
+
+void sorted_sel_current(void)
+{
+ if (lib_cur_track) {
+ struct iter iter;
+
+ sorted_track_to_iter(lib_cur_track, &iter);
+ window_set_sel(lib_editable.win, &iter);
+ }
+}
+
+static int ti_cmp(const void *a, const void *b)
+{
+ const struct track_info *ai = *(const struct track_info **)a;
+ const struct track_info *bi = *(const struct track_info **)b;
+
+ return track_info_cmp(ai, bi, lib_editable.sort_keys);
+}
+
+static int do_lib_for_each(int (*cb)(void *data, struct track_info *ti), void *data, int filtered)
+{
+ int i, rc = 0, count = 0, size = 1024;
+ struct track_info **tis;
+
+ tis = xnew(struct track_info *, size);
+
+ /* collect all track_infos */
+ for (i = 0; i < FH_SIZE; i++) {
+ struct fh_entry *e;
+
+ e = ti_hash[i];
+ while (e) {
+ if (count == size) {
+ size *= 2;
+ tis = xrenew(struct track_info *, tis, size);
+ }
+ if (!filtered || !filter || expr_eval(filter, e->ti))
+ tis[count++] = e->ti;
+ e = e->next;
+ }
+ }
+
+ /* sort to speed up playlist loading */
+ qsort(tis, count, sizeof(struct track_info *), ti_cmp);
+ for (i = 0; i < count; i++) {
+ rc = cb(data, tis[i]);
+ if (rc)
+ break;
+ }
+
+ free(tis);
+ return rc;
+}
+
+int lib_for_each(int (*cb)(void *data, struct track_info *ti), void *data)
+{
+ return do_lib_for_each(cb, data, 0);
+}
+
+int lib_for_each_filtered(int (*cb)(void *data, struct track_info *ti), void *data)
+{
+ return do_lib_for_each(cb, data, 1);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIB_H
+#define LIB_H
+
+#include "editable.h"
+#include "search.h"
+#include "track_info.h"
+#include "expr.h"
+#include "rbtree.h"
+
+struct tree_track {
+ struct shuffle_track shuffle_track;
+
+ /* position in track search tree */
+ struct rb_node tree_node;
+
+ struct album *album;
+};
+
+static inline struct track_info *tree_track_info(const struct tree_track *track)
+{
+ return ((struct simple_track *)track)->info;
+}
+
+static inline struct tree_track *to_tree_track(const struct rb_node *node)
+{
+ return container_of(node, struct tree_track, tree_node);
+}
+
+
+struct album {
+ /* position in album search tree */
+ struct rb_node tree_node;
+
+ /* root of track tree */
+ struct rb_root track_root;
+
+ struct artist *artist;
+ char *name;
+ char *sort_name;
+ char *collkey_name;
+ char *collkey_sort_name;
+ /* date of the first track added to this album */
+ int date;
+};
+
+struct artist {
+ /* position in artist search tree */
+ struct rb_node tree_node;
+
+ /* root of album tree */
+ struct rb_root album_root;
+
+ char *name;
+ char *sort_name;
+ char *auto_sort_name;
+ char *collkey_name;
+ char *collkey_sort_name;
+ char *collkey_auto_sort_name;
+
+ /* albums visible for this artist in the tree_win? */
+ unsigned int expanded : 1;
+ unsigned int is_compilation : 1;
+};
+
+const char *artist_sort_name(const struct artist *);
+
+enum aaa_mode {
+ AAA_MODE_ALL,
+ AAA_MODE_ARTIST,
+ AAA_MODE_ALBUM
+};
+
+extern struct editable lib_editable;
+extern struct tree_track *lib_cur_track;
+extern enum aaa_mode aaa_mode;
+extern unsigned int play_sorted;
+extern char *lib_live_filter;
+
+extern struct searchable *tree_searchable;
+extern struct window *lib_tree_win;
+extern struct window *lib_track_win;
+extern struct window *lib_cur_win;
+extern struct rb_root lib_artist_root;
+
+#define CUR_ALBUM (lib_cur_track->album)
+#define CUR_ARTIST (lib_cur_track->album->artist)
+
+void lib_init(void);
+void tree_init(void);
+struct track_info *lib_set_next(void);
+struct track_info *lib_set_prev(void);
+void lib_add_track(struct track_info *track_info);
+void lib_set_filter(struct expr *expr);
+void lib_set_live_filter(const char *str);
+int lib_remove(struct track_info *ti);
+void lib_clear_store(void);
+void lib_reshuffle(void);
+void lib_set_view(int view);
+int lib_for_each(int (*cb)(void *data, struct track_info *ti), void *data);
+int lib_for_each_filtered(int (*cb)(void *data, struct track_info *ti), void *data);
+
+struct tree_track *lib_find_track(struct track_info *ti);
+struct track_info *lib_set_track(struct tree_track *track);
+void lib_store_cur_track(struct track_info *ti);
+struct track_info *lib_get_cur_stored_track(void);
+
+struct tree_track *tree_get_selected(void);
+struct track_info *tree_set_selected(void);
+void tree_sort_artists(void);
+void tree_add_track(struct tree_track *track);
+void tree_remove(struct tree_track *track);
+void tree_remove_sel(void);
+void tree_toggle_active_window(void);
+void tree_toggle_expand_artist(void);
+void tree_expand_matching(const char *text);
+void tree_expand_all(void);
+void tree_sel_current(void);
+void tree_sel_first(void);
+void tree_sel_track(struct tree_track *t);
+int tree_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse);
+int __tree_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse);
+
+struct track_info *sorted_set_selected(void);
+void sorted_sel_current(void);
+
+static inline struct tree_track *iter_to_sorted_track(const struct iter *iter)
+{
+ return iter->data1;
+}
+
+static inline struct artist *iter_to_artist(const struct iter *iter)
+{
+ return iter->data1;
+}
+
+static inline struct album *iter_to_album(const struct iter *iter)
+{
+ return iter->data2;
+}
+
+static inline struct tree_track *iter_to_tree_track(const struct iter *iter)
+{
+ return iter->data1;
+}
+
+static inline struct artist *to_artist(const struct rb_node *node)
+{
+ return container_of(node, struct artist, tree_node);
+}
+
+static inline struct album *to_album(const struct rb_node *node)
+{
+ return container_of(node, struct album, tree_node);
+}
+
+#endif
--- /dev/null
+/*
+ * Stolen from Linux 2.6.7
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LINUX_LIST_H
+#define _LINUX_LIST_H
+
+#include "compiler.h" /* container_of */
+
+static inline void prefetch(const void *x)
+{
+}
+
+/*
+ * These are non-NULL pointers that will result in page faults
+ * under normal circumstances, used to verify that nobody uses
+ * non-initialized list entries.
+ */
+#define LIST_POISON1 ((void *) 0x00100100)
+#define LIST_POISON2 ((void *) 0x00200200)
+
+/*
+ * Simple doubly linked list implementation.
+ *
+ * Some of the internal functions ("__xxx") are useful when
+ * manipulating whole lists rather than single entries, as
+ * sometimes we already know the next/prev entries and we can
+ * generate better code by using them directly rather than
+ * using the generic single-entry routines.
+ */
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+#define LIST_HEAD_INIT(name) { &(name), &(name) }
+
+#define LIST_HEAD(name) \
+ struct list_head name = LIST_HEAD_INIT(name)
+
+static inline void list_init(struct list_head *head)
+{
+ head->next = head;
+ head->prev = head;
+}
+
+/*
+ * Insert a new entry between two known consecutive entries.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_add(struct list_head *new,
+ struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+/**
+ * list_add - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head, head->next);
+}
+
+/**
+ * list_add_tail - add a new entry
+ * @new: new entry to be added
+ * @head: list head to add it before
+ *
+ * Insert a new entry before the specified head.
+ * This is useful for implementing queues.
+ */
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ __list_add(new, head->prev, head);
+}
+
+/*
+ * Delete a list entry by making the prev/next entries
+ * point to each other.
+ *
+ * This is only for internal list manipulation where we know
+ * the prev/next entries already!
+ */
+static inline void __list_del(struct list_head *prev, struct list_head *next)
+{
+ next->prev = prev;
+ prev->next = next;
+}
+
+/**
+ * list_del - deletes entry from list.
+ * @entry: the element to delete from the list.
+ * Note: list_empty on entry does not return true after this, the entry is
+ * in an undefined state.
+ */
+static inline void list_del(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ entry->next = LIST_POISON1;
+ entry->prev = LIST_POISON2;
+}
+
+/**
+ * list_del_init - deletes entry from list and reinitialize it.
+ * @entry: the element to delete from the list.
+ */
+static inline void list_del_init(struct list_head *entry)
+{
+ __list_del(entry->prev, entry->next);
+ list_init(entry);
+}
+
+/**
+ * list_move - delete from one list and add as another's head
+ * @list: the entry to move
+ * @head: the head that will precede our entry
+ */
+static inline void list_move(struct list_head *list, struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add(list, head);
+}
+
+/**
+ * list_move_tail - delete from one list and add as another's tail
+ * @list: the entry to move
+ * @head: the head that will follow our entry
+ */
+static inline void list_move_tail(struct list_head *list,
+ struct list_head *head)
+{
+ __list_del(list->prev, list->next);
+ list_add_tail(list, head);
+}
+
+/**
+ * list_empty - tests whether a list is empty
+ * @head: the list to test.
+ */
+static inline int list_empty(const struct list_head *head)
+{
+ return head->next == head;
+}
+
+static inline void __list_splice(struct list_head *list,
+ struct list_head *head)
+{
+ struct list_head *first = list->next;
+ struct list_head *last = list->prev;
+ struct list_head *at = head->next;
+
+ first->prev = head;
+ head->next = first;
+
+ last->next = at;
+ at->prev = last;
+}
+
+/**
+ * list_splice - join two lists
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ */
+static inline void list_splice(struct list_head *list, struct list_head *head)
+{
+ if (!list_empty(list))
+ __list_splice(list, head);
+}
+
+/**
+ * list_splice_init - join two lists and reinitialise the emptied list.
+ * @list: the new list to add.
+ * @head: the place to add it in the first list.
+ *
+ * The list at @list is reinitialised
+ */
+static inline void list_splice_init(struct list_head *list,
+ struct list_head *head)
+{
+ if (!list_empty(list)) {
+ __list_splice(list, head);
+ list_init(list);
+ }
+}
+
+/**
+ * list_entry - get the struct for this entry
+ * @ptr: the &struct list_head pointer.
+ * @type: the type of the struct this is embedded in.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+/**
+ * list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each(pos, head) \
+ for (pos = (head)->next, prefetch(pos->next); pos != (head); \
+ pos = pos->next, prefetch(pos->next))
+
+/**
+ * __list_for_each - iterate over a list
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ *
+ * This variant differs from list_for_each() in that it's the
+ * simplest possible list iteration code, no prefetching is done.
+ * Use this for code that knows the list to be very short (empty
+ * or 1 entry) most of the time.
+ */
+#define __list_for_each(pos, head) \
+ for (pos = (head)->next; pos != (head); pos = pos->next)
+
+/**
+ * list_for_each_prev - iterate over a list backwards
+ * @pos: the &struct list_head to use as a loop counter.
+ * @head: the head for your list.
+ */
+#define list_for_each_prev(pos, head) \
+ for (pos = (head)->prev, prefetch(pos->prev); pos != (head); \
+ pos = pos->prev, prefetch(pos->prev))
+
+/**
+ * list_for_each_safe - iterate over a list safe against removal of list entry
+ * @pos: the &struct list_head to use as a loop counter.
+ * @n: another &struct list_head to use as temporary storage
+ * @head: the head for your list.
+ */
+#define list_for_each_safe(pos, n, head) \
+ for (pos = (head)->next, n = pos->next; pos != (head); \
+ pos = n, n = pos->next)
+
+/**
+ * list_for_each_entry - iterate over list of given type
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry(pos, head, member) \
+ for (pos = list_entry((head)->next, __typeof__(*pos), member), \
+ prefetch(pos->member.next); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, __typeof__(*pos), member), \
+ prefetch(pos->member.next))
+
+/**
+ * list_for_each_entry_reverse - iterate backwards over list of given type.
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_reverse(pos, head, member) \
+ for (pos = list_entry((head)->prev, __typeof__(*pos), member), \
+ prefetch(pos->member.prev); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.prev, __typeof__(*pos), member), \
+ prefetch(pos->member.prev))
+
+/**
+ * list_prepare_entry - prepare a pos entry for use as a start point in
+ * list_for_each_entry_continue
+ * @pos: the type * to use as a start point
+ * @head: the head of the list
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_prepare_entry(pos, head, member) \
+ ((pos) ? : list_entry(head, __typeof__(*pos), member))
+
+/**
+ * list_for_each_entry_continue - iterate over list of given type
+ * continuing after existing point
+ * @pos: the type * to use as a loop counter.
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_continue(pos, head, member) \
+ for (pos = list_entry(pos->member.next, __typeof__(*pos), member), \
+ prefetch(pos->member.next); \
+ &pos->member != (head); \
+ pos = list_entry(pos->member.next, __typeof__(*pos), member), \
+ prefetch(pos->member.next))
+
+/**
+ * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
+ * @pos: the type * to use as a loop counter.
+ * @n: another type * to use as temporary storage
+ * @head: the head for your list.
+ * @member: the name of the list_struct within the struct.
+ */
+#define list_for_each_entry_safe(pos, n, head, member) \
+ for (pos = list_entry((head)->next, __typeof__(*pos), member), \
+ n = list_entry(pos->member.next, __typeof__(*pos), member); \
+ &pos->member != (head); \
+ pos = n, n = list_entry(n->member.next, __typeof__(*n), member))
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "load_dir.h"
+#include "xmalloc.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <errno.h>
+
+int dir_open(struct directory *dir, const char *name)
+{
+ int len = strlen(name);
+
+ if (len >= sizeof(dir->path) - 2) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ dir->d = opendir(name);
+ if (!dir->d)
+ return -1;
+
+ memcpy(dir->path, name, len);
+ dir->path[len++] = '/';
+ dir->path[len] = 0;
+ dir->len = len;
+ return 0;
+}
+
+void dir_close(struct directory *dir)
+{
+ closedir(dir->d);
+}
+
+const char *dir_read(struct directory *dir)
+{
+ DIR *d = dir->d;
+ int len = dir->len;
+ char *full = dir->path;
+ struct dirent *de;
+
+#if defined(__CYGWIN__)
+ /* Fix for cygwin "hang" bug when browsing /
+ * Windows treats // as a network path.
+ */
+ if (strcmp(full, "//") == 0)
+ full++;
+#endif
+
+ while ((de = (struct dirent *) readdir(d))) {
+ const char *name = de->d_name;
+ int nlen = strlen(name);
+
+ /* just ignore too long paths
+ * + 2 -> space for \0 or / and \0
+ */
+ if (len + nlen + 2 >= sizeof(dir->path))
+ continue;
+
+ memcpy(full + len, name, nlen + 1);
+ if (lstat(full, &dir->st))
+ continue;
+
+ dir->is_link = 0;
+ if (S_ISLNK(dir->st.st_mode)) {
+ /* argh. must stat the target */
+ if (stat(full, &dir->st))
+ continue;
+ dir->is_link = 1;
+ }
+
+ return full + len;
+ }
+ return NULL;
+}
+
+void ptr_array_add(struct ptr_array *array, void *ptr)
+{
+ void **ptrs = array->ptrs;
+ int alloc = array->alloc;
+
+ if (alloc == array->count) {
+ alloc = alloc * 3 / 2 + 16;
+ ptrs = xrealloc(ptrs, alloc * sizeof(void *));
+ array->ptrs = ptrs;
+ array->alloc = alloc;
+ }
+ ptrs[array->count++] = ptr;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LOAD_DIR_H
+#define _LOAD_DIR_H
+
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <dirent.h>
+
+struct directory {
+ DIR *d;
+ int len;
+ /* we need stat information for symlink targets */
+ int is_link;
+ /* stat() information. ie. for the symlink target */
+ struct stat st;
+ char path[1024];
+};
+
+int dir_open(struct directory *dir, const char *name);
+void dir_close(struct directory *dir);
+const char *dir_read(struct directory *dir);
+
+
+struct ptr_array {
+ /* allocated with malloc(). contains pointers */
+ void *ptrs;
+ int alloc;
+ int count;
+};
+
+/* ptr_array.ptrs is either char ** or struct dir_entry ** */
+struct dir_entry {
+ mode_t mode;
+ char name[];
+};
+
+#define PTR_ARRAY(name) struct ptr_array name = { NULL, 0, 0 }
+
+void ptr_array_add(struct ptr_array *array, void *ptr);
+
+static inline void ptr_array_plug(struct ptr_array *array)
+{
+ ptr_array_add(array, NULL);
+ array->count--;
+}
+
+static inline void ptr_array_sort(struct ptr_array *array,
+ int (*cmp)(const void *a, const void *b))
+{
+ int count = array->count;
+ if (count)
+ qsort(array->ptrs, count, sizeof(void *), cmp);
+}
+
+static inline void ptr_array_unique(struct ptr_array *array,
+ int (*cmp)(const void *a, const void *b))
+{
+ void **ptrs = array->ptrs;
+ int i, j = 0;
+
+ for (i = 1; i < array->count; i++) {
+ if (cmp(&ptrs[i-1], &ptrs[i]) != 0)
+ ptrs[j++] = ptrs[i];
+ }
+ array->count = j;
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "locking.h"
+#include "debug.h"
+
+#include <string.h>
+
+void cmus_mutex_lock(pthread_mutex_t *mutex)
+{
+ int rc = pthread_mutex_lock(mutex);
+ if (unlikely(rc))
+ BUG("error locking mutex: %s\n", strerror(rc));
+}
+
+void cmus_mutex_unlock(pthread_mutex_t *mutex)
+{
+ int rc = pthread_mutex_unlock(mutex);
+ if (unlikely(rc))
+ BUG("error unlocking mutex: %s\n", strerror(rc));
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LOCKING_H
+#define _LOCKING_H
+
+#include <pthread.h>
+
+#define CMUS_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER
+#define CMUS_COND_INITIALIZER PTHREAD_COND_INITIALIZER
+
+void cmus_mutex_lock(pthread_mutex_t *mutex);
+void cmus_mutex_unlock(pthread_mutex_t *mutex);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "nomad.h"
+#include "id3.h"
+#include "ape.h"
+#include "xmalloc.h"
+#include "read_wrapper.h"
+#include "debug.h"
+#include "utils.h"
+#include "comment.h"
+
+#include <stdio.h>
+#include <math.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+/* ------------------------------------------------------------------------- */
+
+static ssize_t read_func(void *datasource, void *buffer, size_t count)
+{
+ struct input_plugin_data *ip_data = datasource;
+
+ return read_wrapper(ip_data, buffer, count);
+}
+
+static off_t lseek_func(void *datasource, off_t offset, int whence)
+{
+ struct input_plugin_data *ip_data = datasource;
+
+ return lseek(ip_data->fd, offset, whence);
+}
+
+static int close_func(void *datasource)
+{
+ struct input_plugin_data *ip_data = datasource;
+
+ return close(ip_data->fd);
+}
+
+static struct nomad_callbacks callbacks = {
+ .read = read_func,
+ .lseek = lseek_func,
+ .close = close_func
+};
+
+/* ------------------------------------------------------------------------- */
+
+static int mad_open(struct input_plugin_data *ip_data)
+{
+ struct nomad *nomad;
+ const struct nomad_info *info;
+ int rc;
+
+ rc = nomad_open_callbacks(&nomad, ip_data, &callbacks);
+ switch (rc) {
+ case -NOMAD_ERROR_ERRNO:
+ return -1;
+ case -NOMAD_ERROR_FILE_FORMAT:
+ return -IP_ERROR_FILE_FORMAT;
+ }
+ ip_data->private = nomad;
+
+ info = nomad_info(nomad);
+
+ /* always 16-bit signed little-endian */
+ ip_data->sf = sf_rate(info->sample_rate) | sf_channels(info->channels) |
+ sf_bits(16) | sf_signed(1);
+ channel_map_init_waveex(info->channels, 0, ip_data->channel_map);
+ return 0;
+}
+
+static int mad_close(struct input_plugin_data *ip_data)
+{
+ struct nomad *nomad;
+
+ nomad = ip_data->private;
+ nomad_close(nomad);
+ ip_data->fd = -1;
+ ip_data->private = NULL;
+ return 0;
+}
+
+static int mad_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct nomad *nomad;
+
+ nomad = ip_data->private;
+ return nomad_read(nomad, buffer, count);
+}
+
+static int mad_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct nomad *nomad;
+
+ nomad = ip_data->private;
+ return nomad_time_seek(nomad, offset);
+}
+
+static int mad_read_comments(struct input_plugin_data *ip_data,
+ struct keyval **comments)
+{
+ struct nomad *nomad = ip_data->private;
+ const struct nomad_lame *lame = nomad_lame(nomad);
+ struct id3tag id3;
+ int fd, rc, save, i;
+ APETAG(ape);
+ GROWING_KEYVALS(c);
+
+ fd = open(ip_data->filename, O_RDONLY);
+ if (fd == -1) {
+ return -1;
+ }
+ d_print("filename: %s\n", ip_data->filename);
+
+ id3_init(&id3);
+ rc = id3_read_tags(&id3, fd, ID3_V1 | ID3_V2);
+ save = errno;
+ close(fd);
+ errno = save;
+ if (rc) {
+ if (rc == -1) {
+ d_print("error: %s\n", strerror(errno));
+ return -1;
+ }
+ d_print("corrupted tag?\n");
+ goto next;
+ }
+
+ for (i = 0; i < NUM_ID3_KEYS; i++) {
+ char *val = id3_get_comment(&id3, i);
+
+ if (val)
+ comments_add(&c, id3_key_names[i], val);
+ }
+
+next:
+ id3_free(&id3);
+
+ rc = ape_read_tags(&ape, ip_data->fd, 0);
+ if (rc < 0)
+ goto out;
+
+ for (i = 0; i < rc; i++) {
+ char *k, *v;
+ k = ape_get_comment(&ape, &v);
+ if (!k)
+ break;
+ comments_add(&c, k, v);
+ free(k);
+ }
+
+out:
+ ape_free(&ape);
+
+ /* add last so the other tags get preference */
+ if (lame && !isnan(lame->trackGain)) {
+ char buf[64];
+
+ if (!isnan(lame->peak)) {
+ sprintf(buf, "%f", lame->peak);
+ comments_add_const(&c, "replaygain_track_peak", buf);
+ }
+ sprintf(buf, "%+.1f dB", lame->trackGain);
+ comments_add_const(&c, "replaygain_track_gain", buf);
+ }
+
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+ return 0;
+}
+
+static int mad_duration(struct input_plugin_data *ip_data)
+{
+ struct nomad *nomad = ip_data->private;
+
+ return nomad_info(nomad)->duration;
+}
+
+static long mad_bitrate(struct input_plugin_data *ip_data)
+{
+ struct nomad *nomad = ip_data->private;
+ long bitrate = nomad_info(nomad)->avg_bitrate;
+
+ return bitrate != -1 ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+static long mad_current_bitrate(struct input_plugin_data *ip_data)
+{
+ struct nomad *nomad = ip_data->private;
+ return nomad_current_bitrate(nomad);
+}
+
+static char *mad_codec(struct input_plugin_data *ip_data)
+{
+ struct nomad *nomad = ip_data->private;
+
+ switch (nomad_info(nomad)->layer) {
+ case 3:
+ return xstrdup("mp3");
+ case 2:
+ return xstrdup("mp2");
+ case 1:
+ return xstrdup("mp1");
+ }
+ return NULL;
+}
+
+static char *mad_codec_profile(struct input_plugin_data *ip_data)
+{
+ struct nomad *nomad = ip_data->private;
+ const struct nomad_lame *lame = nomad_lame(nomad);
+ const char *mode = nomad_info(nomad)->vbr ? "VBR" : "CBR";
+
+ if (lame) {
+ /* LAME:
+ * 0: unknown
+ * 1: cbr
+ * 2: abr
+ * 3: vbr rh (--vbr-old)
+ * 4: vbr mtrh (--vbr-new)
+ * 5: vbr mt (obsolete)
+ */
+ int method = lame->vbr_method;
+ if (method == 2)
+ mode = "ABR";
+ else if (method >= 3 && method <= 5) {
+ const struct nomad_xing *xing = nomad_xing(nomad);
+
+ if (xing && xing->flags & XING_SCALE && xing->scale && xing->scale <= 100) {
+ char buf[16];
+ int v = 10 - (xing->scale + 9) / 10;
+ /* quality (-q): 10 - (xing->scale - ((9 - v) * 10)) */
+
+ sprintf(buf, "VBR V%d", v);
+ return xstrdup(buf);
+
+ }
+ }
+ }
+
+ return xstrdup(mode);
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = mad_open,
+ .close = mad_close,
+ .read = mad_read,
+ .seek = mad_seek,
+ .read_comments = mad_read_comments,
+ .duration = mad_duration,
+ .bitrate = mad_bitrate,
+ .bitrate_current = mad_current_bitrate,
+ .codec = mad_codec,
+ .codec_profile = mad_codec_profile
+};
+
+const int ip_priority = 55;
+const char * const ip_extensions[] = { "mp3", "mp2", NULL };
+const char * const ip_mime_types[] = {
+ "audio/mpeg", "audio/x-mp3", "audio/x-mpeg", NULL
+};
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005-2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "prog.h"
+#include "file.h"
+#include "path.h"
+#include "xmalloc.h"
+#include "utils.h"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+
+static int sock;
+static int raw_args = 0;
+static char *passwd;
+
+static int read_answer(void)
+{
+ char buf[8192];
+ int got_nl = 0;
+ int len = 0;
+
+ while (1) {
+ int rc = read(sock, buf, sizeof(buf));
+
+ if (rc < 0) {
+ warn_errno("read");
+ break;
+ }
+ if (!rc)
+ die("unexpected EOF\n");
+
+ len += rc;
+
+ // Last line should be empty (i.e. read "\n" or "...\n\n").
+ // Write everything but the last \n to stdout.
+ if (got_nl && buf[0] == '\n')
+ break;
+ if (len == 1 && buf[0] == '\n')
+ break;
+ if (rc > 1 && buf[rc - 1] == '\n' && buf[rc - 2] == '\n') {
+ write_all(1, buf, rc - 1);
+ break;
+ }
+ got_nl = buf[rc - 1] == '\n';
+ write_all(1, buf, rc);
+ }
+ return len;
+}
+
+static int write_line(const char *line)
+{
+ if (write_all(sock, line, strlen(line)) == -1)
+ die_errno("write");
+
+ return read_answer();
+}
+
+static int send_cmd(const char *format, ...)
+{
+ char buf[512];
+ va_list ap;
+
+ va_start(ap, format);
+ vsnprintf(buf, sizeof(buf), format, ap);
+ va_end(ap);
+
+ return write_line(buf);
+}
+
+static int remote_connect(const char *address)
+{
+ union {
+ struct sockaddr sa;
+ struct sockaddr_un un;
+ struct sockaddr_storage sas;
+ } addr;
+ size_t addrlen;
+
+ if (strchr(address, '/')) {
+ if (passwd)
+ warn("password ignored for unix connections\n");
+ passwd = NULL;
+
+ addr.sa.sa_family = AF_UNIX;
+ strncpy(addr.un.sun_path, address, sizeof(addr.un.sun_path) - 1);
+
+ addrlen = sizeof(addr.un);
+ } else {
+ const struct addrinfo hints = {
+ .ai_socktype = SOCK_STREAM
+ };
+ const char *port = STRINGIZE(DEFAULT_PORT);
+ struct addrinfo *result;
+ char *s = strrchr(address, ':');
+ int rc;
+
+ if (!passwd)
+ die("password required for tcp/ip connection\n");
+
+ if (s) {
+ *s++ = 0;
+ port = s;
+ }
+
+ rc = getaddrinfo(address, port, &hints, &result);
+ if (rc != 0)
+ die("getaddrinfo: %s\n", gai_strerror(rc));
+ memcpy(&addr.sa, result->ai_addr, result->ai_addrlen);
+ addrlen = result->ai_addrlen;
+ freeaddrinfo(result);
+ }
+
+ sock = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+ if (sock == -1)
+ die_errno("socket");
+
+ if (connect(sock, &addr.sa, addrlen)) {
+ if (errno == ENOENT || errno == ECONNREFUSED) {
+ /* "cmus-remote -C" can be used to check if cmus is running */
+ if (!raw_args)
+ warn("cmus is not running\n");
+ exit(1);
+ }
+ die_errno("connect");
+ }
+
+ if (passwd)
+ return send_cmd("passwd %s\n", passwd) == 1;
+ return 1;
+}
+
+static char *file_url_absolute(const char *str)
+{
+ char *absolute;
+
+ if (strncmp(str, "http://", 7) == 0)
+ return xstrdup(str);
+
+ absolute = path_absolute(str);
+ if (absolute == NULL)
+ die_errno("get_current_dir_name");
+ return absolute;
+}
+
+enum flags {
+ FLAG_SERVER,
+ FLAG_PASSWD,
+ FLAG_HELP,
+ FLAG_VERSION,
+
+ FLAG_PLAY,
+ FLAG_PAUSE,
+ FLAG_STOP,
+ FLAG_NEXT,
+ FLAG_PREV,
+ FLAG_FILE,
+ FLAG_REPEAT,
+ FLAG_SHUFFLE,
+ FLAG_VOLUME,
+ FLAG_SEEK,
+ FLAG_QUERY,
+
+ FLAG_LIBRARY,
+ FLAG_PLAYLIST,
+ FLAG_QUEUE,
+ FLAG_CLEAR,
+
+ FLAG_RAW
+#define NR_FLAGS (FLAG_RAW + 1)
+};
+
+static struct option options[NR_FLAGS + 1] = {
+ { 0, "server", 1 },
+ { 0, "passwd", 1 },
+ { 0, "help", 0 },
+ { 0, "version", 0 },
+
+ { 'p', "play", 0 },
+ { 'u', "pause", 0 },
+ { 's', "stop", 0 },
+ { 'n', "next", 0 },
+ { 'r', "prev", 0 },
+ { 'f', "file", 1 },
+ { 'R', "repeat", 0 },
+ { 'S', "shuffle", 0 },
+ { 'v', "volume", 1 },
+ { 'k', "seek", 1 },
+ { 'Q', "query", 0 },
+
+ { 'l', "library", 0 },
+ { 'P', "playlist", 0 },
+ { 'q', "queue", 0 },
+ { 'c', "clear", 0 },
+
+ { 'C', "raw", 0 },
+ { 0, NULL, 0 }
+};
+
+static int flags[NR_FLAGS] = { 0, };
+
+static const char *usage =
+"Usage: %s [OPTION]... [FILE|DIR|PLAYLIST]...\n"
+" or: %s -C COMMAND...\n"
+" or: %s\n"
+"Control cmus through socket.\n"
+"\n"
+" --server ADDR connect using ADDR instead of ~/.cmus/socket\n"
+" ADDR is either a UNIX socket or host[:port]\n"
+" WARNING: using TCP/IP is insecure!\n"
+" --passwd PASSWD password to use for TCP/IP connection\n"
+" --help display this help and exit\n"
+" --version " VERSION "\n"
+"\n"
+"Cooked mode:\n"
+" -p, --play player-play\n"
+" -u, --pause player-pause\n"
+" -s, --stop player-stop\n"
+" -n, --next player-next\n"
+" -r, --prev player-prev\n"
+" -f, --file player-play FILE\n"
+" -R, --repeat toggle repeat\n"
+" -S, --shuffle toggle shuffle\n"
+" -v, --volume VOL vol VOL\n"
+" -k, --seek SEEK seek SEEK\n"
+" -Q, --query get player status (same as -C status)\n"
+"\n"
+" -l, --library modify library instead of playlist\n"
+" -P, --playlist modify playlist (default)\n"
+" -q, --queue modify play queue instead of playlist\n"
+" -c, --clear clear playlist, library (-l) or play queue (-q)\n"
+"\n"
+" Add FILE/DIR/PLAYLIST to playlist, library (-l) or play queue (-q).\n"
+"\n"
+"Raw mode:\n"
+" -C, --raw treat arguments (instead of stdin) as raw commands\n"
+"\n"
+" By default cmus-remote reads raw commands from stdin (one command per line).\n"
+"\n"
+"Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
+
+int main(int argc, char *argv[])
+{
+ char server_buf[256];
+ char *server = NULL;
+ char *play_file = NULL;
+ char *volume = NULL;
+ char *seek = NULL;
+ int query = 0;
+ int i, nr_cmds = 0;
+ int context = 'p';
+
+ program_name = argv[0];
+ argv++;
+ while (1) {
+ int idx;
+ char *arg;
+
+ idx = get_option(&argv, options, &arg);
+ if (idx < 0)
+ break;
+
+ flags[idx] = 1;
+ switch ((enum flags)idx) {
+ case FLAG_HELP:
+ printf(usage, program_name, program_name, program_name);
+ return 0;
+ case FLAG_VERSION:
+ printf("cmus " VERSION
+ "\nCopyright 2004-2006 Timo Hirvonen"
+ "\nCopyright 2008-2011 Various Authors\n");
+ return 0;
+ case FLAG_SERVER:
+ server = arg;
+ break;
+ case FLAG_PASSWD:
+ passwd = arg;
+ break;
+ case FLAG_VOLUME:
+ volume = arg;
+ nr_cmds++;
+ break;
+ case FLAG_SEEK:
+ seek = arg;
+ nr_cmds++;
+ break;
+ case FLAG_QUERY:
+ query = 1;
+ nr_cmds++;
+ break;
+ case FLAG_FILE:
+ play_file = arg;
+ nr_cmds++;
+ break;
+ case FLAG_LIBRARY:
+ context = 'l';
+ break;
+ case FLAG_PLAYLIST:
+ context = 'p';
+ break;
+ case FLAG_QUEUE:
+ context = 'q';
+ break;
+ case FLAG_PLAY:
+ case FLAG_PAUSE:
+ case FLAG_STOP:
+ case FLAG_NEXT:
+ case FLAG_PREV:
+ case FLAG_REPEAT:
+ case FLAG_SHUFFLE:
+ case FLAG_CLEAR:
+ nr_cmds++;
+ break;
+ case FLAG_RAW:
+ raw_args = 1;
+ break;
+ }
+ }
+
+ if (nr_cmds && raw_args)
+ die("don't mix raw and cooked stuff\n");
+
+ if (server == NULL) {
+ const char *config_dir = getenv("CMUS_HOME");
+
+ if (config_dir && config_dir[0]) {
+ snprintf(server_buf, sizeof(server_buf), "%s/socket", config_dir);
+ } else {
+ const char *home = getenv("HOME");
+
+ if (!home)
+ die("error: environment variable HOME not set\n");
+ snprintf(server_buf, sizeof(server_buf), "%s/.cmus/socket", home);
+ }
+ server = server_buf;
+ }
+
+ if (!remote_connect(server))
+ return 1;
+
+ if (raw_args) {
+ while (*argv)
+ send_cmd("%s\n", *argv++);
+ return 0;
+ }
+
+ if (nr_cmds == 0 && argv[0] == NULL) {
+ char line[512];
+
+ while (fgets(line, sizeof(line), stdin))
+ write_line(line);
+ return 0;
+ }
+
+ if (flags[FLAG_CLEAR])
+ send_cmd("clear -%c\n", context);
+ for (i = 0; argv[i]; i++) {
+ char *filename = file_url_absolute(argv[i]);
+
+ send_cmd("add -%c %s\n", context, filename);
+ free(filename);
+ }
+ if (flags[FLAG_REPEAT])
+ send_cmd("toggle repeat\n");
+ if (flags[FLAG_SHUFFLE])
+ send_cmd("toggle shuffle\n");
+ if (flags[FLAG_STOP])
+ send_cmd("player-stop\n");
+ if (flags[FLAG_NEXT])
+ send_cmd("player-next\n");
+ if (flags[FLAG_PREV])
+ send_cmd("player-prev\n");
+ if (flags[FLAG_PLAY])
+ send_cmd("player-play\n");
+ if (flags[FLAG_PAUSE])
+ send_cmd("player-pause\n");
+ if (flags[FLAG_FILE])
+ send_cmd("player-play %s\n", file_url_absolute(play_file));
+ if (volume)
+ send_cmd("vol %s\n", volume);
+ if (seek)
+ send_cmd("seek %s\n", seek);
+ if (query)
+ send_cmd("status\n");
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mergesort.h"
+#include "list.h"
+
+void list_mergesort(struct list_head *head,
+ int (*compare)(const struct list_head *, const struct list_head *))
+{
+ LIST_HEAD(empty);
+ struct list_head *unsorted_head, *sorted_head, *p, *q, *tmp;
+ int psize, qsize, K, count;
+
+ if (list_empty(head))
+ return;
+
+ unsorted_head = head;
+ sorted_head = ∅
+ K = 1;
+ while (1) {
+ p = unsorted_head->next;
+ count = 0;
+ do {
+ q = p;
+ psize = 0;
+ while (psize < K) {
+ if (q == unsorted_head)
+ break;
+ psize++;
+ q = q->next;
+ }
+ qsize = K;
+ while (1) {
+ struct list_head *e;
+
+ if (q == unsorted_head)
+ qsize = 0;
+ if (psize == 0 && qsize == 0)
+ break;
+ if (!psize || (qsize && compare(p, q) > 0)) {
+ e = q;
+ q = q->next;
+ qsize--;
+ } else {
+ e = p;
+ p = p->next;
+ psize--;
+ }
+ list_del(e);
+ list_add_tail(e, sorted_head);
+ }
+ count++;
+ p = q;
+ } while (p != unsorted_head);
+
+ if (count == 1) {
+ head->next = sorted_head->next;
+ head->prev = sorted_head->prev;
+ sorted_head->prev->next = head;
+ sorted_head->next->prev = head;
+ return;
+ }
+ tmp = unsorted_head;
+ unsorted_head = sorted_head;
+ sorted_head = tmp;
+ K *= 2;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MERGESORT_H
+#define _MERGESORT_H
+
+#include "list.h"
+
+void list_mergesort(struct list_head *head,
+ int (*compare)(const struct list_head *, const struct list_head *));
+
+#endif
--- /dev/null
+/*
+ * Adapted from AlsaPlayer 0.99.76
+ *
+ * mikmod_engine.c
+ * Copyright (C) 1999 Paul N. Fisher <rao@gnu.org>
+ * Copyright (C) 2002 Andy Lo A Foe <andy@alsaplayer.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "xmalloc.h"
+#include <mikmod.h>
+#include "debug.h"
+#include "comment.h"
+
+struct mik_private {
+ MODULE *file;
+};
+
+static int mikmod_init(void)
+{
+ static int inited = 0;
+
+ if (inited)
+ return 1;
+
+ MikMod_RegisterAllDrivers();
+ MikMod_RegisterAllLoaders();
+
+ md_reverb = 0;
+ /* we should let the user decide which one is better... */
+ md_mode = DMODE_SOFT_MUSIC | DMODE_SOFT_SNDFX | DMODE_STEREO |
+ DMODE_16BITS | DMODE_INTERP;
+
+ if (MikMod_Init(NULL)) {
+ d_print("Could not initialize mikmod, reason: %s\n",
+ MikMod_strerror(MikMod_errno));
+ return 0;
+ }
+
+ inited = 1;
+ return 1;
+}
+
+static int mik_open(struct input_plugin_data *ip_data)
+{
+ MODULE *mf = NULL;
+ struct mik_private *priv;
+ int mi = mikmod_init();
+
+ if (!mi)
+ return -IP_ERROR_INTERNAL;
+
+ mf = Player_Load(ip_data->filename, 255, 0);
+
+ if (!mf)
+ return -IP_ERROR_ERRNO;
+
+ priv = xnew(struct mik_private, 1);
+ priv->file = mf;
+
+ ip_data->private = priv;
+ ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
+#ifdef WORDS_BIGENDIAN
+ ip_data->sf |= sf_bigendian(1);
+#endif
+ channel_map_init_stereo(ip_data->channel_map);
+ return 0;
+}
+
+static int mik_close(struct input_plugin_data *ip_data)
+{
+ struct mik_private *priv = ip_data->private;
+
+ Player_Stop();
+ Player_Free(priv->file);
+ free(ip_data->private);
+ ip_data->private = NULL;
+ return 0;
+}
+
+static int mik_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ int length;
+ struct mik_private *priv = ip_data->private;
+
+ if (!Player_Active())
+ Player_Start(priv->file);
+
+ if (!Player_Active())
+ return 0;
+
+ length = VC_WriteBytes(buffer, count);
+
+ return length;
+}
+
+static int mik_seek(struct input_plugin_data *ip_data, double offset)
+{
+ /* cannot seek in modules properly */
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+static int mik_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
+{
+ struct mik_private *priv = ip_data->private;
+ GROWING_KEYVALS(c);
+ const char *val;
+
+ val = priv->file->songname;
+ if (val && val[0])
+ comments_add_const(&c, "title", val);
+
+ val = priv->file->comment;
+ if (val && val[0])
+ comments_add_const(&c, "comment", val);
+
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+ return 0;
+}
+
+static int mik_duration(struct input_plugin_data *ip_data)
+{
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+static long mik_bitrate(struct input_plugin_data *ip_data)
+{
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+static char *mik_codec(struct input_plugin_data *ip_data)
+{
+ struct mik_private *priv = ip_data->private;
+ const char *codec = priv->file->modtype;
+ return (codec && codec[0]) ? xstrdup(codec) : NULL;
+}
+
+static char *mik_codec_profile(struct input_plugin_data *ip_data)
+{
+ return NULL;
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = mik_open,
+ .close = mik_close,
+ .read = mik_read,
+ .seek = mik_seek,
+ .read_comments = mik_read_comments,
+ .duration = mik_duration,
+ .bitrate = mik_bitrate,
+ .bitrate_current = mik_bitrate,
+ .codec = mik_codec,
+ .codec_profile = mik_codec_profile
+};
+
+const int ip_priority = 40;
+const char * const ip_extensions[] = {
+ "mod", "s3m", "xm", "it", "669", "amf", "dsm",
+ "far", "med", "mtm", "stm", "ult",
+ NULL
+};
+const char * const ip_mime_types[] = { NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "misc.h"
+#include "prog.h"
+#include "xmalloc.h"
+#include "xstrjoin.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <stdarg.h>
+#include <pwd.h>
+
+const char *cmus_config_dir = NULL;
+const char *home_dir = NULL;
+const char *user_name = NULL;
+
+char **get_words(const char *text)
+{
+ char **words;
+ int i, j, count;
+
+ while (*text == ' ')
+ text++;
+
+ count = 0;
+ i = 0;
+ while (text[i]) {
+ count++;
+ while (text[i] && text[i] != ' ')
+ i++;
+ while (text[i] == ' ')
+ i++;
+ }
+ words = xnew(char *, count + 1);
+
+ i = 0;
+ j = 0;
+ while (text[i]) {
+ int start = i;
+
+ while (text[i] && text[i] != ' ')
+ i++;
+ words[j++] = xstrndup(text + start, i - start);
+ while (text[i] == ' ')
+ i++;
+ }
+ words[j] = NULL;
+ return words;
+}
+
+int strptrcmp(const void *a, const void *b)
+{
+ const char *as = *(char **)a;
+ const char *bs = *(char **)b;
+
+ return strcmp(as, bs);
+}
+
+int strptrcoll(const void *a, const void *b)
+{
+ const char *as = *(char **)a;
+ const char *bs = *(char **)b;
+
+ return strcoll(as, bs);
+}
+
+const char *escape(const char *str)
+{
+ static char *buf = NULL;
+ static size_t alloc = 0;
+ size_t len = strlen(str);
+ size_t need = len * 2 + 1;
+ int s, d;
+
+ if (need > alloc) {
+ alloc = (need + 16) & ~(16 - 1);
+ buf = xrealloc(buf, alloc);
+ }
+
+ d = 0;
+ for (s = 0; str[s]; s++) {
+ if (str[s] == '\\') {
+ buf[d++] = '\\';
+ buf[d++] = '\\';
+ continue;
+ }
+ if (str[s] == '\n') {
+ buf[d++] = '\\';
+ buf[d++] = 'n';
+ continue;
+ }
+ buf[d++] = str[s];
+ }
+ buf[d] = 0;
+ return buf;
+}
+
+const char *unescape(const char *str)
+{
+ static char *buf = NULL;
+ static size_t alloc = 0;
+ size_t need = strlen(str) + 1;
+ int do_escape = 0;
+ int s, d;
+
+ if (need > alloc) {
+ alloc = (need + 16) & ~(16 - 1);
+ buf = xrealloc(buf, alloc);
+ }
+
+ d = 0;
+ for (s = 0; str[s]; s++) {
+ if (!do_escape && str[s] == '\\')
+ do_escape = 1;
+ else {
+ buf[d++] = (do_escape && str[s] == 'n') ? '\n' : str[s];
+ do_escape = 0;
+ }
+ }
+ buf[d] = 0;
+ return buf;
+}
+
+static int dir_exists(const char *dirname)
+{
+ DIR *dir;
+
+ dir = opendir(dirname);
+ if (dir == NULL) {
+ if (errno == ENOENT)
+ return 0;
+ return -1;
+ }
+ closedir(dir);
+ return 1;
+}
+
+static void make_dir(const char *dirname)
+{
+ int rc;
+
+ rc = dir_exists(dirname);
+ if (rc == 1)
+ return;
+ if (rc == -1)
+ die_errno("error: opening `%s'", dirname);
+ rc = mkdir(dirname, 0700);
+ if (rc == -1)
+ die_errno("error: creating directory `%s'", dirname);
+}
+
+static char *get_non_empty_env(const char *name)
+{
+ const char *val;
+
+ val = getenv(name);
+ if (val == NULL || val[0] == 0)
+ return NULL;
+ return xstrdup(val);
+}
+
+int misc_init(void)
+{
+ home_dir = get_non_empty_env("HOME");
+ if (home_dir == NULL)
+ die("error: environment variable HOME not set\n");
+
+ user_name = get_non_empty_env("USER");
+ if (user_name == NULL) {
+ user_name = get_non_empty_env("USERNAME");
+ if (user_name == NULL)
+ die("error: neither USER or USERNAME environment variable set\n");
+ }
+
+ cmus_config_dir = get_non_empty_env("CMUS_HOME");
+ if (cmus_config_dir == NULL)
+ cmus_config_dir = xstrjoin(home_dir, "/.cmus");
+ make_dir(cmus_config_dir);
+ return 0;
+}
+
+int replaygain_decode(unsigned int field, int *gain)
+{
+ unsigned int name_code, originator_code, sign_bit, val;
+
+ name_code = (field >> 13) & 0x7;
+ if (!name_code || name_code > 2)
+ return 0;
+ originator_code = (field >> 10) & 0x7;
+ if (!originator_code)
+ return 0;
+ sign_bit = (field >> 9) & 0x1;
+ val = field & 0x1ff;
+ if (sign_bit && !val)
+ return 0;
+ *gain = (sign_bit ? -1 : 1) * val;
+ return name_code;
+}
+
+static char *get_home_dir(const char *username)
+{
+ struct passwd *passwd;
+
+ if (username == NULL)
+ return xstrdup(home_dir);
+ passwd = getpwnam(username);
+ if (passwd == NULL)
+ return NULL;
+ /* don't free passwd */
+ return xstrdup(passwd->pw_dir);
+}
+
+char *expand_filename(const char *name)
+{
+ if (name[0] == '~') {
+ char *slash;
+
+ slash = strchr(name, '/');
+ if (slash) {
+ char *username, *home;
+
+ if (slash - name - 1 > 0) {
+ /* ~user/... */
+ username = xstrndup(name + 1, slash - name - 1);
+ } else {
+ /* ~/... */
+ username = NULL;
+ }
+ home = get_home_dir(username);
+ free(username);
+ if (home) {
+ char *expanded;
+
+ expanded = xstrjoin(home, slash);
+ free(home);
+ return expanded;
+ } else {
+ return xstrdup(name);
+ }
+ } else {
+ if (name[1] == 0) {
+ return xstrdup(home_dir);
+ } else {
+ char *home;
+
+ home = get_home_dir(name + 1);
+ if (home)
+ return home;
+ return xstrdup(name);
+ }
+ }
+ } else {
+ return xstrdup(name);
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MISC_H
+#define _MISC_H
+
+extern const char *cmus_config_dir;
+extern const char *home_dir;
+extern const char *user_name;
+
+char **get_words(const char *text);
+int strptrcmp(const void *a, const void *b);
+int strptrcoll(const void *a, const void *b);
+int misc_init(void);
+const char *escape(const char *str);
+const char *unescape(const char *str);
+
+/*
+ * @field contains Replay Gain data format in bit representation
+ * @gain pointer where to store gain value * 10
+ *
+ * Returns 0 if @field doesn't contain a valid gain value,
+ * 1 for track (= radio) adjustment
+ * 2 for album (= audiophile) adjustment
+ *
+ * http://replaygain.hydrogenaudio.org/rg_data_format.html
+ */
+int replaygain_decode(unsigned int field, int *gain);
+
+char *expand_filename(const char *name);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MIXER_H
+#define _MIXER_H
+
+#ifndef __GNUC__
+#include <fcntl.h>
+#endif
+
+#define NR_MIXER_FDS 4
+
+struct mixer_plugin_ops {
+ int (*init)(void);
+ int (*exit)(void);
+ int (*open)(int *volume_max);
+ int (*close)(void);
+ int (*get_fds)(int *fds);
+ int (*set_volume)(int l, int r);
+ int (*get_volume)(int *l, int *r);
+ int (*set_option)(int key, const char *val);
+ int (*get_option)(int key, char **val);
+};
+
+/* symbols exported by plugin */
+extern const struct mixer_plugin_ops op_mixer_ops;
+extern const char * const op_mixer_options[];
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mixer.h"
+#include "op.h"
+#include "xmalloc.h"
+#include "debug.h"
+
+#include <strings.h>
+
+#define ALSA_PCM_NEW_HW_PARAMS_API
+#define ALSA_PCM_NEW_SW_PARAMS_API
+
+#include <alsa/asoundlib.h>
+
+static snd_mixer_t *alsa_mixer_handle;
+static snd_mixer_elem_t *mixer_elem = NULL;
+static long mixer_vol_min, mixer_vol_max;
+
+/* configuration */
+static char *alsa_mixer_device = NULL;
+static char *alsa_mixer_element = NULL;
+
+static int alsa_mixer_init(void)
+{
+ if (alsa_mixer_device == NULL)
+ alsa_mixer_device = xstrdup("default");
+ if (alsa_mixer_element == NULL)
+ alsa_mixer_element = xstrdup("PCM");
+ /* FIXME: check device */
+ return 0;
+}
+
+static int alsa_mixer_exit(void)
+{
+ free(alsa_mixer_device);
+ alsa_mixer_device = NULL;
+ free(alsa_mixer_element);
+ alsa_mixer_element = NULL;
+ return 0;
+}
+
+static snd_mixer_elem_t *find_mixer_elem_by_name(const char *goal_name)
+{
+ snd_mixer_elem_t *elem;
+ snd_mixer_selem_id_t *sid = NULL;
+
+ snd_mixer_selem_id_malloc(&sid);
+
+ for (elem = snd_mixer_first_elem(alsa_mixer_handle); elem;
+ elem = snd_mixer_elem_next(elem)) {
+
+ const char *name;
+
+ snd_mixer_selem_get_id(elem, sid);
+ name = snd_mixer_selem_id_get_name(sid);
+ d_print("name = %s\n", name);
+ d_print("has playback volume = %d\n", snd_mixer_selem_has_playback_volume(elem));
+ d_print("has playback switch = %d\n", snd_mixer_selem_has_playback_switch(elem));
+
+ if (strcasecmp(name, goal_name) == 0) {
+ if (!snd_mixer_selem_has_playback_volume(elem)) {
+ d_print("mixer element `%s' does not have playback volume\n", name);
+ elem = NULL;
+ }
+ break;
+ }
+ }
+
+ snd_mixer_selem_id_free(sid);
+ return elem;
+}
+
+static int alsa_mixer_open(int *volume_max)
+{
+ snd_mixer_elem_t *elem;
+ int count;
+ int rc;
+
+ rc = snd_mixer_open(&alsa_mixer_handle, 0);
+ if (rc < 0)
+ goto error;
+ rc = snd_mixer_attach(alsa_mixer_handle, alsa_mixer_device);
+ if (rc < 0)
+ goto error;
+ rc = snd_mixer_selem_register(alsa_mixer_handle, NULL, NULL);
+ if (rc < 0)
+ goto error;
+ rc = snd_mixer_load(alsa_mixer_handle);
+ if (rc < 0)
+ goto error;
+ count = snd_mixer_get_count(alsa_mixer_handle);
+ if (count == 0) {
+ d_print("error: mixer does not have elements\n");
+ return -2;
+ }
+
+ elem = find_mixer_elem_by_name(alsa_mixer_element);
+ if (!elem) {
+ d_print("mixer element `%s' not found, trying `Master'\n", alsa_mixer_element);
+ elem = find_mixer_elem_by_name("Master");
+ if (!elem) {
+ d_print("error: cannot find suitable mixer element\n");
+ return -2;
+ }
+ }
+ snd_mixer_selem_get_playback_volume_range(elem, &mixer_vol_min, &mixer_vol_max);
+ /* FIXME: get number of channels */
+ mixer_elem = elem;
+ *volume_max = mixer_vol_max - mixer_vol_min;
+
+ return 0;
+
+error:
+ d_print("error: %s\n", snd_strerror(rc));
+ return -1;
+}
+
+static int alsa_mixer_close(void)
+{
+ snd_mixer_close(alsa_mixer_handle);
+ return 0;
+}
+
+static int alsa_mixer_get_fds(int *fds)
+{
+ struct pollfd pfd[NR_MIXER_FDS];
+ int count, i;
+
+ count = snd_mixer_poll_descriptors(alsa_mixer_handle, pfd, NR_MIXER_FDS);
+ for (i = 0; i < count; i++)
+ fds[i] = pfd[i].fd;
+ return count;
+}
+
+static int alsa_mixer_set_volume(int l, int r)
+{
+ if (mixer_elem == NULL) {
+ return -1;
+ }
+ l += mixer_vol_min;
+ r += mixer_vol_min;
+ if (l > mixer_vol_max)
+ d_print("error: left volume too high (%d > %ld)\n",
+ l, mixer_vol_max);
+ if (r > mixer_vol_max)
+ d_print("error: right volume too high (%d > %ld)\n",
+ r, mixer_vol_max);
+ snd_mixer_selem_set_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, l);
+ snd_mixer_selem_set_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_RIGHT, r);
+ return 0;
+}
+
+static int alsa_mixer_get_volume(int *l, int *r)
+{
+ long lv, rv;
+
+ if (mixer_elem == NULL)
+ return -1;
+ snd_mixer_handle_events(alsa_mixer_handle);
+ snd_mixer_selem_get_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, &lv);
+ snd_mixer_selem_get_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_RIGHT, &rv);
+ *l = lv - mixer_vol_min;
+ *r = rv - mixer_vol_min;
+ return 0;
+}
+
+static int alsa_mixer_set_option(int key, const char *val)
+{
+ switch (key) {
+ case 0:
+ free(alsa_mixer_element);
+ alsa_mixer_element = xstrdup(val);
+ break;
+ case 1:
+ free(alsa_mixer_device);
+ alsa_mixer_device = xstrdup(val);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+static int alsa_mixer_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ if (alsa_mixer_element)
+ *val = xstrdup(alsa_mixer_element);
+ break;
+ case 1:
+ if (alsa_mixer_device)
+ *val = xstrdup(alsa_mixer_device);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+const struct mixer_plugin_ops op_mixer_ops = {
+ .init = alsa_mixer_init,
+ .exit = alsa_mixer_exit,
+ .open = alsa_mixer_open,
+ .close = alsa_mixer_close,
+ .get_fds = alsa_mixer_get_fds,
+ .set_volume = alsa_mixer_set_volume,
+ .get_volume = alsa_mixer_get_volume,
+ .set_option = alsa_mixer_set_option,
+ .get_option = alsa_mixer_get_option
+};
+
+const char * const op_mixer_options[] = {
+ "channel",
+ "device",
+ NULL
+};
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mixer.h"
+#include "op.h"
+#include "utils.h"
+#include "xmalloc.h"
+#include "debug.h"
+
+#include <strings.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#if defined(__OpenBSD__)
+#include <soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+
+enum {
+ OSS_MIXER_CHANNEL_VOLUME,
+ OSS_MIXER_CHANNEL_BASS,
+ OSS_MIXER_CHANNEL_TREBLE,
+ OSS_MIXER_CHANNEL_SYNTH,
+ OSS_MIXER_CHANNEL_PCM,
+ OSS_MIXER_CHANNEL_SPEAKER,
+ OSS_MIXER_CHANNEL_LINE,
+ OSS_MIXER_CHANNEL_MIC,
+ OSS_MIXER_CHANNEL_CD,
+ OSS_MIXER_CHANNEL_IMIX,
+ OSS_MIXER_CHANNEL_ALTPCM,
+ OSS_MIXER_CHANNEL_RECLEV,
+ OSS_MIXER_CHANNEL_IGAIN,
+ OSS_MIXER_CHANNEL_OGAIN,
+ OSS_MIXER_CHANNEL_LINE1,
+ OSS_MIXER_CHANNEL_LINE2,
+ OSS_MIXER_CHANNEL_LINE3,
+ OSS_MIXER_CHANNEL_DIGITAL1,
+ OSS_MIXER_CHANNEL_DIGITAL2,
+ OSS_MIXER_CHANNEL_DIGITAL3,
+ OSS_MIXER_CHANNEL_PHONEIN,
+ OSS_MIXER_CHANNEL_PHONEOUT,
+ OSS_MIXER_CHANNEL_VIDEO,
+ OSS_MIXER_CHANNEL_RADIO,
+ OSS_MIXER_CHANNEL_MONITOR,
+ OSS_MIXER_CHANNEL_MAX
+};
+
+static int mixer_fd = -1;
+static int mixer_devmask;
+/* static int mixer_recmask; */
+/* static int mixer_recsrc; */
+/* static int mixer_stereodevs; */
+static char mixer_channels[OSS_MIXER_CHANNEL_MAX];
+
+/* configuration */
+static char *oss_mixer_device = NULL;
+static int oss_volume_controls_pcm = 1;
+
+static int mixer_open(const char *device)
+{
+ int i;
+
+ mixer_fd = open(device, O_RDWR);
+ if (mixer_fd == -1)
+ return -1;
+ ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &mixer_devmask);
+/* ioctl(mixer_fd, SOUND_MIXER_READ_RECMASK, &mixer_recmask); */
+/* ioctl(mixer_fd, SOUND_MIXER_READ_RECSRC, &mixer_recsrc); */
+/* ioctl(mixer_fd, SOUND_MIXER_READ_STEREODEVS, &mixer_stereodevs); */
+ i = 0;
+ while (i < min(SOUND_MIXER_NRDEVICES, OSS_MIXER_CHANNEL_MAX)) {
+ mixer_channels[i] = (mixer_devmask >> i) & 1;
+ i++;
+ }
+ while (i < OSS_MIXER_CHANNEL_MAX)
+ mixer_channels[i++] = 0;
+ return 0;
+}
+
+static int mixer_set_level(int channel, int l, int r)
+{
+ int tmp;
+
+ tmp = (l & 0x7f) + ((r & 0x7f) << 8);
+ if (ioctl(mixer_fd, MIXER_WRITE(channel), &tmp) == -1)
+ return -1;
+ return 0;
+}
+
+static int mixer_get_level(int channel, int *l, int *r)
+{
+ int tmp;
+
+ if (ioctl(mixer_fd, MIXER_READ(channel), &tmp) == -1)
+ return -1;
+ *l = tmp & 0x7f;
+ *r = (tmp >> 8) & 0x7f;
+ return 0;
+}
+
+static int oss_device_exists(const char *device)
+{
+ struct stat s;
+
+ if (stat(device, &s) == 0) {
+ d_print("device %s exists\n", device);
+ return 1;
+ }
+ d_print("device %s does not exist\n", device);
+ return 0;
+}
+
+static int oss_mixer_init(void)
+{
+ const char *new_mixer_dev = "/dev/sound/mixer";
+ const char *mixer_dev = "/dev/mixer";
+
+ if (oss_mixer_device) {
+ if (oss_device_exists(oss_mixer_device))
+ return 0;
+ free(oss_mixer_device);
+ oss_mixer_device = NULL;
+ return -1;
+ }
+ if (oss_device_exists(new_mixer_dev)) {
+ oss_mixer_device = xstrdup(new_mixer_dev);
+ return 0;
+ }
+ if (oss_device_exists(mixer_dev)) {
+ oss_mixer_device = xstrdup(mixer_dev);
+ return 0;
+ }
+ return -1;
+}
+
+static int oss_mixer_exit(void)
+{
+ if (oss_mixer_device) {
+ free(oss_mixer_device);
+ oss_mixer_device = NULL;
+ }
+ return 0;
+}
+
+static int oss_mixer_open(int *volume_max)
+{
+ *volume_max = 100;
+ if (mixer_open(oss_mixer_device) == 0)
+ return 0;
+ return -1;
+}
+
+static int oss_mixer_close(void)
+{
+ close(mixer_fd);
+ mixer_fd = -1;
+ return 0;
+}
+
+static int oss_mixer_set_volume(int l, int r)
+{
+ if (oss_volume_controls_pcm) {
+ return mixer_set_level(OSS_MIXER_CHANNEL_PCM, l, r);
+ } else {
+ return mixer_set_level(OSS_MIXER_CHANNEL_VOLUME, l, r);
+ }
+}
+
+static int oss_mixer_get_volume(int *l, int *r)
+{
+ if (oss_volume_controls_pcm) {
+ return mixer_get_level(OSS_MIXER_CHANNEL_PCM, l, r);
+ } else {
+ return mixer_get_level(OSS_MIXER_CHANNEL_VOLUME, l, r);
+ }
+}
+
+static int oss_mixer_set_option(int key, const char *val)
+{
+ switch (key) {
+ case 0:
+ if (strcasecmp(val, "pcm") == 0) {
+ oss_volume_controls_pcm = 1;
+ } else if (strcasecmp(val, "master") == 0) {
+ oss_volume_controls_pcm = 0;
+ } else {
+ errno = EINVAL;
+ return -OP_ERROR_ERRNO;
+ }
+ break;
+ case 1:
+ free(oss_mixer_device);
+ oss_mixer_device = xstrdup(val);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+static int oss_mixer_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ if (oss_volume_controls_pcm) {
+ *val = xstrdup("PCM");
+ } else {
+ *val = xstrdup("Master");
+ }
+ break;
+ case 1:
+ if (oss_mixer_device)
+ *val = xstrdup(oss_mixer_device);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+const struct mixer_plugin_ops op_mixer_ops = {
+ .init = oss_mixer_init,
+ .exit = oss_mixer_exit,
+ .open = oss_mixer_open,
+ .close = oss_mixer_close,
+ .set_volume = oss_mixer_set_volume,
+ .get_volume = oss_mixer_get_volume,
+ .set_option = oss_mixer_set_option,
+ .get_option = oss_mixer_get_option
+};
+
+const char * const op_mixer_options[] = {
+ "channel",
+ "device",
+ NULL
+};
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * mixer_sun.c by alex <pukpuk@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "mixer.h"
+#include "op.h"
+#include "sf.h"
+#include "xmalloc.h"
+
+static int sun_mixer_device_id = -1;
+static int sun_mixer_channels = -1;
+static int sun_mixer_volume_delta = -1;
+static int mixer_fd = -1;
+
+static char *sun_mixer_device = NULL;
+static char *sun_mixer_channel = NULL;
+
+static int mixer_open(const char *);
+static int min_delta(int, int, int);
+static int sun_device_exists(const char *);
+static int sun_mixer_init(void);
+static int sun_mixer_exit(void);
+static int sun_mixer_open(int *);
+static int sun_mixer_close(void);
+static int sun_mixer_set_volume(int, int);
+static int sun_mixer_get_volume(int *, int *);
+static int sun_mixer_set_option(int, const char *);
+static int sun_mixer_get_option(int, char **);
+
+static int mixer_open(const char *dev)
+{
+ struct mixer_devinfo minf;
+ int output_class;
+
+ mixer_fd = open(dev, O_RDWR);
+ if (mixer_fd == -1)
+ return -1;
+
+ output_class = -1;
+ sun_mixer_device_id = -1;
+ /* determine output class */
+ minf.index = 0;
+ while (ioctl(mixer_fd, AUDIO_MIXER_DEVINFO, &minf) != -1) {
+ if (minf.type == AUDIO_MIXER_CLASS) {
+ if (strcmp(minf.label.name, AudioCoutputs) == 0)
+ output_class = minf.index;
+ }
+ ++minf.index;
+ }
+ /* no output class found?? something must be wrong */
+ if (output_class == -1)
+ return -1;
+
+ minf.index = 0;
+ /* query all mixer devices and try choose the correct one */
+ while (ioctl(mixer_fd, AUDIO_MIXER_DEVINFO, &minf) != -1) {
+ /* only scan output channels */
+ if (minf.type == AUDIO_MIXER_VALUE && minf.prev == AUDIO_MIXER_LAST &&
+ minf.mixer_class == output_class) {
+ if (strcasecmp(minf.label.name, sun_mixer_channel) == 0) {
+ sun_mixer_volume_delta = minf.un.v.delta;
+ sun_mixer_device_id = minf.index;
+ sun_mixer_channels = minf.un.v.num_channels;
+ }
+ }
+ ++minf.index;
+ }
+
+ if (sun_mixer_device_id == -1)
+ return -1;
+
+ d_print("sun: found mixer-channel: %s, devid: %d, delta: %d, channels: %d\n", sun_mixer_channel,
+ sun_mixer_device_id, sun_mixer_volume_delta, sun_mixer_channels);
+
+ if (sun_mixer_volume_delta == 0)
+ sun_mixer_volume_delta = 1;
+
+ return 0;
+
+}
+
+static int min_delta(int oval, int nval, int delta)
+{
+ if (oval > nval && oval - nval < delta)
+ nval -= delta;
+ else if (oval < nval && nval - oval < delta)
+ nval += delta;
+
+ nval = (nval < 0) ? 0 : nval;
+ nval = (nval > AUDIO_MAX_GAIN) ? AUDIO_MAX_GAIN : nval;
+
+ return nval;
+}
+
+static int sun_device_exists(const char *dev)
+{
+ struct stat s;
+
+ if (stat(dev, &s) == 0) {
+ d_print("device %s exists\n", dev);
+ return 1;
+ }
+ d_print("device %s does not exist\n", dev);
+
+ return 0;
+}
+
+static int sun_mixer_init(void)
+{
+ const char *mixer_dev = "/dev/mixer";
+
+ if (sun_mixer_device != NULL) {
+ if (sun_device_exists(sun_mixer_device))
+ return 0;
+ free(sun_mixer_device);
+ sun_mixer_device = NULL;
+ return -1;
+ }
+ if (sun_device_exists(mixer_dev)) {
+ sun_mixer_device = xstrdup(mixer_dev);
+ return 0;
+ }
+
+ return -1;
+}
+
+static int sun_mixer_exit(void)
+{
+ if (sun_mixer_device != NULL) {
+ free(sun_mixer_device);
+ sun_mixer_device = NULL;
+ }
+ if (sun_mixer_channel != NULL) {
+ free(sun_mixer_channel);
+ sun_mixer_channel = NULL;
+ }
+
+ return 0;
+}
+
+static int sun_mixer_open(int *vol_max)
+{
+ const char *mixer_channel = "master";
+
+ /* set default mixer channel */
+ if (sun_mixer_channel == NULL)
+ sun_mixer_channel = xstrdup(mixer_channel);
+
+ if (mixer_open(sun_mixer_device) == 0) {
+ *vol_max = AUDIO_MAX_GAIN;
+ return 0;
+ }
+
+ return -1;
+}
+
+static int sun_mixer_close(void)
+{
+ if (mixer_fd != -1) {
+ close(mixer_fd);
+ mixer_fd = -1;
+ }
+
+ return 0;
+}
+
+static int sun_mixer_set_volume(int l, int r)
+{
+ struct mixer_ctrl minf;
+ int ovall, ovalr;
+
+ if (sun_mixer_get_volume(&ovall, &ovalr) == -1)
+ return -1;
+
+ /* OpenBSD mixer values are `discrete' */
+ l = min_delta(ovall, l, sun_mixer_volume_delta);
+ r = min_delta(ovalr, r, sun_mixer_volume_delta);
+
+ minf.type = AUDIO_MIXER_VALUE;
+ minf.dev = sun_mixer_device_id;
+
+ if (sun_mixer_channels == 1)
+ minf.un.value.level[AUDIO_MIXER_LEVEL_MONO] = l;
+ else {
+ minf.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
+ minf.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
+ }
+ minf.un.value.num_channels = sun_mixer_channels;
+
+ if (ioctl(mixer_fd, AUDIO_MIXER_WRITE, &minf) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int sun_mixer_get_volume(int *l, int *r)
+{
+ struct mixer_ctrl minf;
+
+ minf.dev = sun_mixer_device_id;
+ minf.type = AUDIO_MIXER_VALUE;
+ minf.un.value.num_channels = sun_mixer_channels;
+
+ if (ioctl(mixer_fd, AUDIO_MIXER_READ, &minf) == -1)
+ return -1;
+
+ if (sun_mixer_channels == 1) {
+ *l = minf.un.value.level[AUDIO_MIXER_LEVEL_MONO];
+ *r = *l;
+ } else {
+ *l = minf.un.value.level[AUDIO_MIXER_LEVEL_LEFT];
+ *r = minf.un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
+ }
+
+ return 0;
+}
+
+static int sun_mixer_set_option(int key, const char *val)
+{
+ switch (key) {
+ case 0:
+ if (sun_mixer_channel != NULL)
+ free(sun_mixer_channel);
+ sun_mixer_channel = xstrdup(val);
+ break;
+ case 1:
+ free(sun_mixer_device);
+ sun_mixer_device = xstrdup(val);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+
+ return 0;
+}
+
+static int sun_mixer_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ if (sun_mixer_channel)
+ *val = xstrdup(sun_mixer_channel);
+ break;
+ case 1:
+ if (sun_mixer_device)
+ *val = xstrdup(sun_mixer_device);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+
+ return 0;
+}
+
+const struct mixer_plugin_ops op_mixer_ops = {
+ .init = sun_mixer_init,
+ .exit = sun_mixer_exit,
+ .open = sun_mixer_open,
+ .close = sun_mixer_close,
+ .set_volume = sun_mixer_set_volume,
+ .get_volume = sun_mixer_get_volume,
+ .set_option = sun_mixer_set_option,
+ .get_option = sun_mixer_get_option
+};
+
+const char * const op_mixer_options[] = {
+ "channel",
+ "device",
+ NULL
+};
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "file.h"
+#include "xmalloc.h"
+#include "comment.h"
+#ifdef HAVE_CONFIG
+#include "config/modplug.h"
+#endif
+
+#include <modplug.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+struct mod_private {
+ ModPlugFile *file;
+};
+
+static int mod_open(struct input_plugin_data *ip_data)
+{
+ struct mod_private *priv;
+ char *contents;
+ off_t size;
+ ssize_t rc;
+ ModPlugFile *file;
+ ModPlug_Settings settings;
+
+ size = lseek(ip_data->fd, 0, SEEK_END);
+ if (size == -1)
+ return -IP_ERROR_ERRNO;
+ if (lseek(ip_data->fd, 0, SEEK_SET) == -1)
+ return -IP_ERROR_ERRNO;
+
+ contents = xnew(char, size);
+ rc = read_all(ip_data->fd, contents, size);
+ if (rc == -1) {
+ int save = errno;
+
+ free(contents);
+ errno = save;
+ return -IP_ERROR_ERRNO;
+ }
+ if (rc != size) {
+ free(contents);
+ return -IP_ERROR_FILE_FORMAT;
+ }
+ errno = 0;
+ file = ModPlug_Load(contents, size);
+ if (file == NULL) {
+ int save = errno;
+
+ free(contents);
+ errno = save;
+ if (errno == 0) {
+ /* libmodplug never sets errno? */
+ return -IP_ERROR_FILE_FORMAT;
+ }
+ return -IP_ERROR_ERRNO;
+ }
+ free(contents);
+
+ priv = xnew(struct mod_private, 1);
+ priv->file = file;
+
+ ModPlug_GetSettings(&settings);
+ settings.mFlags = MODPLUG_ENABLE_OVERSAMPLING | MODPLUG_ENABLE_NOISE_REDUCTION;
+/* settings.mFlags |= MODPLUG_ENABLE_REVERB; */
+/* settings.mFlags |= MODPLUG_ENABLE_MEGABASS; */
+/* settings.mFlags |= MODPLUG_ENABLE_SURROUND; */
+ settings.mChannels = 2;
+ settings.mBits = 16;
+ settings.mFrequency = 44100;
+ settings.mResamplingMode = MODPLUG_RESAMPLE_FIR;
+ ModPlug_SetSettings(&settings);
+
+ ip_data->private = priv;
+ ip_data->sf = sf_bits(16) | sf_rate(44100) | sf_channels(2) | sf_signed(1);
+#ifdef WORDS_BIGENDIAN
+ ip_data->sf |= sf_bigendian(1);
+#endif
+ channel_map_init_stereo(ip_data->channel_map);
+ return 0;
+}
+
+static int mod_close(struct input_plugin_data *ip_data)
+{
+ struct mod_private *priv = ip_data->private;
+
+ ModPlug_Unload(priv->file);
+ free(priv);
+ ip_data->private = NULL;
+ return 0;
+}
+
+static int mod_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct mod_private *priv = ip_data->private;
+ int rc;
+
+ errno = 0;
+ rc = ModPlug_Read(priv->file, buffer, count);
+ if (rc < 0) {
+ if (errno == 0)
+ return -IP_ERROR_INTERNAL;
+ return -IP_ERROR_ERRNO;
+ }
+ return rc;
+}
+
+static int mod_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct mod_private *priv = ip_data->private;
+ int ms = (int)(offset * 1000.0 + 0.5);
+
+ /* void */
+ ModPlug_Seek(priv->file, ms);
+ return 0;
+}
+
+static int mod_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
+{
+ struct mod_private *priv = ip_data->private;
+ GROWING_KEYVALS(c);
+ const char *val;
+
+ val = ModPlug_GetName(priv->file);
+ if (val && val[0])
+ comments_add_const(&c, "title", val);
+
+#if MODPLUG_API_8
+ val = ModPlug_GetMessage(priv->file);
+ if (val && val[0])
+ comments_add_const(&c, "comment", val);
+#endif
+
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+ return 0;
+}
+
+static int mod_duration(struct input_plugin_data *ip_data)
+{
+ struct mod_private *priv = ip_data->private;
+
+ return (ModPlug_GetLength(priv->file) + 500) / 1000;
+}
+
+static long mod_bitrate(struct input_plugin_data *ip_data)
+{
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+#if MODPLUG_API_8
+static const char *mod_type_to_string(int type)
+{
+ /* from <libmodplug/sndfile.h>, which is C++ */
+ switch (type) {
+ case 0x01: return "mod";
+ case 0x02: return "s3m";
+ case 0x04: return "xm";
+ case 0x08: return "med";
+ case 0x10: return "mtm";
+ case 0x20: return "it";
+ case 0x40: return "699";
+ case 0x80: return "ult";
+ case 0x100: return "stm";
+ case 0x200: return "far";
+ case 0x800: return "amf";
+ case 0x1000: return "ams";
+ case 0x2000: return "dsm";
+ case 0x4000: return "mdl";
+ case 0x8000: return "okt";
+ case 0x10000: return "midi";
+ case 0x20000: return "dmf";
+ case 0x40000: return "ptm";
+ case 0x80000: return "dbm";
+ case 0x100000: return "mt2";
+ case 0x200000: return "amf0";
+ case 0x400000: return "psm";
+ case 0x80000000:return "umx";
+ }
+ return NULL;
+}
+#endif
+
+static char *mod_codec(struct input_plugin_data *ip_data)
+{
+#if MODPLUG_API_8
+ struct mod_private *priv = ip_data->private;
+ const char *codec;
+ int type;
+
+ type = ModPlug_GetModuleType(priv->file);
+ codec = mod_type_to_string(type);
+
+ return codec ? xstrdup(codec) : NULL;
+#else
+ return NULL;
+#endif
+}
+
+static char *mod_codec_profile(struct input_plugin_data *ip_data)
+{
+ return NULL;
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = mod_open,
+ .close = mod_close,
+ .read = mod_read,
+ .seek = mod_seek,
+ .read_comments = mod_read_comments,
+ .duration = mod_duration,
+ .bitrate = mod_bitrate,
+ .bitrate_current = mod_bitrate,
+ .codec = mod_codec,
+ .codec_profile = mod_codec_profile
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = {
+ "mod", "s3m", "xm", "it", "669", "amf", "ams", "dbm", "dmf", "dsm",
+ "far", "mdl", "med", "mtm", "okt", "ptm", "stm", "ult", "umx", "mt2",
+ "psm", NULL
+};
+const char * const ip_mime_types[] = { NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 dnk <dnk@bjum.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "file.h"
+#ifdef HAVE_CONFIG
+#include "config/mp4.h"
+#endif
+#include "comment.h"
+#include "aac.h"
+
+#if USE_MPEG4IP
+#include <mp4.h>
+#else
+#include <mp4v2/mp4v2.h>
+#endif
+
+#include <neaacdec.h>
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <strings.h>
+
+struct mp4_private {
+ char *overflow_buf;
+ int overflow_buf_len;
+
+ unsigned char channels;
+ unsigned long sample_rate;
+
+ NeAACDecHandle decoder; /* typedef void * */
+
+ struct {
+ MP4FileHandle handle; /* typedef void * */
+
+ MP4TrackId track;
+ MP4SampleId sample;
+ MP4SampleId num_samples;
+ } mp4;
+
+ struct {
+ unsigned long samples;
+ unsigned long bytes;
+ } current;
+};
+
+
+static MP4TrackId mp4_get_track(MP4FileHandle *handle)
+{
+ MP4TrackId num_tracks;
+ const char *track_type;
+ uint8_t obj_type;
+ MP4TrackId i;
+
+ num_tracks = MP4GetNumberOfTracks(handle, NULL, 0);
+
+ for (i = 1; i <= num_tracks; i++) {
+ track_type = MP4GetTrackType(handle, i);
+ if (!track_type)
+ continue;
+
+ if (!MP4_IS_AUDIO_TRACK_TYPE(track_type))
+ continue;
+
+ /* MP4GetTrackAudioType */
+ obj_type = MP4GetTrackEsdsObjectTypeId(handle, i);
+ if (obj_type == MP4_INVALID_AUDIO_TYPE)
+ continue;
+
+ if (obj_type == MP4_MPEG4_AUDIO_TYPE) {
+ obj_type = MP4GetTrackAudioMpeg4Type(handle, i);
+
+ if (MP4_IS_MPEG4_AAC_AUDIO_TYPE(obj_type))
+ return i;
+ } else {
+ if (MP4_IS_AAC_AUDIO_TYPE(obj_type))
+ return i;
+ }
+ }
+
+ return MP4_INVALID_TRACK_ID;
+}
+
+static void mp4_get_channel_map(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv = ip_data->private;
+ unsigned char *aac_data = NULL;
+ unsigned int aac_data_len = 0;
+ NeAACDecFrameInfo frame_info;
+ int i;
+
+ ip_data->channel_map[0] = CHANNEL_POSITION_INVALID;
+
+ if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, priv->mp4.sample,
+ &aac_data, &aac_data_len, NULL, NULL, NULL, NULL) == 0)
+ return;
+
+ if (!aac_data)
+ return;
+
+ NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len);
+ free(aac_data);
+
+ if (frame_info.error != 0 || frame_info.bytesconsumed <= 0
+ || frame_info.channels > CHANNELS_MAX)
+ return;
+
+ for (i = 0; i < frame_info.channels; i++)
+ ip_data->channel_map[i] = channel_position_aac(frame_info.channel_position[i]);
+}
+
+static void mp4_close_handle(MP4FileHandle handle)
+{
+#ifdef MP4_CLOSE_DO_NOT_COMPUTE_BITRATE
+ MP4Close(handle, 0);
+#else
+ MP4Close(handle);
+#endif
+}
+
+static int mp4_open(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv;
+ NeAACDecConfigurationPtr neaac_cfg;
+ unsigned char *buf;
+ unsigned int buf_size;
+ int rc = -IP_ERROR_FILE_FORMAT;
+
+ const struct mp4_private priv_init = {
+ .decoder = NULL
+ };
+
+ /* http://sourceforge.net/forum/message.php?msg_id=3578887 */
+ if (ip_data->remote)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+
+ /* init private struct */
+ priv = xnew(struct mp4_private, 1);
+ *priv = priv_init;
+ ip_data->private = priv;
+
+ priv->decoder = NeAACDecOpen();
+
+ /* set decoder config */
+ neaac_cfg = NeAACDecGetCurrentConfiguration(priv->decoder);
+ neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */
+ neaac_cfg->downMatrix = 0; /* NOT 5.1 -> stereo */
+ NeAACDecSetConfiguration(priv->decoder, neaac_cfg);
+
+ /* open mpeg-4 file */
+#ifdef MP4_DETAILS_ALL
+ priv->mp4.handle = MP4Read(ip_data->filename, 0);
+#else
+ priv->mp4.handle = MP4Read(ip_data->filename);
+#endif
+ if (!priv->mp4.handle) {
+ d_print("MP4Read failed\n");
+ goto out;
+ }
+
+ /* find aac audio track */
+ priv->mp4.track = mp4_get_track(priv->mp4.handle);
+ if (priv->mp4.track == MP4_INVALID_TRACK_ID) {
+ d_print("MP4FindTrackId failed\n");
+ if (MP4GetNumberOfTracks(priv->mp4.handle, MP4_AUDIO_TRACK_TYPE, 0) > 0)
+ rc = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
+ goto out;
+ }
+
+ priv->mp4.num_samples = MP4GetTrackNumberOfSamples(priv->mp4.handle, priv->mp4.track);
+
+ priv->mp4.sample = 1;
+
+ buf = NULL;
+ buf_size = 0;
+ if (!MP4GetTrackESConfiguration(priv->mp4.handle, priv->mp4.track, &buf, &buf_size)) {
+ /* failed to get mpeg-4 audio config... this is ok.
+ * NeAACDecInit2() will simply use default values instead.
+ */
+ buf = NULL;
+ buf_size = 0;
+ }
+
+ /* init decoder according to mpeg-4 audio config */
+ if (NeAACDecInit2(priv->decoder, buf, buf_size, &priv->sample_rate, &priv->channels) < 0) {
+ free(buf);
+ goto out;
+ }
+
+ free(buf);
+
+ d_print("sample rate %luhz, channels %u\n", priv->sample_rate, priv->channels);
+
+ ip_data->sf = sf_rate(priv->sample_rate) | sf_channels(priv->channels) | sf_bits(16) | sf_signed(1);
+#if defined(WORDS_BIGENDIAN)
+ ip_data->sf |= sf_bigendian(1);
+#endif
+ mp4_get_channel_map(ip_data);
+
+ return 0;
+
+out:
+ if (priv->mp4.handle)
+ mp4_close_handle(priv->mp4.handle);
+ if (priv->decoder)
+ NeAACDecClose(priv->decoder);
+ free(priv);
+ return rc;
+}
+
+static int mp4_close(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv;
+
+ priv = ip_data->private;
+
+ if (priv->mp4.handle)
+ mp4_close_handle(priv->mp4.handle);
+
+ if (priv->decoder)
+ NeAACDecClose(priv->decoder);
+
+ free(priv);
+ ip_data->private = NULL;
+
+ return 0;
+}
+
+/* returns -1 on fatal errors
+ * returns -2 on non-fatal errors
+ * 0 on eof
+ * number of bytes put in 'buffer' on success */
+static int decode_one_frame(struct input_plugin_data *ip_data, void *buffer, int count)
+{
+ struct mp4_private *priv;
+ unsigned char *aac_data = NULL;
+ unsigned int aac_data_len = 0;
+ NeAACDecFrameInfo frame_info;
+ char *sample_buf;
+ int bytes;
+
+ priv = ip_data->private;
+
+ BUG_ON(priv->overflow_buf_len);
+
+ if (priv->mp4.sample > priv->mp4.num_samples)
+ return 0; /* EOF */
+
+ if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, priv->mp4.sample,
+ &aac_data, &aac_data_len, NULL, NULL, NULL, NULL) == 0) {
+ d_print("error reading mp4 sample %d\n", priv->mp4.sample);
+ errno = EINVAL;
+ return -1;
+ }
+
+ priv->mp4.sample++;
+
+ if (!aac_data) {
+ d_print("aac_data == NULL\n");
+ errno = EINVAL;
+ return -1;
+ }
+
+ sample_buf = NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len);
+ if (frame_info.error == 0 && frame_info.samples > 0) {
+ priv->current.samples += frame_info.samples;
+ priv->current.bytes += frame_info.bytesconsumed;
+ }
+
+ free(aac_data);
+
+ if (!sample_buf || frame_info.bytesconsumed <= 0) {
+ d_print("fatal error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (frame_info.error != 0) {
+ d_print("frame error: %s\n", NeAACDecGetErrorMessage(frame_info.error));
+ return -2;
+ }
+
+ if (frame_info.samples <= 0)
+ return -2;
+
+ if (frame_info.channels != priv->channels || frame_info.samplerate != priv->sample_rate) {
+ d_print("invalid channel or sample_rate count\n");
+ return -2;
+ }
+
+ /* 16-bit samples */
+ bytes = frame_info.samples * 2;
+
+ if (bytes > count) {
+ /* decoded too much; keep overflow. */
+ priv->overflow_buf = sample_buf + count;
+ priv->overflow_buf_len = bytes - count;
+ memcpy(buffer, sample_buf, count);
+ return count;
+ } else {
+ memcpy(buffer, sample_buf, bytes);
+ }
+
+ return bytes;
+}
+
+static int mp4_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct mp4_private *priv;
+ int rc;
+
+ priv = ip_data->private;
+
+ /* use overflow from previous call (if any) */
+ if (priv->overflow_buf_len > 0) {
+ int len = priv->overflow_buf_len;
+
+ if (len > count)
+ len = count;
+
+ memcpy(buffer, priv->overflow_buf, len);
+ priv->overflow_buf += len;
+ priv->overflow_buf_len -= len;
+
+ return len;
+ }
+
+ do {
+ rc = decode_one_frame(ip_data, buffer, count);
+ } while (rc == -2);
+
+ return rc;
+}
+
+static int mp4_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct mp4_private *priv;
+ MP4SampleId sample;
+ uint32_t scale;
+
+ priv = ip_data->private;
+
+ scale = MP4GetTrackTimeScale(priv->mp4.handle, priv->mp4.track);
+ if (scale == 0)
+ return -IP_ERROR_INTERNAL;
+
+ sample = MP4GetSampleIdFromTime(priv->mp4.handle, priv->mp4.track,
+ (MP4Timestamp)(offset * (double)scale), 0);
+ if (sample == MP4_INVALID_SAMPLE_ID)
+ return -IP_ERROR_INTERNAL;
+
+ priv->mp4.sample = sample;
+
+ d_print("seeking to sample %d\n", sample);
+
+ return 0;
+}
+
+static int mp4_read_comments(struct input_plugin_data *ip_data,
+ struct keyval **comments)
+{
+ struct mp4_private *priv;
+#if USE_MPEG4IP
+ uint16_t meta_num, meta_total;
+ uint8_t val;
+ char *str;
+ /*uint8_t *ustr;
+ uint32_t size;*/
+#else
+ const MP4Tags *tags;
+ MP4ItmfItemList* itmf_list;
+#endif
+ GROWING_KEYVALS(c);
+
+ priv = ip_data->private;
+
+#if USE_MPEG4IP
+ /* MP4GetMetadata* provides malloced pointers, and the data
+ * is in UTF-8 (or at least it should be). */
+ if (MP4GetMetadataArtist(priv->mp4.handle, &str))
+ comments_add(&c, "artist", str);
+ if (MP4GetMetadataAlbum(priv->mp4.handle, &str))
+ comments_add(&c, "album", str);
+ if (MP4GetMetadataName(priv->mp4.handle, &str))
+ comments_add(&c, "title", str);
+ if (MP4GetMetadataGenre(priv->mp4.handle, &str))
+ comments_add(&c, "genre", str);
+ if (MP4GetMetadataYear(priv->mp4.handle, &str))
+ comments_add(&c, "date", str);
+
+ if (MP4GetMetadataCompilation(priv->mp4.handle, &val))
+ comments_add_const(&c, "compilation", val ? "yes" : "no");
+#if 0
+ if (MP4GetBytesProperty(priv->mp4.handle, "moov.udta.meta.ilst.aART.data", &ustr, &size)) {
+ char *xstr;
+
+ /* What's this?
+ * This is the result from lack of documentation.
+ * It's supposed to return just a string, but it
+ * returns an additional 16 bytes of junk at the
+ * beginning. Could be a bug. Could be intentional.
+ * Hopefully this works around it:
+ */
+ if (ustr[0] == 0 && size > 16) {
+ ustr += 16;
+ size -= 16;
+ }
+ xstr = xnew(char, size + 1);
+ memcpy(xstr, ustr, size);
+ xstr[size] = 0;
+ comments_add(&c, "albumartist", xstr);
+ free(xstr);
+ }
+#endif
+ if (MP4GetMetadataTrack(priv->mp4.handle, &meta_num, &meta_total)) {
+ char buf[6];
+ snprintf(buf, 6, "%u", meta_num);
+ comments_add_const(&c, "tracknumber", buf);
+ }
+ if (MP4GetMetadataDisk(priv->mp4.handle, &meta_num, &meta_total)) {
+ char buf[6];
+ snprintf(buf, 6, "%u", meta_num);
+ comments_add_const(&c, "discnumber", buf);
+ }
+
+#else /* !USE_MPEG4IP, new interface */
+
+ tags = MP4TagsAlloc();
+
+ MP4TagsFetch(tags, priv->mp4.handle);
+
+ if (tags->artist)
+ comments_add_const(&c, "artist", tags->artist);
+ if (tags->albumArtist)
+ comments_add_const(&c, "albumartist", tags->albumArtist);
+ if (tags->sortArtist)
+ comments_add_const(&c, "artistsort", tags->sortArtist);
+ if (tags->sortAlbumArtist)
+ comments_add_const(&c, "albumartistsort", tags->sortAlbumArtist);
+ if (tags->sortAlbum)
+ comments_add_const(&c, "albumsort", tags->sortAlbum);
+ if (tags->album)
+ comments_add_const(&c, "album", tags->album);
+ if (tags->name)
+ comments_add_const(&c, "title", tags->name);
+ if (tags->genre)
+ comments_add_const(&c, "genre", tags->genre);
+ if (tags->releaseDate)
+ comments_add_const(&c, "date", tags->releaseDate);
+ if (tags->compilation)
+ comments_add_const(&c, "compilation", *tags->compilation ? "yes" : "no");
+ if (tags->track) {
+ char buf[6];
+ snprintf(buf, 6, "%u", tags->track->index);
+ comments_add_const(&c, "tracknumber", buf);
+ }
+ if (tags->disk) {
+ char buf[6];
+ snprintf(buf, 6, "%u", tags->disk->index);
+ comments_add_const(&c, "discnumber", buf);
+ }
+
+ MP4TagsFree(tags);
+
+ itmf_list = MP4ItmfGetItemsByMeaning(priv->mp4.handle, "com.apple.iTunes", NULL);
+ if (itmf_list) {
+ int i;
+ for (i = 0; i < itmf_list->size; i++) {
+ MP4ItmfItem* item = &itmf_list->elements[i];
+ if (item->dataList.size < 1)
+ continue;
+ if (item->dataList.size > 1)
+ d_print("ignore multiple values for tag %s\n", item->name);
+ else {
+ MP4ItmfData* data = &item->dataList.elements[0];
+ char *val = xstrndup(data->value, data->valueSize);
+ comments_add(&c, item->name, val);
+ }
+ }
+ MP4ItmfItemListFree(itmf_list);
+ }
+#endif
+
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+ return 0;
+}
+
+static int mp4_duration(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv;
+ uint32_t scale;
+ uint64_t duration;
+
+ priv = ip_data->private;
+
+ scale = MP4GetTrackTimeScale(priv->mp4.handle, priv->mp4.track);
+ if (scale == 0)
+ return 0;
+
+ duration = MP4GetTrackDuration(priv->mp4.handle, priv->mp4.track);
+
+ return duration / scale;
+}
+
+static long mp4_bitrate(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv = ip_data->private;
+ long bitrate = MP4GetTrackBitRate(priv->mp4.handle, priv->mp4.track);
+ return bitrate ? bitrate : -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+static long mp4_current_bitrate(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv = ip_data->private;
+ long bitrate = -1;
+ if (priv->current.samples > 0) {
+ priv->current.samples /= priv->channels;
+ bitrate = (8 * priv->current.bytes * priv->sample_rate) / priv->current.samples;
+ priv->current.samples = 0;
+ priv->current.bytes = 0;
+ }
+ return bitrate;
+}
+
+static char *mp4_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("aac");
+}
+
+static const char *object_type_to_str(uint8_t obj_type)
+{
+ switch (obj_type) {
+ case MP4_MPEG4_AAC_MAIN_AUDIO_TYPE: return "Main";
+ case MP4_MPEG4_AAC_LC_AUDIO_TYPE: return "LC";
+ case MP4_MPEG4_AAC_SSR_AUDIO_TYPE: return "SSR";
+ case MP4_MPEG4_AAC_LTP_AUDIO_TYPE: return "LTP";
+#ifdef MP4_MPEG4_AAC_HE_AUDIO_TYPE
+ case MP4_MPEG4_AAC_HE_AUDIO_TYPE: return "HE";
+#endif
+ case MP4_MPEG4_AAC_SCALABLE_AUDIO_TYPE: return "Scalable";
+ }
+ return NULL;
+}
+
+static char *mp4_codec_profile(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv = ip_data->private;
+ const char *profile;
+ uint8_t obj_type;
+
+ obj_type = MP4GetTrackEsdsObjectTypeId(priv->mp4.handle, priv->mp4.track);
+ if (obj_type == MP4_MPEG4_AUDIO_TYPE)
+ obj_type = MP4GetTrackAudioMpeg4Type(priv->mp4.handle, priv->mp4.track);
+
+ profile = object_type_to_str(obj_type);
+
+ return profile ? xstrdup(profile) : NULL;
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = mp4_open,
+ .close = mp4_close,
+ .read = mp4_read,
+ .seek = mp4_seek,
+ .read_comments = mp4_read_comments,
+ .duration = mp4_duration,
+ .bitrate = mp4_bitrate,
+ .bitrate_current = mp4_current_bitrate,
+ .codec = mp4_codec,
+ .codec_profile = mp4_codec_profile
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = { "mp4", "m4a", "m4b", NULL };
+const char * const ip_mime_types[] = { /*"audio/mp4", "audio/mp4a-latm",*/ NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
+ *
+ * Cleaned up by Timo Hirvonen <tihirvon@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "ape.h"
+#include "comment.h"
+#include "file.h"
+#include "xmalloc.h"
+#include "read_wrapper.h"
+
+#ifdef HAVE_CONFIG
+#include "config/mpc.h"
+#endif
+
+#if MPC_SV8
+#include <mpc/mpcdec.h>
+#define callback_t mpc_reader
+#define get_ip_data(d) (d)->data
+#else
+#include <mpcdec/mpcdec.h>
+#define MPC_FALSE FALSE
+#define MPC_TRUE TRUE
+#define callback_t void
+#define get_ip_data(d) (d)
+#endif
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+
+struct mpc_private {
+#if MPC_SV8
+ mpc_demux *decoder;
+#else
+ mpc_decoder decoder;
+#endif
+ mpc_reader reader;
+ mpc_streaminfo info;
+
+ off_t file_size;
+
+ int samples_pos;
+ int samples_avail;
+
+ /* mpcdec/mpcdec.h
+ *
+ * the api doc says this is pcm samples per mpc frame
+ * but it's really pcm _frames_ per mpc frame
+ * MPC_FRAME_LENGTH = 36 * 32 (1152)
+ *
+ * this is wrong, it should be 2 * MPC_FRAME_LENGTH (2304)
+ * MPC_DECODER_BUFFER_LENGTH = 4 * MPC_FRAME_LENGTH (4608)
+ *
+ * use MPC_DECODER_BUFFER_LENGTH just to be sure it works
+ */
+ MPC_SAMPLE_FORMAT samples[MPC_DECODER_BUFFER_LENGTH];
+
+ struct {
+ unsigned long samples;
+ unsigned long bits;
+ } current;
+};
+
+/* callbacks */
+static mpc_int32_t read_impl(callback_t *data, void *ptr, mpc_int32_t size)
+{
+ struct input_plugin_data *ip_data = get_ip_data(data);
+ int rc;
+
+ rc = read_wrapper(ip_data, ptr, size);
+ if (rc == -1)
+ return -1;
+ if (rc == 0) {
+ errno = 0;
+ return 0;
+ }
+ return rc;
+}
+
+static mpc_bool_t seek_impl(callback_t *data, mpc_int32_t offset)
+{
+ struct input_plugin_data *ip_data = get_ip_data(data);
+
+ if (lseek(ip_data->fd, offset, SEEK_SET) == -1)
+ return MPC_FALSE;
+ return MPC_TRUE;
+}
+
+static mpc_int32_t tell_impl(callback_t *data)
+{
+ struct input_plugin_data *ip_data = get_ip_data(data);
+
+ return lseek(ip_data->fd, 0, SEEK_CUR);
+}
+
+static mpc_int32_t get_size_impl(callback_t *data)
+{
+ struct input_plugin_data *ip_data = get_ip_data(data);
+ struct mpc_private *priv = ip_data->private;
+
+ return priv->file_size;
+}
+
+static mpc_bool_t canseek_impl(callback_t *data)
+{
+ struct input_plugin_data *ip_data = get_ip_data(data);
+
+ return !ip_data->remote;
+}
+
+static int mpc_open(struct input_plugin_data *ip_data)
+{
+ struct mpc_private *priv;
+
+ const struct mpc_private priv_init = {
+ .file_size = -1,
+ /* set up an mpc_reader linked to our function implementations */
+ .reader = {
+ .read = read_impl,
+ .seek = seek_impl,
+ .tell = tell_impl,
+ .get_size = get_size_impl,
+ .canseek = canseek_impl,
+ .data = ip_data
+ }
+ };
+
+ priv = xnew(struct mpc_private, 1);
+ *priv = priv_init;
+
+ if (!ip_data->remote) {
+ priv->file_size = lseek(ip_data->fd, 0, SEEK_END);
+ lseek(ip_data->fd, 0, SEEK_SET);
+ }
+
+ /* must be before mpc_streaminfo_read() */
+ ip_data->private = priv;
+
+ /* read file's streaminfo data */
+#if MPC_SV8
+ priv->decoder = mpc_demux_init(&priv->reader);
+ if (!priv->decoder) {
+#else
+ mpc_streaminfo_init(&priv->info);
+ if (mpc_streaminfo_read(&priv->info, &priv->reader) != ERROR_CODE_OK) {
+#endif
+ free(priv);
+ return -IP_ERROR_FILE_FORMAT;
+ }
+
+#if MPC_SV8
+ mpc_demux_get_info(priv->decoder, &priv->info);
+#else
+ /* instantiate a decoder with our file reader */
+ mpc_decoder_setup(&priv->decoder, &priv->reader);
+ if (!mpc_decoder_initialize(&priv->decoder, &priv->info)) {
+ free(priv);
+ return -IP_ERROR_FILE_FORMAT;
+ }
+#endif
+
+ priv->samples_avail = 0;
+ priv->samples_pos = 0;
+
+ ip_data->sf = sf_rate(priv->info.sample_freq) | sf_channels(priv->info.channels) |
+ sf_bits(16) | sf_signed(1);
+ channel_map_init_waveex(priv->info.channels, 0, ip_data->channel_map);
+ return 0;
+}
+
+static int mpc_close(struct input_plugin_data *ip_data)
+{
+ struct mpc_private *priv = ip_data->private;
+
+#if MPC_SV8
+ mpc_demux_exit(priv->decoder);
+#endif
+ free(priv);
+ ip_data->private = NULL;
+ return 0;
+}
+
+static int scale(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct mpc_private *priv = ip_data->private;
+ const MPC_SAMPLE_FORMAT *samples;
+ const int clip_min = -1 << (16 - 1);
+ const int clip_max = (1 << (16 - 1)) - 1;
+ const int float_scale = 1 << (16 - 1);
+ int i, sample_count;
+
+ /* number of bytes to 16-bit samples */
+ sample_count = count / 2;
+ if (sample_count > priv->samples_avail)
+ sample_count = priv->samples_avail;
+
+ /* scale 32-bit samples to 16-bit */
+ samples = priv->samples + priv->samples_pos;
+ for (i = 0; i < sample_count; i++) {
+ int val;
+
+ val = samples[i] * float_scale;
+ if (val < clip_min) {
+ val = clip_min;
+ } else if (val > clip_max) {
+ val = clip_max;
+ }
+
+ buffer[i * 2 + 0] = val & 0xff;
+ buffer[i * 2 + 1] = val >> 8;
+ }
+
+ priv->samples_pos += sample_count;
+ priv->samples_avail -= sample_count;
+ if (priv->samples_avail == 0)
+ priv->samples_pos = 0;
+
+ /* number of 16-bit samples to bytes */
+ return sample_count * 2;
+}
+
+static int mpc_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct mpc_private *priv = ip_data->private;
+
+#if MPC_SV8
+ mpc_status status;
+ mpc_frame_info frame;
+ int samples;
+
+ frame.buffer = priv->samples;
+
+ while (priv->samples_avail == 0) {
+ status = mpc_demux_decode(priv->decoder, &frame);
+
+ if (status != MPC_STATUS_OK) {
+ return -IP_ERROR_ERRNO;
+ }
+ if (frame.bits == -1) {
+ /* EOF */
+ return 0;
+ }
+
+ samples = frame.samples;
+ priv->samples_avail = samples * priv->info.channels;
+
+ priv->current.samples += frame.samples;
+ priv->current.bits += frame.bits;
+ }
+#else
+
+ if (priv->samples_avail == 0) {
+ uint32_t acc = 0, bits = 0;
+ uint32_t status = mpc_decoder_decode(&priv->decoder, priv->samples, &acc, &bits);
+
+ if (status == (uint32_t)(-1)) {
+ /* right ret val? */
+ return -IP_ERROR_ERRNO;
+ }
+ if (status == 0) {
+ /* EOF */
+ return 0;
+ }
+
+ /* status seems to be number of _frames_
+ * the api documentation is wrong
+ */
+ priv->samples_avail = status * priv->info.channels;
+
+ priv->current.samples += status;
+ priv->current.bits += bits;
+ }
+#endif
+
+ return scale(ip_data, buffer, count);
+}
+
+static int mpc_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct mpc_private *priv = ip_data->private;
+
+ priv->samples_pos = 0;
+ priv->samples_avail = 0;
+
+#if MPC_SV8
+ if (mpc_demux_seek_second(priv->decoder, offset) == MPC_STATUS_OK)
+#else
+ if (mpc_decoder_seek_seconds(&priv->decoder, offset))
+#endif
+ return 0;
+ return -1;
+}
+
+static const char *gain_to_str(int gain)
+{
+ static char buf[16];
+#if MPC_SV8
+ float g = MPC_OLD_GAIN_REF - gain / 256.f;
+ sprintf(buf, "%.2f", g);
+#else
+ int b, a = gain / 100;
+
+ if (gain < 0) {
+ b = -gain % 100;
+ } else {
+ b = gain % 100;
+ }
+ sprintf(buf, "%d.%02d", a, b);
+#endif
+ return buf;
+}
+
+static const char *peak_to_str(unsigned int peak)
+{
+ static char buf[16];
+#if MPC_SV8
+ sprintf(buf, "%.5f", peak / 256.f / 100.f);
+#else
+ sprintf(buf, "%d.%05d", peak / 32767, peak % 32767);
+#endif
+ return buf;
+}
+
+static int mpc_read_comments(struct input_plugin_data *ip_data, struct keyval **comments)
+{
+ struct mpc_private *priv = ip_data->private;
+ GROWING_KEYVALS(c);
+ int count, i;
+ APETAG(ape);
+
+ count = ape_read_tags(&ape, ip_data->fd, 1);
+ if (count < 0)
+ goto out;
+
+ for (i = 0; i < count; i++) {
+ char *k, *v;
+ k = ape_get_comment(&ape, &v);
+ if (!k)
+ break;
+ comments_add(&c, k, v);
+ free(k);
+ }
+
+out:
+ if (priv->info.gain_title && priv->info.peak_title) {
+ comments_add_const(&c, "replaygain_track_gain", gain_to_str(priv->info.gain_title));
+ comments_add_const(&c, "replaygain_track_peak", peak_to_str(priv->info.peak_title));
+ }
+ if (priv->info.gain_album && priv->info.peak_album) {
+ comments_add_const(&c, "replaygain_album_gain", gain_to_str(priv->info.gain_album));
+ comments_add_const(&c, "replaygain_album_peak", peak_to_str(priv->info.peak_album));
+ }
+ keyvals_terminate(&c);
+
+ *comments = c.keyvals;
+ ape_free(&ape);
+ return 0;
+}
+
+static int mpc_duration(struct input_plugin_data *ip_data)
+{
+ struct mpc_private *priv = ip_data->private;
+
+ /* priv->info.pcm_samples seems to be number of frames
+ * priv->info.frames is _not_ pcm frames
+ */
+#if MPC_SV8
+ return mpc_streaminfo_get_length(&priv->info);
+#else
+ return priv->info.pcm_samples / priv->info.sample_freq;
+#endif
+}
+
+static long mpc_bitrate(struct input_plugin_data *ip_data)
+{
+ struct mpc_private *priv = ip_data->private;
+ if (priv->info.average_bitrate)
+ return (long) (priv->info.average_bitrate + 0.5);
+ if (priv->info.bitrate)
+ return priv->info.bitrate;
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+}
+
+static long mpc_current_bitrate(struct input_plugin_data *ip_data)
+{
+ struct mpc_private *priv = ip_data->private;
+ long bitrate = -1;
+ if (priv->current.samples > 0) {
+ bitrate = (priv->info.sample_freq * priv->current.bits) / priv->current.samples;
+ priv->current.samples = 0;
+ priv->current.bits = 0;
+ }
+ return bitrate;
+
+}
+
+static char *mpc_codec(struct input_plugin_data *ip_data)
+{
+ struct mpc_private *priv = ip_data->private;
+ switch (priv->info.stream_version) {
+ case 7:
+ return xstrdup("mpc7");
+ case 8:
+ return xstrdup("mpc8");
+ }
+ return NULL;
+}
+
+static char *mpc_codec_profile(struct input_plugin_data *ip_data)
+{
+ struct mpc_private *priv = ip_data->private;
+ const char *profile = priv->info.profile_name;
+ char *s = NULL;
+
+ if (profile && profile[0]) {
+ int i;
+
+ /* remove single quotes */
+ while (*profile == '\'')
+ profile++;
+ s = xstrdup(profile);
+ for (i = strlen(s) - 1; s[i] == '\'' && i >= 0; i--)
+ s[i] = '\0';
+ }
+
+ return s;
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = mpc_open,
+ .close = mpc_close,
+ .read = mpc_read,
+ .seek = mpc_seek,
+ .read_comments = mpc_read_comments,
+ .duration = mpc_duration,
+ .bitrate = mpc_bitrate,
+ .bitrate_current = mpc_current_bitrate,
+ .codec = mpc_codec,
+ .codec_profile = mpc_codec_profile
+};
+
+const int ip_priority = 50;
+const char *const ip_extensions[] = { "mpc", "mpp", "mp+", NULL };
+const char *const ip_mime_types[] = { "audio/x-musepack", NULL };
+const char *const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Gapless decoding added by Chun-Yu Shei <cshei AT cs.indiana.edu>
+ */
+
+/*
+ * Xing code copied from xmms-mad plugin.
+ * Lame code copied from mpd
+ */
+
+#include "nomad.h"
+#include "id3.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "misc.h"
+
+#include <mad.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#define INPUT_BUFFER_SIZE (5 * 8192)
+#define SEEK_IDX_INTERVAL 15
+
+/* the number of samples of silence the decoder inserts at start */
+#define DECODERDELAY 529
+
+#define XING_MAGIC (('X' << 24) | ('i' << 16) | ('n' << 8) | 'g')
+#define INFO_MAGIC (('I' << 24) | ('n' << 16) | ('f' << 8) | 'o')
+
+struct seek_idx_entry {
+ off_t offset;
+ mad_timer_t timer;
+};
+
+struct nomad {
+ struct mad_stream stream;
+ struct mad_frame frame;
+ struct mad_synth synth;
+ mad_timer_t timer;
+ unsigned long cur_frame;
+ off_t input_offset;
+ /* MAD_BUFFER_GUARD zeros are required at the end of the stream to decode the last frame
+ ref: http://www.mars.org/mailman/public/mad-dev/2001-May/000262.html */
+ unsigned char input_buffer[INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD];
+ int i;
+ unsigned int has_xing : 1;
+ unsigned int has_lame : 1;
+ unsigned int seen_first_frame : 1;
+ unsigned int readEOF : 1;
+ int start_drop_frames;
+ int start_drop_samples;
+ int end_drop_samples;
+ int end_drop_frames;
+
+ struct nomad_xing xing;
+ struct nomad_lame lame;
+
+ struct {
+ int size;
+ struct seek_idx_entry *table;
+ } seek_idx;
+
+ struct {
+ unsigned long long int bitrate_sum;
+ unsigned long nr_frames;
+ } current;
+
+ struct nomad_info info;
+ void *datasource;
+ int datasource_fd;
+ struct nomad_callbacks cbs;
+};
+
+static inline int scale(mad_fixed_t sample)
+{
+ sample += 1L << (MAD_F_FRACBITS - 16);
+ if (sample >= MAD_F_ONE) {
+ sample = MAD_F_ONE - 1;
+ } else if (sample < -MAD_F_ONE) {
+ sample = -MAD_F_ONE;
+ }
+ return sample >> (MAD_F_FRACBITS - 15);
+}
+
+static inline double timer_to_seconds(mad_timer_t timer)
+{
+ signed long ms;
+
+ ms = mad_timer_count(timer, MAD_UNITS_MILLISECONDS);
+ return (double)ms / 1000.0;
+}
+
+static int parse_lame(struct nomad *nomad, struct mad_bitptr ptr, int bitlen)
+{
+ int i, adj = 0;
+ unsigned int version_major, version_minor;
+ float val;
+
+ /* Unlike the xing header, the lame tag has a fixed length. Fail if
+ * not all 36 bytes (288 bits) are there. */
+ if (bitlen < 288) return 0;
+
+ for (i = 0; i < 9; i++) nomad->lame.encoder[i] = (char)mad_bit_read(&ptr, 8);
+ nomad->lame.encoder[9] = '\0';
+
+ /* This is technically incorrect, since the encoder might not be lame.
+ * But there's no other way to determine if this is a lame tag, and we
+ * wouldn't want to go reading a tag that's not there. */
+ if (strncmp(nomad->lame.encoder, "LAME", 4) != 0) return 0;
+
+ if (sscanf(nomad->lame.encoder + 4, "%u.%u", &version_major, &version_minor) != 2)
+ return 0;
+
+#if defined(DEBUG_LAME)
+ d_print("detected LAME version %s\n", nomad->lame.encoder + 4);
+#endif
+
+ i = mad_bit_read(&ptr, 4);
+#if defined(DEBUG_LAME)
+ d_print("LAME tag revision: %d\n", i);
+#endif
+ nomad->lame.vbr_method = mad_bit_read(&ptr, 4);
+
+ /* ReplayGain in LAME tag was added in 3.94 */
+ if (version_major > 3 || (version_major == 3 && version_minor >= 94)) {
+ /* lowpass */
+ mad_bit_read(&ptr, 8);
+
+ /* The reference volume was changed from the 83dB used in the
+ * ReplayGain spec to 89dB in lame 3.95.1. Bump the gain for older
+ * versions, since everyone else uses 89dB instead of 83dB.
+ * Unfortunately, lame didn't differentiate between 3.95 and 3.95.1, so
+ * it's impossible to make the proper adjustment for 3.95.
+ * Fortunately, 3.95 was only out for about a day before 3.95.1 was
+ * released. -- tmz */
+ if (version_major < 3 || (version_major == 3 && version_minor < 95))
+ adj = 6;
+
+ val = mad_bit_read(&ptr, 32) / (float) (1 << 23);
+ /* peak value of 0.0 means lame didn't calculate the peak at all
+ * (--replaygain-fast), even silence has a value > 0.0 */
+ if (val)
+ nomad->lame.peak = val;
+ for (i = 0; i < 2; i++) {
+ int gain, gain_type;
+ gain_type = replaygain_decode(mad_bit_read(&ptr, 16), &gain);
+ val = gain / 10.f + adj;
+ if (gain_type == 1)
+ nomad->lame.trackGain = val;
+ /* LAME currently doesn't store any album gain!
+ else if (gain_type == 2)
+ nomad->lame.albumGain = val;
+ */
+ }
+
+ /*
+ * 4 encoding flags
+ * 4 ATH type
+ * 8 minimal bitrate (if ABR -> specified bitrate)
+ */
+ mad_bit_read(&ptr, 16);
+ } else
+ mad_bit_read(&ptr, 88);
+
+ nomad->lame.encoderDelay = mad_bit_read(&ptr, 12);
+ nomad->lame.encoderPadding = mad_bit_read(&ptr, 12);
+#if defined(DEBUG_LAME)
+ if (adj > 0)
+ d_print("adjusted gains by %+d dB (old LAME)\n", adj);
+ if (!isnan(nomad->lame.peak))
+ d_print("peak: %f\n", nomad->lame.peak);
+ if (!isnan(nomad->lame.trackGain))
+ d_print("trackGain: %+.1f dB\n", nomad->lame.trackGain);
+ if (!isnan(nomad->lame.albumGain))
+ d_print("albumGain: %+.1f dB\n", nomad->lame.albumGain);
+ d_print("encoderDelay: %d, encoderPadding: %d\n", nomad->lame.encoderDelay, nomad->lame.encoderPadding);
+#endif
+
+ mad_bit_read(&ptr, 96);
+
+ nomad->start_drop_frames = 1; /* XING/LAME header is an empty frame */
+ nomad->start_drop_samples = nomad->lame.encoderDelay + DECODERDELAY;
+ nomad->end_drop_samples = nomad->lame.encoderPadding - DECODERDELAY;
+
+ nomad->has_lame = 1;
+
+ return 1;
+}
+
+/*
+ * format:
+ *
+ * 4 "Xing"
+ * 4 flags
+ * 4 frames (optional)
+ * 4 bytes (optional)
+ * 100 TOC (optional)
+ * 4 scale (optional)
+ */
+static int xing_parse(struct nomad *nomad)
+{
+ struct mad_bitptr ptr = nomad->stream.anc_ptr;
+ struct mad_bitptr start = ptr;
+ int oldbitlen = nomad->stream.anc_bitlen;
+ int bitlen = nomad->stream.anc_bitlen;
+ int bitsleft;
+ unsigned xing_id;
+
+ nomad->has_xing = 0;
+ nomad->has_lame = 0;
+ if (bitlen < 64)
+ return -1;
+ xing_id = mad_bit_read(&ptr, 32);
+ if (xing_id != XING_MAGIC && xing_id != INFO_MAGIC) {
+ /*
+ * Due to an unfortunate historical accident, a Xing VBR tag
+ * may be misplaced in a stream with CRC protection. We check
+ * for this by assuming the tag began two octets prior and the
+ * high bits of the following flags field are always zero.
+ */
+ if (xing_id != ((XING_MAGIC << 16) & 0xffffffffL) &&
+ xing_id != ((INFO_MAGIC << 16) & 0xffffffffL))
+ return -1;
+ xing_id >>= 16;
+ ptr = start;
+ mad_bit_skip(&ptr, 16);
+ bitlen += 16;
+ }
+ nomad->xing.is_info = ((xing_id & 0x0000ffffL) == (INFO_MAGIC & 0x0000ffffL));
+ nomad->xing.flags = mad_bit_read(&ptr, 32);
+ bitlen -= 64;
+ if (nomad->xing.flags & XING_FRAMES) {
+ if (bitlen < 32)
+ return -1;
+ nomad->xing.nr_frames = mad_bit_read(&ptr, 32);
+ bitlen -= 32;
+ }
+ if (nomad->xing.flags & XING_BYTES) {
+ if (bitlen < 32)
+ return -1;
+ nomad->xing.bytes = mad_bit_read(&ptr, 32);
+ bitlen -= 32;
+ }
+ if (nomad->xing.flags & XING_TOC) {
+ int i;
+
+ if (bitlen < 800)
+ return -1;
+ for (i = 0; i < 100; i++)
+ nomad->xing.toc[i] = mad_bit_read(&ptr, 8);
+ bitlen -= 800;
+ }
+ if (nomad->xing.flags & XING_SCALE) {
+ if (bitlen < 32)
+ return -1;
+ nomad->xing.scale = mad_bit_read(&ptr, 32);
+ bitlen -= 32;
+ }
+
+ /* Make sure we consume no less than 120 bytes (960 bits) in hopes that
+ * the LAME tag is found there, and not right after the Xing header */
+ bitsleft = 960 - (oldbitlen - bitlen);
+ if (bitsleft < 0) return -1;
+ else if (bitsleft > 0) {
+ mad_bit_read(&ptr, bitsleft);
+ bitlen -= bitsleft;
+ }
+
+ nomad->has_xing = 1;
+#if defined(DEBUG_XING)
+ if (nomad->xing.flags & XING_FRAMES)
+ d_print("frames: %d (xing)\n", nomad->xing.nr_frames);
+#endif
+
+ parse_lame(nomad, ptr, bitlen);
+
+ return 0;
+}
+
+/*
+ * returns:
+ * 0: eof
+ * -1: error
+ * >0: ok
+ */
+static int fill_buffer(struct nomad *nomad)
+{
+ if (nomad->stream.buffer == NULL || nomad->stream.error == MAD_ERROR_BUFLEN) {
+ ssize_t read_size, remaining, len;
+ unsigned char *read_start;
+
+ if (nomad->stream.next_frame != NULL) {
+ remaining = nomad->stream.bufend - nomad->stream.next_frame;
+ memmove(nomad->input_buffer, nomad->stream.next_frame, remaining);
+ read_start = nomad->input_buffer + remaining;
+ read_size = INPUT_BUFFER_SIZE - remaining;
+ } else {
+ read_size = INPUT_BUFFER_SIZE;
+ read_start = nomad->input_buffer;
+ remaining = 0;
+ }
+ read_size = nomad->cbs.read(nomad->datasource, read_start, read_size);
+ if (read_size == -1) {
+ if (errno != EAGAIN)
+ d_print("read error on bitstream (%d:%s)\n", errno, strerror(errno));
+ return -1;
+ }
+ if (read_size == 0) {
+ if (!nomad->readEOF) {
+ memset(nomad->input_buffer + remaining, 0, MAD_BUFFER_GUARD);
+ remaining += MAD_BUFFER_GUARD;
+ d_print("hit end of stream, appended MAD_BUFFER_GUARD zeros\n");
+ nomad->readEOF = 1;
+ }
+ else return 0;
+ }
+
+ len = read_size + remaining;
+
+ nomad->input_offset += read_size;
+
+ mad_stream_buffer(&nomad->stream, nomad->input_buffer, len);
+ nomad->stream.error = 0;
+ }
+ return 1;
+}
+
+static void handle_lost_sync(struct nomad *nomad)
+{
+ unsigned long frame;
+ int size;
+
+ frame = nomad->cur_frame;
+ if (frame == 0) {
+ /* cur_frame is not set when scanning file */
+ frame = nomad->info.nr_frames;
+ }
+
+ size = id3_tag_size((const char *)nomad->stream.this_frame,
+ nomad->stream.bufend - nomad->stream.this_frame);
+ if (size > 0) {
+ d_print("frame %ld, skipping ID3 tag (%d bytes)\n", frame, size);
+ mad_stream_skip(&nomad->stream, size);
+ } else {
+ d_print("frame %ld\n", frame);
+ }
+}
+
+
+/* Builds a seek index as the file is decoded
+ * NOTE: increases nomad->timer (current position)
+ */
+static void build_seek_index(struct nomad *nomad)
+{
+ mad_timer_t timer_now = nomad->timer;
+ off_t offset;
+ int idx;
+
+ mad_timer_add(&nomad->timer, nomad->frame.header.duration);
+
+ if (nomad->has_xing)
+ return;
+
+ if (nomad->timer.seconds < (nomad->seek_idx.size + 1) * SEEK_IDX_INTERVAL)
+ return;
+
+ /* offset = ftell() */
+ offset = nomad->input_offset;
+ /* subtract by buffer length to get offset to start of buffer */
+ offset -= (nomad->stream.bufend - nomad->input_buffer);
+ /* then add offset to the current frame */
+ offset += (nomad->stream.this_frame - nomad->input_buffer);
+
+ idx = nomad->seek_idx.size;
+
+ nomad->seek_idx.table = xrenew(struct seek_idx_entry, nomad->seek_idx.table, idx + 1);
+ nomad->seek_idx.table[idx].offset = offset;
+ nomad->seek_idx.table[idx].timer = timer_now;
+
+ nomad->seek_idx.size++;
+}
+
+static void calc_frames_fast(struct nomad *nomad)
+{
+ if (nomad->has_xing && (nomad->xing.flags & XING_FRAMES) && nomad->xing.nr_frames) {
+ nomad->info.nr_frames = nomad->xing.nr_frames;
+ mad_timer_multiply(&nomad->timer, nomad->info.nr_frames);
+ } else {
+ nomad->info.nr_frames = nomad->info.filesize /
+ (nomad->stream.next_frame - nomad->stream.this_frame);
+ mad_timer_multiply(&nomad->timer, nomad->info.nr_frames);
+ }
+}
+
+static void calc_bitrate_fast(struct nomad *nomad)
+{
+ nomad->info.vbr = nomad->has_xing ? !nomad->xing.is_info : 0;
+
+ if (nomad->has_lame && nomad->lame.vbr_method == 1)
+ nomad->info.vbr = 0;
+
+ if (nomad->has_xing && (nomad->xing.flags & XING_BYTES) && nomad->xing.bytes)
+ nomad->info.avg_bitrate = (nomad->xing.bytes * 8.0) / nomad->info.duration;
+ else
+ nomad->info.avg_bitrate = nomad->frame.header.bitrate;
+}
+
+/*
+ * fields
+ * nomad->info.avg_bitrate and
+ * nomad->info.vbr
+ * are only estimated
+ */
+static int scan(struct nomad *nomad)
+{
+ struct mad_header *header = &nomad->frame.header;
+
+ while (1) {
+ int rc;
+
+ rc = fill_buffer(nomad);
+ if (rc == -1)
+ return -1;
+ if (rc == 0)
+ break;
+
+ if (mad_frame_decode(&nomad->frame, &nomad->stream) == -1) {
+ if (nomad->stream.error == MAD_ERROR_BUFLEN)
+ continue;
+ if (!MAD_RECOVERABLE(nomad->stream.error)) {
+ d_print("unrecoverable frame level error.\n");
+ return -1;
+ }
+ if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
+ handle_lost_sync(nomad);
+ continue;
+ }
+
+ build_seek_index(nomad);
+
+ // first valid frame
+ nomad->info.sample_rate = header->samplerate;
+ nomad->info.channels = MAD_NCHANNELS(header);
+ nomad->info.layer = header->layer;
+ nomad->info.dual_channel = header->mode == MAD_MODE_DUAL_CHANNEL;
+ nomad->info.joint_stereo = header->mode == MAD_MODE_JOINT_STEREO;
+
+ xing_parse(nomad);
+ calc_frames_fast(nomad);
+ break;
+ }
+ if (nomad->info.nr_frames == 0) {
+ d_print("error: not an mp3 file!\n");
+ return -NOMAD_ERROR_FILE_FORMAT;
+ }
+ nomad->info.duration = timer_to_seconds(nomad->timer);
+ calc_bitrate_fast(nomad);
+ nomad->cur_frame = 0;
+ nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET);
+ nomad->input_offset = 0;
+ return 0;
+}
+
+static int decode(struct nomad *nomad)
+{
+ int rc;
+
+start:
+ rc = fill_buffer(nomad);
+ if (rc == -1)
+ return -1;
+ if (rc == 0)
+ return 1;
+
+ if (mad_frame_decode(&nomad->frame, &nomad->stream)) {
+ if (nomad->stream.error == MAD_ERROR_BUFLEN)
+ goto start;
+ if (!MAD_RECOVERABLE(nomad->stream.error)) {
+ d_print("unrecoverable frame level error.\n");
+ return -1;
+ }
+ if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
+ handle_lost_sync(nomad);
+ goto start;
+ }
+ nomad->cur_frame++;
+ nomad->current.bitrate_sum += nomad->frame.header.bitrate;
+ nomad->current.nr_frames++;
+ if (nomad->info.filesize != -1) {
+ build_seek_index(nomad);
+ } else {
+ mad_timer_add(&nomad->timer, nomad->frame.header.duration);
+ }
+ mad_synth_frame(&nomad->synth, &nomad->frame);
+ return 0;
+}
+
+static void init_mad(struct nomad *nomad)
+{
+ mad_stream_init(&nomad->stream);
+ nomad->stream.options |= MAD_OPTION_IGNORECRC;
+ mad_frame_init(&nomad->frame);
+ mad_synth_init(&nomad->synth);
+ mad_timer_reset(&nomad->timer);
+ nomad->cur_frame = 0;
+ nomad->i = -1;
+ nomad->input_offset = 0;
+ nomad->seen_first_frame = 0;
+ nomad->readEOF = 0;
+}
+
+static void free_mad(struct nomad *nomad)
+{
+ mad_stream_finish(&nomad->stream);
+ mad_frame_finish(&nomad->frame);
+ mad_synth_finish(nomad->synth);
+}
+
+static int do_open(struct nomad *nomad)
+{
+ int rc;
+
+ init_mad(nomad);
+ nomad->info.filesize = nomad->cbs.lseek(nomad->datasource, 0, SEEK_END);
+ if (nomad->info.filesize != -1)
+ nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET);
+ if (nomad->info.filesize == -1) {
+ rc = decode(nomad);
+ if (rc < 0)
+ goto error;
+ if (rc == 1)
+ goto eof;
+ nomad->info.sample_rate = nomad->frame.header.samplerate;
+ nomad->info.channels = MAD_NCHANNELS(&nomad->frame.header);
+ nomad->info.layer = nomad->frame.header.layer;
+ nomad->info.dual_channel = nomad->frame.header.mode == MAD_MODE_DUAL_CHANNEL;
+ nomad->info.joint_stereo = nomad->frame.header.mode == MAD_MODE_JOINT_STEREO;
+
+ /* unknown */
+ nomad->info.duration = -1.0;
+ nomad->info.nr_frames = -1;
+ nomad->info.vbr = -1;
+ nomad->info.avg_bitrate = -1;
+ } else {
+ rc = scan(nomad);
+ if (rc < 0)
+ goto error;
+ if (rc == 1)
+ goto eof;
+ free_mad(nomad);
+ init_mad(nomad);
+ }
+ d_print("\n frames: %d, br: %d b/s, sr: %d Hz, ch: %d, layer: %d, joint stereo: %d\n"
+ " dual channel: %d, vbr: %d, duration: %g s, xing: %d\n",
+ nomad->info.nr_frames, nomad->info.avg_bitrate,
+ nomad->info.sample_rate, nomad->info.channels,
+ nomad->info.layer, nomad->info.joint_stereo,
+ nomad->info.dual_channel, nomad->info.vbr,
+ nomad->info.duration,
+ nomad->has_xing);
+#if defined(DEBUG_XING)
+ if (nomad->has_xing)
+ d_print("xing: flags: 0x%x, frames: %d, bytes: %d, scale: %d\n",
+ nomad->xing.flags,
+ nomad->xing.nr_frames,
+ nomad->xing.bytes,
+ nomad->xing.scale);
+#endif
+ return 0;
+error:
+ nomad_close(nomad);
+ return rc;
+eof:
+ nomad_close(nomad);
+ return -NOMAD_ERROR_FILE_FORMAT;
+}
+
+int nomad_open_callbacks(struct nomad **nomadp, void *datasource, struct nomad_callbacks *cbs)
+{
+ struct nomad *nomad;
+
+ const struct nomad nomad_init = {
+ .datasource = datasource,
+ .cbs = {
+ .read = cbs->read,
+ .lseek = cbs->lseek,
+ .close = cbs->close
+ }
+ };
+
+ nomad = xnew(struct nomad, 1);
+ *nomad = nomad_init;
+ nomad->lame.peak = nomad->lame.trackGain = nomad->lame.albumGain = strtof("NAN", NULL);
+ *nomadp = nomad;
+ /* on error do_open calls nomad_close */
+ return do_open(nomad);
+}
+
+void nomad_close(struct nomad *nomad)
+{
+ free_mad(nomad);
+ nomad->cbs.close(nomad->datasource);
+ free(nomad->seek_idx.table);
+ free(nomad);
+}
+
+int nomad_read(struct nomad *nomad, char *buffer, int count)
+{
+ int i, j, size, psize, to;
+
+ if (nomad->i == -1) {
+ int rc;
+
+next_frame:
+ rc = decode(nomad);
+ if (rc < 0)
+ return rc;
+ if (rc == 1)
+ return 0;
+ nomad->i = 0;
+ }
+
+ if (nomad->has_lame) {
+ /* skip samples at start for gapless playback */
+ if (nomad->start_drop_frames) {
+ nomad->start_drop_frames--;
+ /* XING header is an empty frame we want to skip */
+ if (!nomad->seen_first_frame) {
+ nomad->cur_frame--;
+ nomad->seen_first_frame = 1;
+ }
+#if defined(DEBUG_LAME)
+ d_print("skipped a frame at start\n");
+#endif
+ goto next_frame;
+ }
+ if (nomad->start_drop_samples) {
+ if (nomad->start_drop_samples < nomad->synth.pcm.length) {
+ nomad->i += nomad->start_drop_samples;
+ nomad->start_drop_samples = 0;
+ /* Take advantage of the fact that this block is only executed once per file, and
+ calculate the # of samples/frames to skip at the end. Note that synth.pcm.length
+ is needed for the calculation. */
+ nomad->end_drop_frames = nomad->end_drop_samples / nomad->synth.pcm.length;
+ nomad->end_drop_samples = nomad->end_drop_samples % nomad->synth.pcm.length;
+#if defined(DEBUG_LAME)
+ d_print("skipped %d samples at start\n", nomad->i);
+ d_print("will skip %d samples and %d frame(s) at end\n",
+ nomad->end_drop_samples, nomad->end_drop_frames);
+#endif
+ }
+ else {
+ nomad->start_drop_samples -= nomad->synth.pcm.length;
+#if defined(DEBUG_LAME)
+ d_print("skipped %d samples at start and moving to next frame\n", nomad->synth.pcm.length);
+#endif
+ goto next_frame;
+ }
+ }
+ /* skip samples/frames at end for gapless playback */
+ if (nomad->cur_frame == (nomad->xing.nr_frames + 1 - nomad->end_drop_frames)) {
+#if defined(DEBUG_LAME)
+ d_print("skipped %d frame(s) at end\n", nomad->end_drop_frames);
+#endif
+ return 0;
+ }
+ }
+
+ psize = nomad->info.channels * 16 / 8;
+ size = (nomad->synth.pcm.length - nomad->i) * psize;
+
+ if (size > count) {
+ to = nomad->i + count / psize;
+ } else {
+ to = nomad->synth.pcm.length;
+ }
+ j = 0;
+ for (i = nomad->i; i < to; i++) {
+ short sample;
+
+ /* skip samples/frames at end for gapless playback */
+ if (nomad->has_lame
+ && nomad->end_drop_samples
+ && (nomad->cur_frame == (nomad->xing.nr_frames - nomad->end_drop_frames))
+ && i == (nomad->synth.pcm.length - nomad->end_drop_samples)) {
+ nomad->i = -1;
+#if defined(DEBUG_LAME)
+ d_print("skipped %d samples at end of frame %d\n", nomad->end_drop_samples, (int)nomad->cur_frame);
+#endif
+ return j;
+ }
+ sample = scale(nomad->synth.pcm.samples[0][i]);
+ buffer[j++] = (sample >> 0) & 0xff;
+ buffer[j++] = (sample >> 8) & 0xff;
+
+ if (nomad->info.channels == 2) {
+ sample = scale(nomad->synth.pcm.samples[1][i]);
+ buffer[j++] = (sample >> 0) & 0xff;
+ buffer[j++] = (sample >> 8) & 0xff;
+ }
+ }
+ if (to != nomad->synth.pcm.length) {
+ nomad->i = i;
+ } else {
+ nomad->i = -1;
+ }
+ return j;
+}
+
+static int nomad_time_seek_accurate(struct nomad *nomad, double pos)
+{
+ int rc;
+
+ /* seek to beginning of file and search frame-by-frame */
+ if (nomad->cbs.lseek(nomad->datasource, 0, SEEK_SET) == -1)
+ return -1;
+
+ /* XING header should NOT be counted - if we're here, we know it's present */
+ nomad->cur_frame = -1;
+
+ while (timer_to_seconds(nomad->timer) < pos) {
+ rc = fill_buffer(nomad);
+ if (rc == -1)
+ return -1;
+ if (rc == 0)
+ return 1;
+
+ if (mad_header_decode(&nomad->frame.header, &nomad->stream)) {
+ if (nomad->stream.error == MAD_ERROR_BUFLEN)
+ continue;
+ if (!MAD_RECOVERABLE(nomad->stream.error)) {
+ d_print("unrecoverable frame level error.\n");
+ return -1;
+ }
+ if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
+ handle_lost_sync(nomad);
+ continue;
+ }
+ nomad->cur_frame++;
+ mad_timer_add(&nomad->timer, nomad->frame.header.duration);
+ }
+#if defined(DEBUG_LAME)
+ d_print("seeked to %g = %g\n", pos, timer_to_seconds(nomad->timer));
+#endif
+ return 0;
+}
+
+int nomad_time_seek(struct nomad *nomad, double pos)
+{
+ off_t offset = 0;
+
+ if (pos < 0.0 || pos > nomad->info.duration) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (nomad->info.filesize == -1) {
+ errno = ESPIPE;
+ return -1;
+ }
+ free_mad(nomad);
+ init_mad(nomad);
+
+ /* if file has a LAME header, perform frame-accurate seek for gapless playback */
+ if (nomad->has_lame) {
+ return nomad_time_seek_accurate(nomad, pos);
+ } else if (nomad->has_xing) {
+ /* calculate seek offset */
+ /* seek to truncate(pos / duration * 100) / 100 * duration */
+ double k, tmp_pos;
+ int ki;
+
+ k = pos / nomad->info.duration * 100.0;
+ ki = k;
+ tmp_pos = ((double)ki) / 100.0 * nomad->info.duration;
+ nomad->timer.seconds = (signed int)tmp_pos;
+ nomad->timer.fraction = (tmp_pos - (double)nomad->timer.seconds) * MAD_TIMER_RESOLUTION;
+#if defined(DEBUG_XING)
+ d_print("seeking to %g = %g %d%%\n",
+ pos,
+ timer_to_seconds(nomad->timer),
+ ki);
+#endif
+ offset = ((unsigned long long)nomad->xing.toc[ki] * nomad->xing.bytes) / 256;
+ } else if (nomad->seek_idx.size > 0) {
+ int idx = (int)(pos / SEEK_IDX_INTERVAL) - 1;
+
+ if (idx > nomad->seek_idx.size - 1)
+ idx = nomad->seek_idx.size - 1;
+
+ if (idx >= 0) {
+ offset = nomad->seek_idx.table[idx].offset;
+ nomad->timer = nomad->seek_idx.table[idx].timer;
+ }
+ }
+ if (nomad->cbs.lseek(nomad->datasource, offset, SEEK_SET) == -1)
+ return -1;
+
+ nomad->input_offset = offset;
+ while (timer_to_seconds(nomad->timer) < pos) {
+ int rc;
+
+ rc = fill_buffer(nomad);
+ if (rc == -1)
+ return -1;
+ if (rc == 0)
+ return 0;
+
+ if (mad_header_decode(&nomad->frame.header, &nomad->stream) == 0) {
+ build_seek_index(nomad);
+ } else {
+ if (!MAD_RECOVERABLE(nomad->stream.error) && nomad->stream.error != MAD_ERROR_BUFLEN) {
+ d_print("unrecoverable frame level error.\n");
+ return -1;
+ }
+ if (nomad->stream.error == MAD_ERROR_LOSTSYNC)
+ handle_lost_sync(nomad);
+ }
+ }
+#if defined(DEBUG_XING)
+ if (nomad->has_xing)
+ d_print("seeked to %g = %g\n", pos, timer_to_seconds(nomad->timer));
+#endif
+ return 0;
+}
+
+const struct nomad_xing *nomad_xing(struct nomad *nomad)
+{
+ return nomad->has_xing ? &nomad->xing : NULL;
+}
+
+const struct nomad_lame *nomad_lame(struct nomad *nomad)
+{
+ return nomad->has_lame ? &nomad->lame : NULL;
+}
+
+const struct nomad_info *nomad_info(struct nomad *nomad)
+{
+ return &nomad->info;
+}
+
+long nomad_current_bitrate(struct nomad *nomad)
+{
+ long bitrate = -1;
+ if (nomad->current.nr_frames > 0) {
+ bitrate = nomad->current.bitrate_sum / nomad->current.nr_frames;
+ nomad->current.bitrate_sum = 0;
+ nomad->current.nr_frames = 0;
+ }
+ return bitrate;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NOMAD_H
+#define _NOMAD_H
+
+#include <sys/types.h>
+
+#ifndef __GNUC__
+#include <unistd.h>
+#endif
+
+/* default callbacks use read, lseek, close */
+struct nomad_callbacks {
+ ssize_t (*read)(void *datasource, void *buffer, size_t count);
+ off_t (*lseek)(void *datasource, off_t offset, int whence);
+ int (*close)(void *datasource);
+};
+
+enum {
+ XING_FRAMES = 0x00000001L,
+ XING_BYTES = 0x00000002L,
+ XING_TOC = 0x00000004L,
+ XING_SCALE = 0x00000008L
+};
+
+struct nomad_xing {
+ unsigned int is_info : 1;
+ unsigned int flags;
+ unsigned int nr_frames;
+ unsigned int bytes;
+ unsigned int scale;
+ unsigned char toc[100];
+};
+
+struct nomad_lame {
+ char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */
+ int vbr_method; /* VBR method */
+ float peak; /* replaygain peak */
+ float trackGain; /* replaygain track gain */
+ float albumGain; /* replaygain album gain */
+ int encoderDelay; /* # of added samples at start of mp3 */
+ int encoderPadding; /* # of added samples at end of mp3 */
+};
+
+/* always 16-bit signed little-endian */
+struct nomad_info {
+ double duration;
+ int sample_rate;
+ int channels;
+ int nr_frames;
+ int layer;
+ /* guessed */
+ int vbr;
+ /* guessed */
+ int avg_bitrate;
+ /* -1 if file not seekable */
+ off_t filesize;
+ unsigned int joint_stereo : 1;
+ unsigned int dual_channel : 1;
+};
+
+enum {
+ NOMAD_ERROR_SUCCESS,
+ NOMAD_ERROR_ERRNO,
+ NOMAD_ERROR_FILE_FORMAT
+};
+
+struct nomad;
+
+/* -NOMAD_ERROR_ERRNO -NOMAD_ERROR_FILE_FORMAT */
+int nomad_open_callbacks(struct nomad **nomadp, void *datasource,
+ struct nomad_callbacks *cbs);
+
+void nomad_close(struct nomad *nomad);
+
+/* -NOMAD_ERROR_ERRNO */
+int nomad_read(struct nomad *nomad, char *buffer, int count);
+
+/* -NOMAD_ERROR_ERRNO */
+int nomad_time_seek(struct nomad *nomad, double pos);
+
+const struct nomad_xing *nomad_xing(struct nomad *nomad);
+const struct nomad_lame *nomad_lame(struct nomad *nomad);
+const struct nomad_info *nomad_info(struct nomad *nomad);
+long nomad_current_bitrate(struct nomad *nomad);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _OP_H
+#define _OP_H
+
+#include "sf.h"
+#include "channelmap.h"
+
+#ifndef __GNUC__
+#include <fcntl.h>
+#endif
+
+enum {
+ /* no error */
+ OP_ERROR_SUCCESS,
+ /* system error (error code in errno) */
+ OP_ERROR_ERRNO,
+ /* no such plugin */
+ OP_ERROR_NO_PLUGIN,
+ /* plugin not initialized */
+ OP_ERROR_NOT_INITIALIZED,
+ /* function not supported */
+ OP_ERROR_NOT_SUPPORTED,
+ /* mixer not open */
+ OP_ERROR_NOT_OPEN,
+ /* plugin does not support the sample format */
+ OP_ERROR_SAMPLE_FORMAT,
+ /* plugin does not have this option */
+ OP_ERROR_NOT_OPTION,
+ /* */
+ OP_ERROR_INTERNAL
+};
+
+struct output_plugin_ops {
+ int (*init)(void);
+ int (*exit)(void);
+ int (*open)(sample_format_t sf, const channel_position_t *channel_map);
+ int (*close)(void);
+ int (*drop)(void);
+ int (*write)(const char *buffer, int count);
+ int (*buffer_space)(void);
+
+ /* these can be NULL */
+ int (*pause)(void);
+ int (*unpause)(void);
+
+ int (*set_option)(int key, const char *val);
+ int (*get_option)(int key, char **val);
+};
+
+/* symbols exported by plugin */
+extern const struct output_plugin_ops op_pcm_ops;
+extern const char * const op_pcm_options[];
+extern const int op_priority;
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "options.h"
+#include "list.h"
+#include "utils.h"
+#include "xmalloc.h"
+#include "player.h"
+#include "buffer.h"
+#include "ui_curses.h"
+#include "cmus.h"
+#include "misc.h"
+#include "lib.h"
+#include "pl.h"
+#include "browser.h"
+#include "keys.h"
+#include "filters.h"
+#include "command_mode.h"
+#include "file.h"
+#include "prog.h"
+#include "output.h"
+#include "input.h"
+#ifdef HAVE_CONFIG
+#include "config/datadir.h"
+#endif
+#include "track_info.h"
+#include "cache.h"
+#include "debug.h"
+#include "discid.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <strings.h>
+
+#if defined(__sun__)
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+/* initialized option variables */
+
+char *cdda_device = NULL;
+char *output_plugin = NULL;
+char *status_display_program = NULL;
+char *server_password;
+int auto_reshuffle = 1;
+int confirm_run = 1;
+int resume_cmus = 0;
+int show_hidden = 0;
+int show_current_bitrate = 0;
+int show_remaining_time = 0;
+int set_term_title = 1;
+int wrap_search = 1;
+int play_library = 1;
+int repeat = 0;
+int shuffle = 0;
+int display_artist_sort_name;
+int smart_artist_sort = 1;
+int scroll_offset = 2;
+int skip_track_info = 0;
+
+int colors[NR_COLORS] = {
+ -1,
+ -1,
+ COLOR_RED | BRIGHT,
+ COLOR_YELLOW | BRIGHT,
+
+ COLOR_BLUE,
+ COLOR_WHITE,
+ COLOR_BLACK,
+ COLOR_BLUE,
+
+ COLOR_WHITE | BRIGHT,
+ -1,
+ COLOR_YELLOW | BRIGHT,
+ COLOR_BLUE,
+
+ COLOR_YELLOW | BRIGHT,
+ COLOR_BLUE | BRIGHT,
+ -1,
+ COLOR_WHITE,
+
+ COLOR_YELLOW | BRIGHT,
+ COLOR_WHITE,
+ COLOR_BLACK,
+ COLOR_BLUE,
+
+ COLOR_WHITE | BRIGHT,
+ COLOR_BLUE,
+ COLOR_WHITE | BRIGHT
+};
+
+int attrs[NR_ATTRS] = {
+ A_NORMAL,
+ A_NORMAL,
+ A_NORMAL,
+ A_NORMAL,
+ A_NORMAL,
+ A_NORMAL,
+ A_NORMAL,
+ A_NORMAL,
+ A_NORMAL
+};
+
+/* uninitialized option variables */
+char *track_win_format = NULL;
+char *track_win_format_va = NULL;
+char *track_win_alt_format = NULL;
+char *list_win_format = NULL;
+char *list_win_format_va = NULL;
+char *list_win_alt_format = NULL;
+char *current_format = NULL;
+char *current_alt_format = NULL;
+char *window_title_format = NULL;
+char *window_title_alt_format = NULL;
+char *id3_default_charset = NULL;
+char *icecast_default_charset = NULL;
+
+static void buf_int(char *buf, int val)
+{
+ snprintf(buf, OPTION_MAX_SIZE, "%d", val);
+}
+
+static int parse_int(const char *buf, int minval, int maxval, int *val)
+{
+ long int tmp;
+
+ if (str_to_int(buf, &tmp) == -1 || tmp < minval || tmp > maxval) {
+ error_msg("integer in range %d..%d expected", minval, maxval);
+ return 0;
+ }
+ *val = tmp;
+ return 1;
+}
+
+int parse_enum(const char *buf, int minval, int maxval, const char * const names[], int *val)
+{
+ long int tmp;
+ int i;
+
+ if (str_to_int(buf, &tmp) == 0) {
+ if (tmp < minval || tmp > maxval)
+ goto err;
+ *val = tmp;
+ return 1;
+ }
+
+ for (i = 0; names[i]; i++) {
+ if (strcasecmp(buf, names[i]) == 0) {
+ *val = i + minval;
+ return 1;
+ }
+ }
+err:
+ error_msg("name or integer in range %d..%d expected", minval, maxval);
+ return 0;
+}
+
+static const char * const bool_names[] = {
+ "false", "true", NULL
+};
+
+static int parse_bool(const char *buf, int *val)
+{
+ return parse_enum(buf, 0, 1, bool_names, val);
+}
+
+/* this is used as id in struct cmus_opt */
+enum format_id {
+ FMT_CURRENT_ALT,
+ FMT_PLAYLIST_ALT,
+ FMT_TITLE_ALT,
+ FMT_TRACKWIN_ALT,
+ FMT_CURRENT,
+ FMT_PLAYLIST,
+ FMT_PLAYLIST_VA,
+ FMT_TITLE,
+ FMT_TRACKWIN,
+ FMT_TRACKWIN_VA,
+
+ NR_FMTS
+};
+
+/* callbacks for normal options {{{ */
+
+static void get_device(unsigned int id, char *buf)
+{
+ strcpy(buf, cdda_device);
+}
+
+static void set_device(unsigned int id, const char *buf)
+{
+ free(cdda_device);
+ cdda_device = expand_filename(buf);
+}
+
+#define SECOND_SIZE (44100 * 16 / 8 * 2)
+static void get_buffer_seconds(unsigned int id, char *buf)
+{
+ buf_int(buf, (player_get_buffer_chunks() * CHUNK_SIZE + SECOND_SIZE / 2) / SECOND_SIZE);
+}
+
+static void set_buffer_seconds(unsigned int id, const char *buf)
+{
+ int sec;
+
+ if (parse_int(buf, 1, 300, &sec))
+ player_set_buffer_chunks((sec * SECOND_SIZE + CHUNK_SIZE / 2) / CHUNK_SIZE);
+}
+
+static void get_scroll_offset(unsigned int id, char *buf)
+{
+ buf_int(buf, scroll_offset);
+}
+
+static void set_scroll_offset(unsigned int id, const char *buf)
+{
+ int offset;
+
+ if (parse_int(buf, 0, 9999, &offset))
+ scroll_offset = offset;
+}
+
+static void get_id3_default_charset(unsigned int id, char *buf)
+{
+ strcpy(buf, id3_default_charset);
+}
+
+static void get_icecast_default_charset(unsigned int id, char *buf)
+{
+ strcpy(buf, icecast_default_charset);
+}
+
+static void set_id3_default_charset(unsigned int id, const char *buf)
+{
+ free(id3_default_charset);
+ id3_default_charset = xstrdup(buf);
+}
+
+static void set_icecast_default_charset(unsigned int id, const char *buf)
+{
+ free(icecast_default_charset);
+ icecast_default_charset = xstrdup(buf);
+}
+
+static const struct {
+ const char *str;
+ sort_key_t key;
+} sort_key_map[] = {
+ { "artist", SORT_ARTIST },
+ { "album", SORT_ALBUM },
+ { "title", SORT_TITLE },
+ { "tracknumber", SORT_TRACKNUMBER },
+ { "discnumber", SORT_DISCNUMBER },
+ { "date", SORT_DATE },
+ { "originaldate", SORT_ORIGINALDATE },
+ { "genre", SORT_GENRE },
+ { "comment", SORT_COMMENT },
+ { "albumartist", SORT_ALBUMARTIST },
+ { "filename", SORT_FILENAME },
+ { "filemtime", SORT_FILEMTIME },
+ { "rg_track_gain", SORT_RG_TRACK_GAIN },
+ { "rg_track_peak", SORT_RG_TRACK_PEAK },
+ { "rg_album_gain", SORT_RG_ALBUM_GAIN },
+ { "rg_album_peak", SORT_RG_ALBUM_PEAK },
+ { "bitrate", SORT_BITRATE },
+ { "codec", SORT_CODEC },
+ { "codec_profile", SORT_CODEC_PROFILE },
+ { "media", SORT_MEDIA },
+ { NULL, SORT_INVALID }
+};
+
+static sort_key_t *parse_sort_keys(const char *value)
+{
+ sort_key_t *keys;
+ const char *s, *e;
+ int size = 4;
+ int pos = 0;
+
+ size = 4;
+ keys = xnew(sort_key_t, size);
+
+ s = value;
+ while (1) {
+ char buf[32];
+ int i, len;
+
+ while (*s == ' ')
+ s++;
+
+ e = s;
+ while (*e && *e != ' ')
+ e++;
+
+ len = e - s;
+ if (len == 0)
+ break;
+ if (len > 31)
+ len = 31;
+
+ memcpy(buf, s, len);
+ buf[len] = 0;
+ s = e;
+
+ for (i = 0; ; i++) {
+ if (sort_key_map[i].str == NULL) {
+ error_msg("invalid sort key '%s'", buf);
+ free(keys);
+ return NULL;
+ }
+
+ if (strcmp(buf, sort_key_map[i].str) == 0)
+ break;
+ }
+ if (pos == size - 1) {
+ size *= 2;
+ keys = xrenew(sort_key_t, keys, size);
+ }
+ keys[pos++] = sort_key_map[i].key;
+ }
+ keys[pos] = SORT_INVALID;
+ return keys;
+}
+
+static const char *sort_key_to_str(sort_key_t key)
+{
+ int i;
+ for (i = 0; sort_key_map[i].str; i++) {
+ if (sort_key_map[i].key == key)
+ return sort_key_map[i].str;
+ }
+ return NULL;
+}
+
+static void sort_keys_to_str(const sort_key_t *keys, char *buf, size_t bufsize)
+{
+ int i, pos = 0;
+
+ for (i = 0; keys[i] != SORT_INVALID; i++) {
+ const char *key = sort_key_to_str(keys[i]);
+ int len = strlen(key);
+
+ if ((int)bufsize - pos - len - 2 < 0)
+ break;
+
+ memcpy(buf + pos, key, len);
+ pos += len;
+ buf[pos++] = ' ';
+ }
+ if (pos > 0)
+ pos--;
+ buf[pos] = 0;
+}
+
+static void get_lib_sort(unsigned int id, char *buf)
+{
+ strcpy(buf, lib_editable.sort_str);
+}
+
+static void set_lib_sort(unsigned int id, const char *buf)
+{
+ sort_key_t *keys = parse_sort_keys(buf);
+
+ if (keys) {
+ editable_lock();
+ editable_set_sort_keys(&lib_editable, keys);
+ editable_unlock();
+ sort_keys_to_str(keys, lib_editable.sort_str, sizeof(lib_editable.sort_str));
+ }
+}
+
+static void get_pl_sort(unsigned int id, char *buf)
+{
+ strcpy(buf, pl_editable.sort_str);
+}
+
+static void set_pl_sort(unsigned int id, const char *buf)
+{
+ sort_key_t *keys = parse_sort_keys(buf);
+
+ if (keys) {
+ editable_lock();
+ editable_set_sort_keys(&pl_editable, keys);
+ editable_unlock();
+ sort_keys_to_str(keys, pl_editable.sort_str, sizeof(pl_editable.sort_str));
+ }
+}
+
+static void get_output_plugin(unsigned int id, char *buf)
+{
+ const char *value = op_get_current();
+
+ if (value)
+ strcpy(buf, value);
+}
+
+static void set_output_plugin(unsigned int id, const char *buf)
+{
+ if (ui_initialized) {
+ if (!soft_vol)
+ mixer_close();
+ player_set_op(buf);
+ if (!soft_vol)
+ mixer_open();
+ } else {
+ /* must set it later manually */
+ output_plugin = xstrdup(buf);
+ }
+}
+
+static void get_passwd(unsigned int id, char *buf)
+{
+ if (server_password)
+ strcpy(buf, server_password);
+}
+
+static void set_passwd(unsigned int id, const char *buf)
+{
+ int len = strlen(buf);
+
+ if (len == 0) {
+ free(server_password);
+ server_password = NULL;
+ } else if (len < 6) {
+ error_msg("unsafe password");
+ } else {
+ free(server_password);
+ server_password = xstrdup(buf);
+ }
+}
+
+static void get_replaygain_preamp(unsigned int id, char *buf)
+{
+ sprintf(buf, "%f", replaygain_preamp);
+}
+
+static void set_replaygain_preamp(unsigned int id, const char *buf)
+{
+ double val;
+ char *end;
+
+ val = strtod(buf, &end);
+ if (end == buf) {
+ error_msg("floating point number expected (dB)");
+ return;
+ }
+ player_set_rg_preamp(val);
+}
+
+static void get_softvol_state(unsigned int id, char *buf)
+{
+ sprintf(buf, "%d %d", soft_vol_l, soft_vol_r);
+}
+
+static void set_softvol_state(unsigned int id, const char *buf)
+{
+ char buffer[OPTION_MAX_SIZE];
+ char *ptr;
+ long int l, r;
+
+ strcpy(buffer, buf);
+ ptr = strchr(buffer, ' ');
+ if (!ptr)
+ goto err;
+ while (*ptr == ' ')
+ *ptr++ = 0;
+
+ if (str_to_int(buffer, &l) == -1 || l < 0 || l > 100)
+ goto err;
+ if (str_to_int(ptr, &r) == -1 || r < 0 || r > 100)
+ goto err;
+
+ player_set_soft_volume(l, r);
+ return;
+err:
+ error_msg("two integers in range 0..100 expected");
+}
+
+static void get_status_display_program(unsigned int id, char *buf)
+{
+ if (status_display_program)
+ strcpy(buf, status_display_program);
+}
+
+static void set_status_display_program(unsigned int id, const char *buf)
+{
+ free(status_display_program);
+ status_display_program = NULL;
+ if (buf[0])
+ status_display_program = expand_filename(buf);
+}
+
+/* }}} */
+
+/* callbacks for toggle options {{{ */
+
+static void get_auto_reshuffle(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[auto_reshuffle]);
+}
+
+static void set_auto_reshuffle(unsigned int id, const char *buf)
+{
+ parse_bool(buf, &auto_reshuffle);
+}
+
+static void toggle_auto_reshuffle(unsigned int id)
+{
+ auto_reshuffle ^= 1;
+}
+
+static void get_continue(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[player_cont]);
+}
+
+static void set_continue(unsigned int id, const char *buf)
+{
+ if (!parse_bool(buf, &player_cont))
+ return;
+ update_statusline();
+}
+
+static void toggle_continue(unsigned int id)
+{
+ player_cont ^= 1;
+ update_statusline();
+}
+
+static void get_repeat_current(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[player_repeat_current]);
+}
+
+static void set_repeat_current(unsigned int id, const char *buf)
+{
+ if (!parse_bool(buf, &player_repeat_current))
+ return;
+ update_statusline();
+}
+
+static void toggle_repeat_current(unsigned int id)
+{
+ player_repeat_current ^= 1;
+ update_statusline();
+}
+
+static void get_confirm_run(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[confirm_run]);
+}
+
+static void set_confirm_run(unsigned int id, const char *buf)
+{
+ parse_bool(buf, &confirm_run);
+}
+
+static void toggle_confirm_run(unsigned int id)
+{
+ confirm_run ^= 1;
+}
+
+const char * const view_names[NR_VIEWS + 1] = {
+ "tree", "sorted", "playlist", "queue", "browser", "filters", "settings", NULL
+};
+
+static void get_play_library(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[play_library]);
+}
+
+static void set_play_library(unsigned int id, const char *buf)
+{
+ if (!parse_bool(buf, &play_library))
+ return;
+ update_statusline();
+}
+
+static void toggle_play_library(unsigned int id)
+{
+ play_library ^= 1;
+ update_statusline();
+}
+
+static void get_play_sorted(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[play_sorted]);
+}
+
+static void set_play_sorted(unsigned int id, const char *buf)
+{
+ int tmp;
+
+ if (!parse_bool(buf, &tmp))
+ return;
+
+ play_sorted = tmp;
+ update_statusline();
+}
+
+static void toggle_play_sorted(unsigned int id)
+{
+ editable_lock();
+ play_sorted = play_sorted ^ 1;
+
+ /* shuffle would override play_sorted... */
+ if (play_sorted) {
+ /* play_sorted makes no sense in playlist */
+ play_library = 1;
+ shuffle = 0;
+ }
+
+ editable_unlock();
+ update_statusline();
+}
+
+static void get_smart_artist_sort(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[smart_artist_sort]);
+}
+
+static void set_smart_artist_sort(unsigned int id, const char *buf)
+{
+ if (parse_bool(buf, &smart_artist_sort))
+ tree_sort_artists();
+}
+
+static void toggle_smart_artist_sort(unsigned int id)
+{
+ smart_artist_sort ^= 1;
+ tree_sort_artists();
+}
+
+static void get_display_artist_sort_name(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[display_artist_sort_name]);
+}
+
+static void set_display_artist_sort_name(unsigned int id, const char *buf)
+{
+ parse_bool(buf, &display_artist_sort_name);
+ lib_tree_win->changed = 1;
+}
+
+static void toggle_display_artist_sort_name(unsigned int id)
+{
+ display_artist_sort_name ^= 1;
+ lib_tree_win->changed = 1;
+}
+
+const char * const aaa_mode_names[] = {
+ "all", "artist", "album", NULL
+};
+
+static void get_aaa_mode(unsigned int id, char *buf)
+{
+ strcpy(buf, aaa_mode_names[aaa_mode]);
+}
+
+static void set_aaa_mode(unsigned int id, const char *buf)
+{
+ int tmp;
+
+ if (!parse_enum(buf, 0, 2, aaa_mode_names, &tmp))
+ return;
+
+ aaa_mode = tmp;
+ update_statusline();
+}
+
+static void toggle_aaa_mode(unsigned int id)
+{
+ editable_lock();
+
+ /* aaa mode makes no sense in playlist */
+ play_library = 1;
+
+ aaa_mode++;
+ aaa_mode %= 3;
+ editable_unlock();
+ update_statusline();
+}
+
+static void get_repeat(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[repeat]);
+}
+
+static void set_repeat(unsigned int id, const char *buf)
+{
+ if (!parse_bool(buf, &repeat))
+ return;
+ update_statusline();
+}
+
+static void toggle_repeat(unsigned int id)
+{
+ repeat ^= 1;
+ update_statusline();
+}
+
+static const char * const replaygain_names[] = {
+ "disabled", "track", "album", "track-preferred", "album-preferred", NULL
+};
+
+static void get_replaygain(unsigned int id, char *buf)
+{
+ strcpy(buf, replaygain_names[replaygain]);
+}
+
+static void set_replaygain(unsigned int id, const char *buf)
+{
+ int tmp;
+
+ if (!parse_enum(buf, 0, 4, replaygain_names, &tmp))
+ return;
+ player_set_rg(tmp);
+}
+
+static void toggle_replaygain(unsigned int id)
+{
+ player_set_rg((replaygain + 1) % 5);
+}
+
+static void get_replaygain_limit(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[replaygain_limit]);
+}
+
+static void set_replaygain_limit(unsigned int id, const char *buf)
+{
+ int tmp;
+
+ if (!parse_bool(buf, &tmp))
+ return;
+ player_set_rg_limit(tmp);
+}
+
+static void toggle_replaygain_limit(unsigned int id)
+{
+ player_set_rg_limit(replaygain_limit ^ 1);
+}
+
+static void get_resume(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[resume_cmus]);
+}
+
+static void set_resume(unsigned int id, const char *buf)
+{
+ parse_bool(buf, &resume_cmus);
+}
+
+static void toggle_resume(unsigned int id)
+{
+ resume_cmus ^= 1;
+}
+
+static void get_show_hidden(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[show_hidden]);
+}
+
+static void set_show_hidden(unsigned int id, const char *buf)
+{
+ if (!parse_bool(buf, &show_hidden))
+ return;
+ browser_reload();
+}
+
+static void toggle_show_hidden(unsigned int id)
+{
+ show_hidden ^= 1;
+ browser_reload();
+}
+
+static void get_show_current_bitrate(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[show_current_bitrate]);
+}
+
+static void set_show_current_bitrate(unsigned int id, const char *buf)
+{
+ if (parse_bool(buf, &show_current_bitrate))
+ update_statusline();
+}
+
+static void toggle_show_current_bitrate(unsigned int id)
+{
+ show_current_bitrate ^= 1;
+ update_statusline();
+}
+
+static void get_show_remaining_time(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[show_remaining_time]);
+}
+
+static void set_show_remaining_time(unsigned int id, const char *buf)
+{
+ if (!parse_bool(buf, &show_remaining_time))
+ return;
+ update_statusline();
+}
+
+static void toggle_show_remaining_time(unsigned int id)
+{
+ show_remaining_time ^= 1;
+ update_statusline();
+}
+
+static void get_set_term_title(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[set_term_title]);
+}
+
+static void set_set_term_title(unsigned int id, const char *buf)
+{
+ parse_bool(buf, &set_term_title);
+}
+
+static void toggle_set_term_title(unsigned int id)
+{
+ set_term_title ^= 1;
+}
+
+static void get_shuffle(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[shuffle]);
+}
+
+static void set_shuffle(unsigned int id, const char *buf)
+{
+ if (!parse_bool(buf, &shuffle))
+ return;
+ update_statusline();
+}
+
+static void toggle_shuffle(unsigned int id)
+{
+ shuffle ^= 1;
+ update_statusline();
+}
+
+static void get_softvol(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[soft_vol]);
+}
+
+static void do_set_softvol(int soft)
+{
+ if (!soft_vol)
+ mixer_close();
+ player_set_soft_vol(soft);
+ if (!soft_vol)
+ mixer_open();
+ update_statusline();
+}
+
+static void set_softvol(unsigned int id, const char *buf)
+{
+ int soft;
+
+ if (!parse_bool(buf, &soft))
+ return;
+ do_set_softvol(soft);
+}
+
+static void toggle_softvol(unsigned int id)
+{
+ do_set_softvol(soft_vol ^ 1);
+}
+
+static void get_wrap_search(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[wrap_search]);
+}
+
+static void set_wrap_search(unsigned int id, const char *buf)
+{
+ parse_bool(buf, &wrap_search);
+}
+
+static void toggle_wrap_search(unsigned int id)
+{
+ wrap_search ^= 1;
+}
+
+static void get_skip_track_info(unsigned int id, char *buf)
+{
+ strcpy(buf, bool_names[skip_track_info]);
+}
+
+static void set_skip_track_info(unsigned int id, const char *buf)
+{
+ parse_bool(buf, &skip_track_info);
+}
+
+static void toggle_skip_track_info(unsigned int id)
+{
+ skip_track_info ^= 1;
+}
+
+
+/* }}} */
+
+/* special callbacks (id set) {{{ */
+
+static const char * const color_enum_names[1 + 8 * 2 + 1] = {
+ "default",
+ "black", "red", "green", "yellow", "blue", "magenta", "cyan", "gray",
+ "darkgray", "lightred", "lightgreen", "lightyellow", "lightblue", "lightmagenta", "lightcyan", "white",
+ NULL
+};
+
+static void get_color(unsigned int id, char *buf)
+{
+ int val;
+
+ val = colors[id];
+ if (val < 16) {
+ strcpy(buf, color_enum_names[val + 1]);
+ } else {
+ buf_int(buf, val);
+ }
+}
+
+static void set_color(unsigned int id, const char *buf)
+{
+ int color;
+
+ if (!parse_enum(buf, -1, 255, color_enum_names, &color))
+ return;
+
+ colors[id] = color;
+ update_colors();
+ update_full();
+}
+
+static const char * const attr_enum_names[6 + 1] = {
+ "default",
+ "standout", "underline", "reverse", "blink", "bold",
+ NULL
+};
+
+static void get_attr(unsigned int id, char *buf)
+{
+ int attr = attrs[id];
+
+ if (attr == 0) {
+ strcpy(buf, "default");
+ return;
+ }
+
+ if (attr & A_STANDOUT)
+ strcat(buf, "standout|");
+
+ if (attr & A_UNDERLINE)
+ strcat(buf, "underline|");
+
+ if (attr & A_REVERSE)
+ strcat(buf, "reverse|");
+
+ if (attr & A_BLINK)
+ strcat(buf, "blink|");
+
+ if (attr & A_BOLD)
+ strcat(buf, "bold|");
+
+ buf[strlen(buf) - 1] = '\0';
+}
+
+static void set_attr(unsigned int id, const char *buf)
+{
+ int attr = 0;
+ size_t i = 0;
+ size_t offset = 0;
+ size_t length = 0;
+ char* current;
+
+ do {
+ if (buf[i] == '|' || buf[i] == '\0') {
+ current = xstrndup(&buf[offset], length);
+
+ if (strcmp(current, "default") == 0)
+ attr |= A_NORMAL;
+ else if (strcmp(current, "standout") == 0)
+ attr |= A_STANDOUT;
+ else if (strcmp(current, "underline") == 0)
+ attr |= A_UNDERLINE;
+ else if (strcmp(current, "reverse") == 0)
+ attr |= A_REVERSE;
+ else if (strcmp(current, "blink") == 0)
+ attr |= A_BLINK;
+ else if (strcmp(current, "bold") == 0)
+ attr |= A_BOLD;
+
+ free(current);
+
+ offset = i;
+ length = -1;
+ }
+
+ i++;
+ length++;
+ } while (buf[i - 1] != '\0');
+
+ attrs[id] = attr;
+ update_colors();
+ update_full();
+}
+
+static char **id_to_fmt(enum format_id id)
+{
+ switch (id) {
+ case FMT_CURRENT_ALT:
+ return ¤t_alt_format;
+ case FMT_PLAYLIST_ALT:
+ return &list_win_alt_format;
+ case FMT_TITLE_ALT:
+ return &window_title_alt_format;
+ case FMT_TRACKWIN_ALT:
+ return &track_win_alt_format;
+ case FMT_CURRENT:
+ return ¤t_format;
+ case FMT_PLAYLIST:
+ return &list_win_format;
+ case FMT_PLAYLIST_VA:
+ return &list_win_format_va;
+ case FMT_TITLE:
+ return &window_title_format;
+ case FMT_TRACKWIN:
+ return &track_win_format;
+ case FMT_TRACKWIN_VA:
+ return &track_win_format_va;
+ default:
+ die("unhandled format code: %d\n", id);
+ }
+ return NULL;
+}
+
+static void get_format(unsigned int id, char *buf)
+{
+ char **fmtp = id_to_fmt(id);
+
+ strcpy(buf, *fmtp);
+}
+
+static void set_format(unsigned int id, const char *buf)
+{
+ char **fmtp = id_to_fmt(id);
+
+ if (!track_format_valid(buf)) {
+ error_msg("invalid format string");
+ return;
+ }
+ free(*fmtp);
+ *fmtp = xstrdup(buf);
+
+ update_full();
+}
+
+/* }}} */
+
+#define DN(name) { #name, get_ ## name, set_ ## name, NULL, 0 },
+#define DN_FLAGS(name, flags) { #name, get_ ## name, set_ ## name, NULL, flags },
+#define DT(name) { #name, get_ ## name, set_ ## name, toggle_ ## name, 0 },
+
+static const struct {
+ const char *name;
+ opt_get_cb get;
+ opt_set_cb set;
+ opt_toggle_cb toggle;
+ unsigned int flags;
+} simple_options[] = {
+ DT(aaa_mode)
+ DT(auto_reshuffle)
+ DN_FLAGS(device, OPT_PROGRAM_PATH)
+ DN(buffer_seconds)
+ DN(scroll_offset)
+ DT(confirm_run)
+ DT(continue)
+ DT(smart_artist_sort)
+ DN(id3_default_charset)
+ DN(icecast_default_charset)
+ DN(lib_sort)
+ DN(output_plugin)
+ DN(passwd)
+ DN(pl_sort)
+ DT(play_library)
+ DT(play_sorted)
+ DT(display_artist_sort_name)
+ DT(repeat)
+ DT(repeat_current)
+ DT(replaygain)
+ DT(replaygain_limit)
+ DN(replaygain_preamp)
+ DT(resume)
+ DT(show_hidden)
+ DT(show_current_bitrate)
+ DT(show_remaining_time)
+ DT(set_term_title)
+ DT(shuffle)
+ DT(softvol)
+ DN(softvol_state)
+ DN_FLAGS(status_display_program, OPT_PROGRAM_PATH)
+ DT(wrap_search)
+ DT(skip_track_info)
+ { NULL, NULL, NULL, NULL, 0 }
+};
+
+static const char * const color_names[NR_COLORS] = {
+ "color_cmdline_bg",
+ "color_cmdline_fg",
+ "color_error",
+ "color_info",
+ "color_separator",
+ "color_statusline_bg",
+ "color_statusline_fg",
+ "color_titleline_bg",
+ "color_titleline_fg",
+ "color_win_bg",
+ "color_win_cur",
+ "color_win_cur_sel_bg",
+ "color_win_cur_sel_fg",
+ "color_win_dir",
+ "color_win_fg",
+ "color_win_inactive_cur_sel_bg",
+ "color_win_inactive_cur_sel_fg",
+ "color_win_inactive_sel_bg",
+ "color_win_inactive_sel_fg",
+ "color_win_sel_bg",
+ "color_win_sel_fg",
+ "color_win_title_bg",
+ "color_win_title_fg"
+};
+
+static const char * const attr_names[NR_ATTRS] = {
+ "color_cmdline_attr",
+ "color_statusline_attr",
+ "color_titleline_attr",
+ "color_win_attr",
+ "color_win_cur_sel_attr",
+ "color_cur_sel_attr",
+ "color_win_inactive_cur_sel_attr",
+ "color_win_inactive_sel_attr",
+ "color_win_sel_attr",
+ "color_win_title_attr"
+};
+
+/* default values for the variables which we must initialize but
+ * can't do it statically */
+static const struct {
+ const char *name;
+ const char *value;
+} str_defaults[] = {
+ { "altformat_current", " %F " },
+ { "altformat_playlist", " %f%= %d " },
+ { "altformat_title", "%f" },
+ { "altformat_trackwin", " %f%= %d " },
+ { "format_current", " %a - %l -%3n. %t%= %y " },
+ { "format_playlist", " %-25%a %3n. %t%= %y %d " },
+ { "format_playlist_va", " %-25%A %3n. %t (%a)%= %y %d " },
+ { "format_title", "%a - %l - %t (%y)" },
+ { "format_trackwin", "%3n. %t%= %y %d " },
+ { "format_trackwin_va", "%3n. %t (%a)%= %y %d " },
+
+ { "lib_sort" , "albumartist date album discnumber tracknumber title filename" },
+ { "pl_sort", "" },
+ { "id3_default_charset","ISO-8859-1" },
+ { "icecast_default_charset","ISO-8859-1" },
+ { NULL, NULL }
+};
+
+LIST_HEAD(option_head);
+int nr_options = 0;
+
+void option_add(const char *name, unsigned int id, opt_get_cb get,
+ opt_set_cb set, opt_toggle_cb toggle, unsigned int flags)
+{
+ struct cmus_opt *opt = xnew(struct cmus_opt, 1);
+ struct list_head *item;
+
+ opt->name = name;
+ opt->id = id;
+ opt->get = get;
+ opt->set = set;
+ opt->toggle = toggle;
+ opt->flags = flags;
+
+ item = option_head.next;
+ while (item != &option_head) {
+ struct cmus_opt *o = container_of(item, struct cmus_opt, node);
+
+ if (strcmp(name, o->name) < 0)
+ break;
+ item = item->next;
+ }
+ /* add before item */
+ list_add_tail(&opt->node, item);
+ nr_options++;
+}
+
+struct cmus_opt *option_find(const char *name)
+{
+ struct cmus_opt *opt;
+
+ list_for_each_entry(opt, &option_head, node) {
+ if (strcmp(name, opt->name) == 0)
+ return opt;
+ }
+ error_msg("no such option %s", name);
+ return NULL;
+}
+
+void option_set(const char *name, const char *value)
+{
+ struct cmus_opt *opt = option_find(name);
+
+ if (opt)
+ opt->set(opt->id, value);
+}
+
+void options_add(void)
+{
+ int i;
+
+ for (i = 0; simple_options[i].name; i++)
+ option_add(simple_options[i].name, 0, simple_options[i].get,
+ simple_options[i].set, simple_options[i].toggle,
+ simple_options[i].flags);
+
+ for (i = 0; i < NR_FMTS; i++)
+ option_add(str_defaults[i].name, i, get_format, set_format, NULL, 0);
+
+ for (i = 0; i < NR_COLORS; i++)
+ option_add(color_names[i], i, get_color, set_color, NULL, 0);
+
+ for (i = 0; i < NR_ATTRS; i++)
+ option_add(attr_names[i], i, get_attr, set_attr, NULL, 0);
+
+ ip_add_options();
+ op_add_options();
+}
+
+static int handle_line(void *data, const char *line)
+{
+ run_command(line);
+ return 0;
+}
+
+int source_file(const char *filename)
+{
+ return file_for_each_line(filename, handle_line, NULL);
+}
+
+void options_load(void)
+{
+ char filename[512];
+ int i;
+
+ /* initialize those that can't be statically initialized */
+ cdda_device = get_default_cdda_device();
+ for (i = 0; str_defaults[i].name; i++)
+ option_set(str_defaults[i].name, str_defaults[i].value);
+
+ /* load autosave config */
+ snprintf(filename, sizeof(filename), "%s/autosave", cmus_config_dir);
+ if (source_file(filename) == -1) {
+ const char *def = DATADIR "/cmus/rc";
+
+ if (errno != ENOENT)
+ error_msg("loading %s: %s", filename, strerror(errno));
+
+ /* load defaults */
+ if (source_file(def) == -1)
+ die_errno("loading %s", def);
+ }
+
+ /* load optional static config */
+ snprintf(filename, sizeof(filename), "%s/rc", cmus_config_dir);
+ if (source_file(filename) == -1) {
+ if (errno != ENOENT)
+ error_msg("loading %s: %s", filename, strerror(errno));
+ }
+}
+
+void options_exit(void)
+{
+ struct cmus_opt *opt;
+ struct filter_entry *filt;
+ char filename_tmp[512];
+ char filename[512];
+ FILE *f;
+ int i;
+
+ snprintf(filename_tmp, sizeof(filename_tmp), "%s/autosave.tmp", cmus_config_dir);
+ f = fopen(filename_tmp, "w");
+ if (f == NULL) {
+ warn_errno("creating %s", filename_tmp);
+ return;
+ }
+
+ /* save options */
+ list_for_each_entry(opt, &option_head, node) {
+ char buf[OPTION_MAX_SIZE];
+
+ buf[0] = 0;
+ opt->get(opt->id, buf);
+ fprintf(f, "set %s=%s\n", opt->name, buf);
+ }
+
+ /* save key bindings */
+ for (i = 0; i < NR_CTXS; i++) {
+ struct binding *b = key_bindings[i];
+
+ while (b) {
+ fprintf(f, "bind %s %s %s\n", key_context_names[i], b->key->name, b->cmd);
+ b = b->next;
+ }
+ }
+
+ /* save filters */
+ list_for_each_entry(filt, &filters_head, node)
+ fprintf(f, "fset %s=%s\n", filt->name, filt->filter);
+ fprintf(f, "factivate");
+ list_for_each_entry(filt, &filters_head, node) {
+ switch (filt->act_stat) {
+ case FS_YES:
+ fprintf(f, " %s", filt->name);
+ break;
+ case FS_NO:
+ fprintf(f, " !%s", filt->name);
+ break;
+ }
+ }
+ fprintf(f, "\n");
+
+ fclose(f);
+
+ snprintf(filename, sizeof(filename), "%s/autosave", cmus_config_dir);
+ i = rename(filename_tmp, filename);
+ if (i)
+ warn_errno("renaming %s to %s", filename_tmp, filename);
+}
+
+struct resume {
+ enum player_status status;
+ char *filename;
+ long int position;
+ char *lib_filename;
+ int view;
+ char *live_filter;
+ char *browser_dir;
+};
+
+static int handle_resume_line(void *data, const char *line)
+{
+ struct resume *resume = data;
+ char *cmd, *arg;
+
+ if (!parse_command(line, &cmd, &arg))
+ return 0;
+ if (!arg)
+ goto out;
+
+ if (strcmp(cmd, "status") == 0) {
+ parse_enum(arg, 0, NR_PLAYER_STATUS, player_status_names, (int *) &resume->status);
+ } else if (strcmp(cmd, "file") == 0) {
+ free(resume->filename);
+ resume->filename = xstrdup(unescape(arg));
+ } else if (strcmp(cmd, "position") == 0) {
+ str_to_int(arg, &resume->position);
+ } else if (strcmp(cmd, "lib_file") == 0) {
+ free(resume->lib_filename);
+ resume->lib_filename = xstrdup(unescape(arg));
+ } else if (strcmp(cmd, "view") == 0) {
+ parse_enum(arg, 0, NR_VIEWS, view_names, &resume->view);
+ } else if (strcmp(cmd, "live-filter") == 0) {
+ free(resume->live_filter);
+ resume->live_filter = xstrdup(unescape(arg));
+ } else if (strcmp(cmd, "browser-dir") == 0) {
+ free(resume->browser_dir);
+ resume->browser_dir = xstrdup(unescape(arg));
+ }
+
+ free(arg);
+out:
+ free(cmd);
+ return 0;
+}
+
+void resume_load(void)
+{
+ char filename[512];
+ struct track_info *ti, *old;
+ struct resume resume = { .status = PLAYER_STATUS_STOPPED, .view = -1 };
+
+ snprintf(filename, sizeof(filename), "%s/resume", cmus_config_dir);
+ if (file_for_each_line(filename, handle_resume_line, &resume) == -1) {
+ if (errno != ENOENT)
+ error_msg("loading %s: %s", filename, strerror(errno));
+ return;
+ }
+ if (resume.view >= 0 && resume.view != cur_view)
+ set_view(resume.view);
+ if (resume.lib_filename) {
+ cache_lock();
+ ti = old = cache_get_ti(resume.lib_filename, 0);
+ cache_unlock();
+ if (ti) {
+ editable_lock();
+ lib_add_track(ti);
+ track_info_unref(ti);
+ lib_store_cur_track(ti);
+ track_info_unref(ti);
+ ti = lib_set_track(lib_find_track(ti));
+ if (ti) {
+ BUG_ON(ti != old);
+ track_info_unref(ti);
+ tree_sel_current();
+ sorted_sel_current();
+ }
+ editable_unlock();
+ }
+ free(resume.lib_filename);
+ }
+ if (resume.filename) {
+ cache_lock();
+ ti = cache_get_ti(resume.filename, 0);
+ cache_unlock();
+ if (ti) {
+ player_set_file(ti);
+ if (resume.status != PLAYER_STATUS_STOPPED)
+ player_seek(resume.position, 0, resume.status == PLAYER_STATUS_PLAYING);
+ }
+ free(resume.filename);
+ }
+ if (resume.live_filter) {
+ editable_lock();
+ filters_set_live(resume.live_filter);
+ editable_unlock();
+ free(resume.live_filter);
+ }
+ if (resume.browser_dir) {
+ browser_chdir(resume.browser_dir);
+ free(resume.browser_dir);
+ }
+}
+
+void resume_exit(void)
+{
+ char filename_tmp[512];
+ char filename[512];
+ struct track_info *ti;
+ FILE *f;
+ int rc;
+
+ snprintf(filename_tmp, sizeof(filename_tmp), "%s/resume.tmp", cmus_config_dir);
+ f = fopen(filename_tmp, "w");
+ if (!f) {
+ warn_errno("creating %s", filename_tmp);
+ return;
+ }
+
+ player_info_lock();
+ fprintf(f, "status %s\n", player_status_names[player_info.status]);
+ ti = player_info.ti;
+ if (ti) {
+ fprintf(f, "file %s\n", escape(ti->filename));
+ fprintf(f, "position %d\n", player_info.pos);
+ }
+ player_info_unlock();
+ if (lib_cur_track)
+ ti = tree_track_info(lib_cur_track);
+ else
+ ti = lib_get_cur_stored_track();
+ if (ti)
+ fprintf(f, "lib_file %s\n", escape(ti->filename));
+ fprintf(f, "view %s\n", view_names[cur_view]);
+ if (lib_live_filter)
+ fprintf(f, "live-filter %s\n", escape(lib_live_filter));
+ fprintf(f, "browser-dir %s\n", escape(browser_dir));
+
+ fclose(f);
+
+ snprintf(filename, sizeof(filename), "%s/resume", cmus_config_dir);
+ rc = rename(filename_tmp, filename);
+ if (rc)
+ warn_errno("renaming %s to %s", filename_tmp, filename);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef OPTIONS_H
+#define OPTIONS_H
+
+#include "list.h"
+
+#define OPTION_MAX_SIZE 256
+
+typedef void (*opt_get_cb)(unsigned int id, char *buf);
+typedef void (*opt_set_cb)(unsigned int id, const char *buf);
+typedef void (*opt_toggle_cb)(unsigned int id);
+
+enum {
+ OPT_PROGRAM_PATH = 1 << 0,
+};
+
+struct cmus_opt {
+ struct list_head node;
+
+ const char *name;
+
+ /* If there are many similar options you should write generic get(),
+ * set() and optionally toggle() and set id to index of option array or
+ * what ever.
+ *
+ * Useful for colors, format strings and plugin options.
+ */
+ unsigned int id;
+
+ opt_get_cb get;
+ opt_set_cb set;
+
+ /* NULL if not toggle-able */
+ opt_toggle_cb toggle;
+
+ unsigned int flags;
+};
+
+extern struct list_head option_head;
+extern int nr_options;
+
+enum {
+ TREE_VIEW,
+ SORTED_VIEW,
+ PLAYLIST_VIEW,
+ QUEUE_VIEW,
+ BROWSER_VIEW,
+ FILTERS_VIEW,
+ HELP_VIEW,
+ NR_VIEWS
+};
+
+enum {
+ COLOR_CMDLINE_BG,
+ COLOR_CMDLINE_FG,
+ COLOR_ERROR,
+ COLOR_INFO,
+
+ COLOR_SEPARATOR,
+ COLOR_STATUSLINE_BG,
+ COLOR_STATUSLINE_FG,
+ COLOR_TITLELINE_BG,
+
+ COLOR_TITLELINE_FG,
+ COLOR_WIN_BG,
+ COLOR_WIN_CUR,
+ COLOR_WIN_CUR_SEL_BG,
+
+ COLOR_WIN_CUR_SEL_FG,
+ COLOR_WIN_DIR,
+ COLOR_WIN_FG,
+ COLOR_WIN_INACTIVE_CUR_SEL_BG,
+
+ COLOR_WIN_INACTIVE_CUR_SEL_FG,
+ COLOR_WIN_INACTIVE_SEL_BG,
+ COLOR_WIN_INACTIVE_SEL_FG,
+ COLOR_WIN_SEL_BG,
+
+ COLOR_WIN_SEL_FG,
+ COLOR_WIN_TITLE_BG,
+ COLOR_WIN_TITLE_FG,
+ NR_COLORS
+};
+
+enum {
+ COLOR_CMDLINE_ATTR,
+ COLOR_STATUSLINE_ATTR,
+ COLOR_TITLELINE_ATTR,
+ COLOR_WIN_ATTR,
+ COLOR_WIN_CUR_SEL_ATTR,
+ COLOR_CUR_SEL_ATTR,
+ COLOR_WIN_INACTIVE_CUR_SEL_ATTR,
+ COLOR_WIN_INACTIVE_SEL_ATTR,
+ COLOR_WIN_SEL_ATTR,
+ COLOR_WIN_TITLE_ATTR,
+ NR_ATTRS
+};
+
+#define BRIGHT (1 << 3)
+
+extern char *cdda_device;
+extern char *output_plugin;
+extern char *status_display_program;
+extern char *server_password;
+extern int auto_reshuffle;
+extern int confirm_run;
+extern int resume_cmus;
+extern int show_hidden;
+extern int show_current_bitrate;
+extern int show_remaining_time;
+extern int set_term_title;
+extern int wrap_search;
+extern int play_library;
+extern int repeat;
+extern int shuffle;
+extern int display_artist_sort_name;
+extern int smart_artist_sort;
+extern int scroll_offset;
+extern int skip_track_info;
+
+extern const char * const aaa_mode_names[];
+extern const char * const view_names[NR_VIEWS + 1];
+
+extern int colors[NR_COLORS];
+extern int attrs[NR_ATTRS];
+
+/* format string for track window (tree view) */
+extern char *track_win_format;
+extern char *track_win_format_va;
+extern char *track_win_alt_format;
+
+/* format string for shuffle, sorted and play queue views */
+extern char *list_win_format;
+extern char *list_win_format_va;
+extern char *list_win_alt_format;
+
+/* format string for currently playing track */
+extern char *current_format;
+extern char *current_alt_format;
+
+/* format string for window title */
+extern char *window_title_format;
+extern char *window_title_alt_format;
+
+extern char *id3_default_charset;
+extern char *icecast_default_charset;
+
+/* build option list */
+void options_add(void);
+
+/* load options from the config file */
+void options_load(void);
+
+int source_file(const char *filename);
+
+/* save options */
+void options_exit(void);
+
+/* load resume file */
+void resume_load(void);
+/* save resume file */
+void resume_exit(void);
+
+void option_add(const char *name, unsigned int id, opt_get_cb get,
+ opt_set_cb set, opt_toggle_cb toggle, unsigned int flags);
+struct cmus_opt *option_find(const char *name);
+void option_set(const char *name, const char *value);
+int parse_enum(const char *buf, int minval, int maxval, const char * const names[], int *val);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "op.h"
+#include "sf.h"
+#include "xmalloc.h"
+#include "debug.h"
+
+#if defined(__OpenBSD__)
+#include <soundcard.h>
+#else
+#include <sys/soundcard.h>
+#endif
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+static sample_format_t oss_sf;
+static int oss_fd = -1;
+
+/* configuration */
+static char *oss_dsp_device = NULL;
+
+static int oss_close(void);
+
+static int oss_reset(void)
+{
+ if (fcntl(oss_fd, SNDCTL_DSP_RESET, 0) == -1) {
+ return -1;
+ }
+ return 0;
+}
+
+/* defined only in OSSv4, but seem to work in OSSv3 (Linux) */
+#ifndef AFMT_S32_LE
+#define AFMT_S32_LE 0x00001000
+#endif
+#ifndef AFMT_S32_BE
+#define AFMT_S32_BE 0x00002000
+#endif
+#ifndef AFMT_S24_PACKED
+#define AFMT_S24_PACKED 0x00040000
+#endif
+
+static int oss_set_sf(sample_format_t sf)
+{
+ int tmp, log2_fragment_size, nr_fragments, bytes_per_second;
+
+ oss_reset();
+ oss_sf = sf;
+
+#ifdef SNDCTL_DSP_CHANNELS
+ tmp = sf_get_channels(oss_sf);
+ if (ioctl(oss_fd, SNDCTL_DSP_CHANNELS, &tmp) == -1)
+ return -1;
+#else
+ tmp = sf_get_channels(oss_sf) - 1;
+ if (ioctl(oss_fd, SNDCTL_DSP_STEREO, &tmp) == -1)
+ return -1;
+#endif
+
+ if (sf_get_bits(oss_sf) == 16) {
+ if (sf_get_signed(oss_sf)) {
+ if (sf_get_bigendian(oss_sf)) {
+ tmp = AFMT_S16_BE;
+ } else {
+ tmp = AFMT_S16_LE;
+ }
+ } else {
+ if (sf_get_bigendian(oss_sf)) {
+ tmp = AFMT_U16_BE;
+ } else {
+ tmp = AFMT_U16_LE;
+ }
+ }
+ } else if (sf_get_bits(oss_sf) == 8) {
+ if (sf_get_signed(oss_sf)) {
+ tmp = AFMT_S8;
+ } else {
+ tmp = AFMT_U8;
+ }
+ } else if (sf_get_bits(oss_sf) == 32 && sf_get_signed(oss_sf)) {
+ if (sf_get_bigendian(oss_sf)) {
+ tmp = AFMT_S32_BE;
+ } else {
+ tmp = AFMT_S32_LE;
+ }
+ } else if (sf_get_bits(oss_sf) == 24 && sf_get_signed(oss_sf) && !sf_get_bigendian(oss_sf)) {
+ tmp = AFMT_S24_PACKED;
+ } else {
+ d_print("unsupported sample format: %c%u_%s\n",
+ sf_get_signed(oss_sf) ? 'S' : 'U', sf_get_bits(oss_sf),
+ sf_get_bigendian(oss_sf) ? "BE" : "LE");
+ return -1;
+ }
+ if (ioctl(oss_fd, SNDCTL_DSP_SAMPLESIZE, &tmp) == -1)
+ return -1;
+
+ tmp = sf_get_rate(oss_sf);
+ if (ioctl(oss_fd, SNDCTL_DSP_SPEED, &tmp) == -1)
+ return -1;
+
+ bytes_per_second = sf_get_second_size(oss_sf);
+ log2_fragment_size = 0;
+ while (1 << log2_fragment_size < bytes_per_second / 25)
+ log2_fragment_size++;
+ log2_fragment_size--;
+ nr_fragments = 32;
+
+ /* bits 0..15 = size of fragment, 16..31 = log2(number of fragments) */
+ tmp = (nr_fragments << 16) + log2_fragment_size;
+ if (ioctl(oss_fd, SNDCTL_DSP_SETFRAGMENT, &tmp) == -1)
+ return -1;
+ return 0;
+}
+
+static int oss_device_exists(const char *device)
+{
+ struct stat s;
+
+ if (stat(device, &s))
+ return 0;
+ return 1;
+}
+
+static int oss_init(void)
+{
+ const char *new_dsp_dev = "/dev/sound/dsp";
+ const char *dsp_dev = "/dev/dsp";
+
+ if (oss_dsp_device) {
+ if (oss_device_exists(oss_dsp_device))
+ return 0;
+ free(oss_dsp_device);
+ oss_dsp_device = NULL;
+ return -1;
+ }
+ if (oss_device_exists(new_dsp_dev)) {
+ oss_dsp_device = xstrdup(new_dsp_dev);
+ return 0;
+ }
+ if (oss_device_exists(dsp_dev)) {
+ oss_dsp_device = xstrdup(dsp_dev);
+ return 0;
+ }
+ return -1;
+}
+
+static int oss_exit(void)
+{
+ free(oss_dsp_device);
+ oss_dsp_device = NULL;
+ return 0;
+}
+
+static int oss_open(sample_format_t sf, const channel_position_t *channel_map)
+{
+ int oss_version = 0;
+ oss_fd = open(oss_dsp_device, O_WRONLY);
+ if (oss_fd == -1)
+ return -1;
+ ioctl(oss_fd, OSS_GETVERSION, &oss_version);
+ d_print("oss version: %#08x\n", oss_version);
+ if (oss_set_sf(sf) == -1) {
+ oss_close();
+ return -1;
+ }
+ return 0;
+}
+
+static int oss_close(void)
+{
+ close(oss_fd);
+ oss_fd = -1;
+ return 0;
+}
+
+static int oss_write(const char *buffer, int count)
+{
+ int rc;
+
+ count -= count % sf_get_frame_size(oss_sf);
+ rc = write(oss_fd, buffer, count);
+ return rc;
+}
+
+static int oss_pause(void)
+{
+ if (ioctl(oss_fd, SNDCTL_DSP_POST, NULL) == -1)
+ return -1;
+ return 0;
+}
+
+static int oss_unpause(void)
+{
+ return 0;
+}
+
+static int oss_buffer_space(void)
+{
+ audio_buf_info info;
+ int space;
+
+ if (ioctl(oss_fd, SNDCTL_DSP_GETOSPACE, &info) == -1)
+ return -1;
+ space = (info.fragments - 1) * info.fragsize;
+ return space;
+}
+
+static int op_oss_set_option(int key, const char *val)
+{
+ switch (key) {
+ case 0:
+ free(oss_dsp_device);
+ oss_dsp_device = xstrdup(val);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+static int op_oss_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ if (oss_dsp_device)
+ *val = xstrdup(oss_dsp_device);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+const struct output_plugin_ops op_pcm_ops = {
+ .init = oss_init,
+ .exit = oss_exit,
+ .open = oss_open,
+ .close = oss_close,
+ .write = oss_write,
+ .pause = oss_pause,
+ .unpause = oss_unpause,
+ .buffer_space = oss_buffer_space,
+ .set_option = op_oss_set_option,
+ .get_option = op_oss_get_option
+};
+
+const char * const op_pcm_options[] = {
+ "device",
+ NULL
+};
+
+const int op_priority = 1;
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "output.h"
+#include "op.h"
+#include "mixer.h"
+#include "sf.h"
+#include "utils.h"
+#include "xmalloc.h"
+#include "list.h"
+#include "debug.h"
+#include "ui_curses.h"
+#include "options.h"
+#ifdef HAVE_CONFIG
+#include "config/libdir.h"
+#endif
+
+#include <string.h>
+#include <strings.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <dlfcn.h>
+
+struct output_plugin {
+ struct list_head node;
+ char *name;
+ void *handle;
+
+ const struct output_plugin_ops *pcm_ops;
+ const struct mixer_plugin_ops *mixer_ops;
+ const char * const *pcm_options;
+ const char * const *mixer_options;
+ int priority;
+
+ unsigned int pcm_initialized : 1;
+ unsigned int mixer_initialized : 1;
+ unsigned int mixer_open : 1;
+};
+
+static const char * const plugin_dir = LIBDIR "/cmus/op";
+static LIST_HEAD(op_head);
+static struct output_plugin *op = NULL;
+
+/* volume is between 0 and volume_max */
+int volume_max = 0;
+int volume_l = -1;
+int volume_r = -1;
+
+static void add_plugin(struct output_plugin *plugin)
+{
+ struct list_head *item = op_head.next;
+
+ while (item != &op_head) {
+ struct output_plugin *o = container_of(item, struct output_plugin, node);
+
+ if (plugin->priority < o->priority)
+ break;
+ item = item->next;
+ }
+
+ /* add before item */
+ list_add_tail(&plugin->node, item);
+}
+
+void op_load_plugins(void)
+{
+ DIR *dir;
+ struct dirent *d;
+
+ dir = opendir(plugin_dir);
+ if (dir == NULL) {
+ error_msg("couldn't open directory `%s': %s", plugin_dir, strerror(errno));
+ return;
+ }
+ while ((d = (struct dirent *) readdir(dir)) != NULL) {
+ char filename[256];
+ struct output_plugin *plug;
+ void *so, *symptr;
+ char *ext;
+
+ if (d->d_name[0] == '.')
+ continue;
+ ext = strrchr(d->d_name, '.');
+ if (ext == NULL)
+ continue;
+ if (strcmp(ext, ".so"))
+ continue;
+
+ snprintf(filename, sizeof(filename), "%s/%s", plugin_dir, d->d_name);
+
+ so = dlopen(filename, RTLD_NOW);
+ if (so == NULL) {
+ d_print("%s: %s\n", filename, dlerror());
+ continue;
+ }
+
+ plug = xnew(struct output_plugin, 1);
+
+ plug->pcm_ops = dlsym(so, "op_pcm_ops");
+ plug->pcm_options = dlsym(so, "op_pcm_options");
+ symptr = dlsym(so, "op_priority");
+ if (!plug->pcm_ops || !plug->pcm_options || !symptr) {
+ error_msg("%s: missing symbol", filename);
+ free(plug);
+ dlclose(so);
+ continue;
+ }
+ plug->priority = *(int *)symptr;
+
+ plug->mixer_ops = dlsym(so, "op_mixer_ops");
+ plug->mixer_options = dlsym(so, "op_mixer_options");
+ if (plug->mixer_ops == NULL || plug->mixer_options == NULL) {
+ plug->mixer_ops = NULL;
+ plug->mixer_options = NULL;
+ }
+
+ plug->name = xstrndup(d->d_name, ext - d->d_name);
+ plug->handle = so;
+ plug->pcm_initialized = 0;
+ plug->mixer_initialized = 0;
+ plug->mixer_open = 0;
+
+ add_plugin(plug);
+ }
+ closedir(dir);
+}
+
+static void init_plugin(struct output_plugin *o)
+{
+ if (!o->mixer_initialized && o->mixer_ops) {
+ if (o->mixer_ops->init() == 0) {
+ d_print("initialized mixer for %s\n", o->name);
+ o->mixer_initialized = 1;
+ } else {
+ d_print("could not initialize mixer `%s'\n", o->name);
+ }
+ }
+ if (!o->pcm_initialized) {
+ if (o->pcm_ops->init() == 0) {
+ d_print("initialized pcm for %s\n", o->name);
+ o->pcm_initialized = 1;
+ } else {
+ d_print("could not initialize pcm `%s'\n", o->name);
+ }
+ }
+}
+
+void op_exit_plugins(void)
+{
+ struct output_plugin *o;
+
+ list_for_each_entry(o, &op_head, node) {
+ if (o->mixer_initialized && o->mixer_ops)
+ o->mixer_ops->exit();
+ if (o->pcm_initialized)
+ o->pcm_ops->exit();
+ }
+}
+
+void mixer_close(void)
+{
+ volume_max = 0;
+ if (op && op->mixer_open) {
+ BUG_ON(op->mixer_ops == NULL);
+ op->mixer_ops->close();
+ op->mixer_open = 0;
+ }
+}
+
+void mixer_open(void)
+{
+ if (op == NULL)
+ return;
+
+ BUG_ON(op->mixer_open);
+ if (op->mixer_ops && op->mixer_initialized) {
+ int rc;
+
+ rc = op->mixer_ops->open(&volume_max);
+ if (rc == 0) {
+ op->mixer_open = 1;
+ mixer_read_volume();
+ } else {
+ volume_max = 0;
+ }
+ }
+}
+
+static int select_plugin(struct output_plugin *o)
+{
+ /* try to initialize if not initialized yet */
+ init_plugin(o);
+
+ if (!o->pcm_initialized)
+ return -OP_ERROR_NOT_INITIALIZED;
+ op = o;
+ return 0;
+}
+
+int op_select(const char *name)
+{
+ struct output_plugin *o;
+
+ list_for_each_entry(o, &op_head, node) {
+ if (strcasecmp(name, o->name) == 0)
+ return select_plugin(o);
+ }
+ return -OP_ERROR_NO_PLUGIN;
+}
+
+int op_select_any(void)
+{
+ struct output_plugin *o;
+ int rc = -OP_ERROR_NO_PLUGIN;
+ sample_format_t sf = sf_channels(2) | sf_rate(44100) | sf_bits(16) | sf_signed(1);
+
+ list_for_each_entry(o, &op_head, node) {
+ rc = select_plugin(o);
+ if (rc != 0)
+ continue;
+ rc = o->pcm_ops->open(sf, NULL);
+ if (rc == 0) {
+ o->pcm_ops->close();
+ break;
+ }
+ }
+ return rc;
+}
+
+int op_open(sample_format_t sf, const channel_position_t *channel_map)
+{
+ if (op == NULL)
+ return -OP_ERROR_NOT_INITIALIZED;
+ return op->pcm_ops->open(sf, channel_map);
+}
+
+int op_drop(void)
+{
+ if (op->pcm_ops->drop == NULL)
+ return -OP_ERROR_NOT_SUPPORTED;
+ return op->pcm_ops->drop();
+}
+
+int op_close(void)
+{
+ return op->pcm_ops->close();
+}
+
+int op_write(const char *buffer, int count)
+{
+ return op->pcm_ops->write(buffer, count);
+}
+
+int op_pause(void)
+{
+ if (op->pcm_ops->pause == NULL)
+ return 0;
+ return op->pcm_ops->pause();
+}
+
+int op_unpause(void)
+{
+ if (op->pcm_ops->unpause == NULL)
+ return 0;
+ return op->pcm_ops->unpause();
+}
+
+int op_buffer_space(void)
+{
+ return op->pcm_ops->buffer_space();
+}
+
+int mixer_set_volume(int left, int right)
+{
+ if (op == NULL)
+ return -OP_ERROR_NOT_INITIALIZED;
+ if (!op->mixer_open)
+ return -OP_ERROR_NOT_OPEN;
+ return op->mixer_ops->set_volume(left, right);
+}
+
+int mixer_read_volume(void)
+{
+ if (op == NULL)
+ return -OP_ERROR_NOT_INITIALIZED;
+ if (!op->mixer_open)
+ return -OP_ERROR_NOT_OPEN;
+ return op->mixer_ops->get_volume(&volume_l, &volume_r);
+}
+
+int mixer_get_fds(int *fds)
+{
+ if (op == NULL)
+ return -OP_ERROR_NOT_INITIALIZED;
+ if (!op->mixer_open)
+ return -OP_ERROR_NOT_OPEN;
+ if (!op->mixer_ops->get_fds)
+ return -OP_ERROR_NOT_SUPPORTED;
+ return op->mixer_ops->get_fds(fds);
+}
+
+static struct output_plugin *find_plugin(int idx)
+{
+ struct output_plugin *o;
+
+ list_for_each_entry(o, &op_head, node) {
+ if (idx == 0)
+ return o;
+ idx--;
+ }
+ return NULL;
+}
+
+extern int soft_vol;
+
+static void option_error(int rc)
+{
+ char *msg = op_get_error_msg(rc, "setting option");
+ error_msg("%s", msg);
+ free(msg);
+}
+
+static void set_dsp_option(unsigned int id, const char *val)
+{
+ const struct output_plugin *o = find_plugin(id >> 16);
+ int rc;
+
+ rc = o->pcm_ops->set_option(id & 0xffff, val);
+ if (rc)
+ option_error(rc);
+}
+
+static void set_mixer_option(unsigned int id, const char *val)
+{
+ const struct output_plugin *o = find_plugin(id >> 16);
+ int rc;
+
+ rc = o->mixer_ops->set_option(id & 0xffff, val);
+ if (rc) {
+ option_error(rc);
+ return;
+ }
+ if (op && op->mixer_ops == o->mixer_ops) {
+ /* option of the current op was set
+ * try to reopen the mixer */
+ mixer_close();
+ if (!soft_vol)
+ mixer_open();
+ }
+}
+
+static void get_dsp_option(unsigned int id, char *buf)
+{
+ const struct output_plugin *o = find_plugin(id >> 16);
+ char *val = NULL;
+
+ o->pcm_ops->get_option(id & 0xffff, &val);
+ if (val) {
+ strcpy(buf, val);
+ free(val);
+ }
+}
+
+static void get_mixer_option(unsigned int id, char *buf)
+{
+ const struct output_plugin *o = find_plugin(id >> 16);
+ char *val = NULL;
+
+ o->mixer_ops->get_option(id & 0xffff, &val);
+ if (val) {
+ strcpy(buf, val);
+ free(val);
+ }
+}
+
+void op_add_options(void)
+{
+ struct output_plugin *o;
+ unsigned int oid, pid = 0;
+ char key[64];
+
+ list_for_each_entry(o, &op_head, node) {
+ for (oid = 0; o->pcm_options[oid]; oid++) {
+ snprintf(key, sizeof(key), "dsp.%s.%s",
+ o->name,
+ o->pcm_options[oid]);
+ option_add(xstrdup(key), (pid << 16) | oid, get_dsp_option, set_dsp_option, NULL, 0);
+ }
+ for (oid = 0; o->mixer_ops && o->mixer_options[oid]; oid++) {
+ snprintf(key, sizeof(key), "mixer.%s.%s",
+ o->name,
+ o->mixer_options[oid]);
+ option_add(xstrdup(key), (pid << 16) | oid, get_mixer_option, set_mixer_option, NULL, 0);
+ }
+ pid++;
+ }
+}
+
+char *op_get_error_msg(int rc, const char *arg)
+{
+ char buffer[1024];
+
+ switch (-rc) {
+ case OP_ERROR_ERRNO:
+ snprintf(buffer, sizeof(buffer), "%s: %s", arg, strerror(errno));
+ break;
+ case OP_ERROR_NO_PLUGIN:
+ snprintf(buffer, sizeof(buffer),
+ "%s: no such plugin", arg);
+ break;
+ case OP_ERROR_NOT_INITIALIZED:
+ snprintf(buffer, sizeof(buffer),
+ "%s: couldn't initialize required output plugin", arg);
+ break;
+ case OP_ERROR_NOT_SUPPORTED:
+ snprintf(buffer, sizeof(buffer),
+ "%s: function not supported", arg);
+ break;
+ case OP_ERROR_NOT_OPEN:
+ snprintf(buffer, sizeof(buffer),
+ "%s: mixer is not open", arg);
+ break;
+ case OP_ERROR_SAMPLE_FORMAT:
+ snprintf(buffer, sizeof(buffer),
+ "%s: sample format not supported", arg);
+ break;
+ case OP_ERROR_NOT_OPTION:
+ snprintf(buffer, sizeof(buffer),
+ "%s: no such option", arg);
+ break;
+ case OP_ERROR_INTERNAL:
+ snprintf(buffer, sizeof(buffer), "%s: internal error", arg);
+ break;
+ case OP_ERROR_SUCCESS:
+ default:
+ snprintf(buffer, sizeof(buffer),
+ "%s: this is not an error (%d), this is a bug",
+ arg, rc);
+ break;
+ }
+ return xstrdup(buffer);
+}
+
+void op_dump_plugins(void)
+{
+ struct output_plugin *o;
+
+ printf("\nOutput Plugins: %s\n", plugin_dir);
+ list_for_each_entry(o, &op_head, node) {
+ printf(" %s\n", o->name);
+ }
+}
+
+const char *op_get_current(void)
+{
+ if (op)
+ return op->name;
+ return NULL;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _OUTPUT_H
+#define _OUTPUT_H
+
+#include "sf.h"
+#include "channelmap.h"
+
+extern int volume_max;
+extern int volume_l;
+extern int volume_r;
+
+void op_load_plugins(void);
+void op_exit_plugins(void);
+
+/*
+ * select output plugin and open its mixer
+ *
+ * errors: OP_ERROR_{ERRNO, NO_PLUGIN}
+ */
+int op_select(const char *name);
+int op_select_any(void);
+
+/*
+ * open selected plugin
+ *
+ * errors: OP_ERROR_{}
+ */
+int op_open(sample_format_t sf, const channel_position_t *channel_map);
+
+/*
+ * drop pcm data
+ *
+ * errors: OP_ERROR_{ERRNO}
+ */
+int op_drop(void);
+
+/*
+ * close plugin
+ *
+ * errors: OP_ERROR_{}
+ */
+int op_close(void);
+
+/*
+ * returns bytes written or error
+ *
+ * errors: OP_ERROR_{ERRNO}
+ */
+int op_write(const char *buffer, int count);
+
+/*
+ * errors: OP_ERROR_{}
+ */
+int op_pause(void);
+int op_unpause(void);
+
+/*
+ * returns space in output buffer in bytes or -1 if busy
+ */
+int op_buffer_space(void);
+
+/*
+ * errors: OP_ERROR_{}
+ */
+int op_reset(void);
+
+void mixer_open(void);
+void mixer_close(void);
+int mixer_set_volume(int left, int right);
+int mixer_read_volume(void);
+int mixer_get_fds(int *fds);
+
+void op_add_options(void);
+char *op_get_error_msg(int rc, const char *arg);
+void op_dump_plugins(void);
+const char *op_get_current(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "path.h"
+#include "xmalloc.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+
+const char *get_extension(const char *filename)
+{
+ const char *ext;
+
+ ext = filename + strlen(filename) - 1;
+ while (ext >= filename && *ext != '/') {
+ if (*ext == '.') {
+ ext++;
+ return ext;
+ }
+ ext--;
+ }
+ return NULL;
+}
+
+const char *path_basename(const char *path)
+{
+ const char *f;
+
+ f = strrchr(path, '/');
+
+ return f ? f + 1 : path;
+}
+
+void path_strip(char *str)
+{
+ int i, s, d;
+
+ i = 0;
+ if (str[0] == '/')
+ i = 1;
+ while (str[i]) {
+ if (str[i] == '/') {
+ d = i;
+ s = i + 1;
+ while (str[s] && str[s] == '/')
+ s++;
+ s--;
+ do {
+ str[d++] = str[++s];
+ } while (str[s]);
+ } else if (i && str[i] == '.') {
+ if (str[i + 1] == '/') {
+ d = i;
+ s = i + 1;
+ do {
+ str[d++] = str[++s];
+ } while (str[s]);
+ } else if (str[i + 1] == 0) {
+ str[i] = 0;
+ break;
+ } else if (str[i + 1] == '.' &&
+ (str[i + 2] == '/' || str[i + 2] == 0)) {
+ /* aaa/bbb/../ccc */
+ /* aaa/ccc */
+ if (str[i + 2]) {
+ s = i + 3; /* ccc */
+ } else {
+ s = i + 2;
+ }
+ d = i - 1; /* /../ccc */
+ do {
+ if (d == 0)
+ break;
+ d--;
+ } while (str[d] != '/');
+ d++;
+ /* std[d] is bbb/../ccc */
+ i = d;
+ s--;
+ do {
+ str[d++] = str[++s];
+ } while (str[s]);
+ } else {
+ while (str[i] && str[i] != '/')
+ i++;
+ if (str[i])
+ i++;
+ }
+ } else {
+ while (str[i] && str[i] != '/')
+ i++;
+ if (str[i])
+ i++;
+ }
+ }
+ if (i > 1 && str[i - 1] == '/')
+ str[i - 1] = 0;
+}
+
+char *path_absolute_cwd(const char *src, const char *cwd)
+{
+ char *str;
+
+ if (src[0] == '/') {
+ /* already absolute */
+ str = xstrdup(src);
+ } else {
+ int src_len;
+ int cwd_len;
+
+ src_len = strlen(src);
+ cwd_len = strlen(cwd);
+ str = xnew(char, cwd_len + 1 + src_len + 1);
+ memcpy(str, cwd, cwd_len);
+ str[cwd_len] = '/';
+ memcpy(str + cwd_len + 1, src, src_len + 1);
+ }
+ path_strip(str);
+ return str;
+}
+
+char *path_absolute(const char *src)
+{
+ char cwd[1024];
+
+ if (!getcwd(cwd, sizeof(cwd))) {
+ cwd[0] = '/';
+ cwd[1] = 0;
+ }
+ return path_absolute_cwd(src, cwd);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _PATH_H
+#define _PATH_H
+
+const char *get_extension(const char *filename);
+const char *path_basename(const char *path);
+void path_strip(char *str);
+char *path_absolute_cwd(const char *src, const char *cwd);
+char *path_absolute(const char *src);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pcm.h"
+#include "utils.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+
+/*
+ * Functions to convert PCM to 16-bit signed little-endian stereo
+ *
+ * Conversion for 8-bit PCM):
+ * 1. phase
+ * unsigned -> signed
+ * mono -> stereo
+ * 8 -> 16
+ *
+ * Conversion for 16-bit PCM:
+ * 1. phase
+ * be -> le
+ * unsigned -> signed
+ *
+ * 2. phase
+ * mono -> stereo
+ *
+ * There's no reason to split 8-bit conversion to 2 phases because we need to
+ * use separate buffer for 8->16 conversion anyway.
+ *
+ * Conversions for 16-bit stereo can be done in place. 16-bit mono needs to be
+ * converted to stereo so it's worthwhile to split the conversion to 2 phases.
+ */
+
+static void convert_u8_1ch_to_s16_2ch(char *dst, const char *src, int count)
+{
+ int16_t *d = (int16_t *)dst;
+ const uint8_t *s = (const uint8_t *)src;
+ int i, j = 0;
+
+ for (i = 0; i < count; i++) {
+ int16_t sample = s[i] << 8;
+ sample -= 32768;
+ d[j++] = sample;
+ d[j++] = sample;
+ }
+}
+
+static void convert_s8_1ch_to_s16_2ch(char *dst, const char *src, int count)
+{
+ int16_t *d = (int16_t *)dst;
+ const int8_t *s = (const int8_t *)src;
+ int i, j = 0;
+
+ for (i = 0; i < count; i++) {
+ int16_t sample = s[i] << 8;
+ d[j++] = sample;
+ d[j++] = sample;
+ }
+}
+
+static void convert_u8_2ch_to_s16_2ch(char *dst, const char *src, int count)
+{
+ int16_t *d = (int16_t *)dst;
+ const int8_t *s = (const int8_t *)src;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ int16_t sample = s[i] << 8;
+ sample -= 32768;
+ d[i] = sample;
+ }
+}
+
+static void convert_s8_2ch_to_s16_2ch(char *dst, const char *src, int count)
+{
+ int16_t *d = (int16_t *)dst;
+ const int8_t *s = (const int8_t *)src;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ int16_t sample = s[i] << 8;
+ d[i] = sample;
+ }
+}
+
+static void convert_u16_le_to_s16_le(char *buf, int count)
+{
+ int16_t *b = (int16_t *)buf;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ int sample = (uint16_t)b[i];
+ sample -= 32768;
+ b[i] = sample;
+ }
+}
+
+static void convert_u16_be_to_s16_le(char *buf, int count)
+{
+ int16_t *b = (int16_t *)buf;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ uint16_t u = b[i];
+ int sample;
+
+ u = swap_uint16(u);
+ sample = (int)u - 32768;
+ b[i] = sample;
+ }
+}
+
+static void swap_s16_byte_order(char *buf, int count)
+{
+ int16_t *b = (int16_t *)buf;
+ int i;
+
+ for (i = 0; i < count; i++)
+ b[i] = swap_uint16(b[i]);
+}
+
+static void convert_16_1ch_to_16_2ch(char *dst, const char *src, int count)
+{
+ int16_t *d = (int16_t *)dst;
+ const int16_t *s = (const int16_t *)src;
+ int i, j = 0;
+
+ for (i = 0; i < count; i++) {
+ d[j++] = s[i];
+ d[j++] = s[i];
+ }
+}
+
+/* index is ((bits >> 2) & 4) | (is_signed << 1) | (channels - 1) */
+pcm_conv_func pcm_conv[8] = {
+ convert_u8_1ch_to_s16_2ch,
+ convert_u8_2ch_to_s16_2ch,
+ convert_s8_1ch_to_s16_2ch,
+ convert_s8_2ch_to_s16_2ch,
+
+ convert_16_1ch_to_16_2ch,
+ NULL,
+ convert_16_1ch_to_16_2ch,
+ NULL
+};
+
+/* index is ((bits >> 2) & 4) | (is_signed << 1) | bigendian */
+pcm_conv_in_place_func pcm_conv_in_place[8] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+
+ convert_u16_le_to_s16_le,
+ convert_u16_be_to_s16_le,
+
+#ifdef WORDS_BIGENDIAN
+ swap_s16_byte_order,
+ NULL,
+#else
+ NULL,
+ swap_s16_byte_order,
+#endif
+};
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _PCM_H
+#define _PCM_H
+
+typedef void (*pcm_conv_func)(char *dst, const char *src, int count);
+typedef void (*pcm_conv_in_place_func)(char *buf, int count);
+
+extern pcm_conv_func pcm_conv[8];
+extern pcm_conv_in_place_func pcm_conv_in_place[8];
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pl.h"
+#include "editable.h"
+#include "options.h"
+#include "xmalloc.h"
+
+struct editable pl_editable;
+struct simple_track *pl_cur_track = NULL;
+
+static struct rb_root pl_shuffle_root;
+
+static void pl_free_track(struct list_head *item)
+{
+ struct simple_track *track = to_simple_track(item);
+ struct shuffle_track *shuffle_track = (struct shuffle_track *) track;
+
+ if (track == pl_cur_track)
+ pl_cur_track = NULL;
+
+ rb_erase(&shuffle_track->tree_node, &pl_shuffle_root);
+ track_info_unref(track->info);
+ free(track);
+}
+
+void pl_init(void)
+{
+ editable_init(&pl_editable, pl_free_track);
+}
+
+static int dummy_filter(const struct simple_track *track)
+{
+ return 1;
+}
+
+static struct track_info *set_track(struct simple_track *track)
+{
+ struct track_info *ti = NULL;
+
+ if (track) {
+ pl_cur_track = track;
+ ti = track->info;
+ track_info_ref(ti);
+ pl_editable.win->changed = 1;
+ }
+ return ti;
+}
+
+struct track_info *pl_set_next(void)
+{
+ struct simple_track *track;
+
+ if (list_empty(&pl_editable.head))
+ return NULL;
+
+ if (shuffle) {
+ track = (struct simple_track *)shuffle_list_get_next(&pl_shuffle_root,
+ (struct shuffle_track *)pl_cur_track, dummy_filter);
+ } else {
+ track = simple_list_get_next(&pl_editable.head, pl_cur_track, dummy_filter);
+ }
+ return set_track(track);
+}
+
+struct track_info *pl_set_prev(void)
+{
+ struct simple_track *track;
+
+ if (list_empty(&pl_editable.head))
+ return NULL;
+
+ if (shuffle) {
+ track = (struct simple_track *)shuffle_list_get_prev(&pl_shuffle_root,
+ (struct shuffle_track *)pl_cur_track, dummy_filter);
+ } else {
+ track = simple_list_get_prev(&pl_editable.head, pl_cur_track, dummy_filter);
+ }
+ return set_track(track);
+}
+
+struct track_info *pl_set_selected(void)
+{
+ struct iter sel;
+
+ if (list_empty(&pl_editable.head))
+ return NULL;
+
+ window_get_sel(pl_editable.win, &sel);
+ return set_track(iter_to_simple_track(&sel));
+}
+
+void pl_sel_current(void)
+{
+ if (pl_cur_track) {
+ struct iter iter;
+
+ editable_track_to_iter(&pl_editable, pl_cur_track, &iter);
+ window_set_sel(pl_editable.win, &iter);
+ }
+}
+
+void pl_add_track(struct track_info *ti)
+{
+ struct shuffle_track *track = xnew(struct shuffle_track, 1);
+
+ track_info_ref(ti);
+ simple_track_init((struct simple_track *)track, ti);
+ shuffle_list_add(track, &pl_shuffle_root);
+ editable_add(&pl_editable, (struct simple_track *)track);
+}
+
+void pl_reshuffle(void)
+{
+ shuffle_list_reshuffle(&pl_shuffle_root);
+}
+
+int pl_for_each(int (*cb)(void *data, struct track_info *ti), void *data)
+{
+ struct simple_track *track;
+ int rc = 0;
+
+ list_for_each_entry(track, &pl_editable.head, node) {
+ rc = cb(data, track->info);
+ if (rc)
+ break;
+ }
+ return rc;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PL_H
+#define PL_H
+
+#include "editable.h"
+#include "track_info.h"
+#include "track.h"
+
+extern struct editable pl_editable;
+extern struct simple_track *pl_cur_track;
+
+void pl_init(void);
+struct track_info *pl_set_next(void);
+struct track_info *pl_set_prev(void);
+struct track_info *pl_set_selected(void);
+void pl_add_track(struct track_info *track_info);
+void pl_sel_current(void);
+void pl_reshuffle(void);
+int pl_for_each(int (*cb)(void *data, struct track_info *ti), void *data);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005-2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "play_queue.h"
+#include "editable.h"
+#include "track.h"
+#include "xmalloc.h"
+
+struct editable pq_editable;
+
+static void pq_free_track(struct list_head *item)
+{
+ struct simple_track *track = to_simple_track(item);
+
+ track_info_unref(track->info);
+ free(track);
+}
+
+void play_queue_init(void)
+{
+ editable_init(&pq_editable, pq_free_track);
+}
+
+void play_queue_append(struct track_info *ti)
+{
+ struct simple_track *t = simple_track_new(ti);
+
+ editable_add(&pq_editable, t);
+}
+
+void play_queue_prepend(struct track_info *ti)
+{
+ struct simple_track *t = simple_track_new(ti);
+
+ editable_add_before(&pq_editable, t);
+}
+
+struct track_info *play_queue_remove(void)
+{
+ struct track_info *info = NULL;
+
+ if (!list_empty(&pq_editable.head)) {
+ struct simple_track *t = to_simple_track(pq_editable.head.next);
+ info = t->info;
+ track_info_ref(info);
+ editable_remove_track(&pq_editable, t);
+ }
+
+ return info;
+}
+
+int play_queue_for_each(int (*cb)(void *data, struct track_info *ti), void *data)
+{
+ struct simple_track *track;
+ int rc = 0;
+
+ list_for_each_entry(track, &pq_editable.head, node) {
+ rc = cb(data, track->info);
+ if (rc)
+ break;
+ }
+ return rc;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005-2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _PLAY_QUEUE_H
+#define _PLAY_QUEUE_H
+
+#include "editable.h"
+#include "track_info.h"
+
+extern struct editable pq_editable;
+
+void play_queue_init(void);
+void play_queue_append(struct track_info *ti);
+void play_queue_prepend(struct track_info *ti);
+struct track_info *play_queue_remove(void);
+int play_queue_for_each(int (*cb)(void *data, struct track_info *ti), void *data);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "player.h"
+#include "buffer.h"
+#include "input.h"
+#include "output.h"
+#include "sf.h"
+#include "utils.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "compiler.h"
+#include "options.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/time.h>
+#include <stdarg.h>
+#include <math.h>
+
+const char * const player_status_names[] = { "stopped", "playing", "paused", NULL };
+
+enum producer_status {
+ PS_UNLOADED,
+ PS_STOPPED,
+ PS_PLAYING,
+ PS_PAUSED
+};
+
+enum consumer_status {
+ CS_STOPPED,
+ CS_PLAYING,
+ CS_PAUSED
+};
+
+struct player_info player_info = {
+ .mutex = CMUS_MUTEX_INITIALIZER,
+ .ti = NULL,
+ .metadata = { 0, },
+ .status = PLAYER_STATUS_STOPPED,
+ .pos = 0,
+ .current_bitrate = -1,
+ .buffer_fill = 0,
+ .buffer_size = 0,
+ .error_msg = NULL,
+ .file_changed = 0,
+ .metadata_changed = 0,
+ .status_changed = 0,
+ .position_changed = 0,
+ .buffer_fill_changed = 0,
+};
+
+/* continue playing after track is finished? */
+int player_cont = 1;
+
+/* repeat current track forever? */
+int player_repeat_current;
+
+enum replaygain replaygain;
+int replaygain_limit = 1;
+double replaygain_preamp = 6.0;
+
+int soft_vol;
+int soft_vol_l;
+int soft_vol_r;
+
+static const struct player_callbacks *player_cbs = NULL;
+
+static sample_format_t buffer_sf;
+static CHANNEL_MAP(buffer_channel_map);
+
+static pthread_t producer_thread;
+static pthread_mutex_t producer_mutex = CMUS_MUTEX_INITIALIZER;
+static pthread_cond_t producer_playing = CMUS_COND_INITIALIZER;
+static int producer_running = 1;
+static enum producer_status producer_status = PS_UNLOADED;
+static struct input_plugin *ip = NULL;
+
+static pthread_t consumer_thread;
+static pthread_mutex_t consumer_mutex = CMUS_MUTEX_INITIALIZER;
+static pthread_cond_t consumer_playing = CMUS_COND_INITIALIZER;
+static int consumer_running = 1;
+static enum consumer_status consumer_status = CS_STOPPED;
+static unsigned int consumer_pos = 0;
+
+/* for replay gain and soft vol
+ * usually same as consumer_pos, sometimes less than consumer_pos
+ */
+static unsigned int scale_pos;
+static double replaygain_scale = 1.0;
+
+/* locking {{{ */
+
+#define producer_lock() cmus_mutex_lock(&producer_mutex)
+#define producer_unlock() cmus_mutex_unlock(&producer_mutex)
+
+#define consumer_lock() cmus_mutex_lock(&consumer_mutex)
+#define consumer_unlock() cmus_mutex_unlock(&consumer_mutex)
+
+#define player_lock() \
+ do { \
+ consumer_lock(); \
+ producer_lock(); \
+ } while (0)
+
+#define player_unlock() \
+ do { \
+ producer_unlock(); \
+ consumer_unlock(); \
+ } while (0)
+
+/* locking }}} */
+
+static void reset_buffer(void)
+{
+ buffer_reset();
+ consumer_pos = 0;
+ scale_pos = 0;
+ pthread_cond_broadcast(&producer_playing);
+}
+
+static void set_buffer_sf(void)
+{
+ buffer_sf = ip_get_sf(ip);
+ ip_get_channel_map(ip, buffer_channel_map);
+
+ /* ip_read converts samples to this format */
+ if (sf_get_channels(buffer_sf) <= 2 && sf_get_bits(buffer_sf) <= 16) {
+ buffer_sf &= SF_RATE_MASK;
+ buffer_sf |= sf_channels(2) | sf_bits(16) | sf_signed(1);
+#ifdef WORDS_BIGENDIAN
+ buffer_sf |= sf_bigendian(1);
+#endif
+ channel_map_init_stereo(buffer_channel_map);
+ }
+}
+
+#define SOFT_VOL_SCALE 65536
+
+/* coefficients for volumes 0..99, for 100 65536 is used
+ * data copied from alsa-lib src/pcm/pcm_softvol.c
+ */
+static const unsigned short soft_vol_db[100] = {
+ 0x0000, 0x0110, 0x011c, 0x012f, 0x013d, 0x0152, 0x0161, 0x0179,
+ 0x018a, 0x01a5, 0x01c1, 0x01d5, 0x01f5, 0x020b, 0x022e, 0x0247,
+ 0x026e, 0x028a, 0x02b6, 0x02d5, 0x0306, 0x033a, 0x035f, 0x0399,
+ 0x03c2, 0x0403, 0x0431, 0x0479, 0x04ac, 0x04fd, 0x0553, 0x058f,
+ 0x05ef, 0x0633, 0x069e, 0x06ea, 0x0761, 0x07b5, 0x083a, 0x0898,
+ 0x092c, 0x09cb, 0x0a3a, 0x0aeb, 0x0b67, 0x0c2c, 0x0cb6, 0x0d92,
+ 0x0e2d, 0x0f21, 0x1027, 0x10de, 0x1202, 0x12cf, 0x1414, 0x14f8,
+ 0x1662, 0x1761, 0x18f5, 0x1a11, 0x1bd3, 0x1db4, 0x1f06, 0x211d,
+ 0x2297, 0x24ec, 0x2690, 0x292a, 0x2aff, 0x2de5, 0x30fe, 0x332b,
+ 0x369f, 0x390d, 0x3ce6, 0x3f9b, 0x43e6, 0x46eb, 0x4bb3, 0x4f11,
+ 0x5466, 0x5a18, 0x5e19, 0x6472, 0x68ea, 0x6ffd, 0x74f8, 0x7cdc,
+ 0x826a, 0x8b35, 0x9499, 0x9b35, 0xa5ad, 0xad0b, 0xb8b7, 0xc0ee,
+ 0xcdf1, 0xd71a, 0xe59c, 0xefd3
+};
+
+static inline void scale_sample_int16_t(int16_t *buf, int i, int vol, int swap)
+{
+ int32_t sample = swap ? (int16_t)swap_uint16(buf[i]) : buf[i];
+
+ if (sample < 0) {
+ sample = (sample * vol - SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
+ if (sample < INT16_MIN)
+ sample = INT16_MIN;
+ } else {
+ sample = (sample * vol + SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
+ if (sample > INT16_MAX)
+ sample = INT16_MAX;
+ }
+ buf[i] = swap ? swap_uint16(sample) : sample;
+}
+
+static inline int32_t scale_sample_s24le(int32_t s, int vol)
+{
+ int64_t sample = s;
+ if (sample < 0) {
+ sample = (sample * vol - SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
+ if (sample < -0x800000)
+ sample = -0x800000;
+ } else {
+ sample = (sample * vol + SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
+ if (sample > 0x7fffff)
+ sample = 0x7fffff;
+ }
+ return sample;
+}
+
+static inline void scale_sample_int32_t(int32_t *buf, int i, int vol, int swap)
+{
+ int64_t sample = swap ? (int32_t)swap_uint32(buf[i]) : buf[i];
+
+ if (sample < 0) {
+ sample = (sample * vol - SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
+ if (sample < INT32_MIN)
+ sample = INT32_MIN;
+ } else {
+ sample = (sample * vol + SOFT_VOL_SCALE / 2) / SOFT_VOL_SCALE;
+ if (sample > INT32_MAX)
+ sample = INT32_MAX;
+ }
+ buf[i] = swap ? swap_uint32(sample) : sample;
+}
+
+static inline int sf_need_swap(sample_format_t sf)
+{
+#ifdef WORDS_BIGENDIAN
+ return !sf_get_bigendian(sf);
+#else
+ return sf_get_bigendian(sf);
+#endif
+}
+
+#define SCALE_SAMPLES(TYPE, buffer, count, l, r, swap) \
+{ \
+ const int frames = count / sizeof(TYPE) / 2; \
+ TYPE *buf = (TYPE *) buffer; \
+ int i; \
+ /* avoid underflowing -32768 to 32767 when scale is 65536 */ \
+ if (l != SOFT_VOL_SCALE && r != SOFT_VOL_SCALE) { \
+ for (i = 0; i < frames; i++) { \
+ scale_sample_##TYPE(buf, i * 2, l, swap); \
+ scale_sample_##TYPE(buf, i * 2 + 1, r, swap); \
+ } \
+ } else if (l != SOFT_VOL_SCALE) { \
+ for (i = 0; i < frames; i++) \
+ scale_sample_##TYPE(buf, i * 2, l, swap); \
+ } else if (r != SOFT_VOL_SCALE) { \
+ for (i = 0; i < frames; i++) \
+ scale_sample_##TYPE(buf, i * 2 + 1, r, swap); \
+ } \
+}
+
+static inline int32_t read_s24le(const char *buf)
+{
+ const unsigned char *b = (const unsigned char *) buf;
+ return b[0] | (b[1] << 8) | (((const signed char *) buf)[2] << 16);
+}
+
+static inline void write_s24le(char *buf, int32_t x)
+{
+ unsigned char *b = (unsigned char *) buf;
+ b[0] = x;
+ b[1] = x >> 8;
+ b[2] = x >> 16;
+}
+
+static void scale_samples_s24le(char *buf, unsigned int count, int l, int r)
+{
+ int frames = count / 3 / 2;
+ if (l != SOFT_VOL_SCALE && r != SOFT_VOL_SCALE) {
+ while (frames--) {
+ write_s24le(buf, scale_sample_s24le(read_s24le(buf), l));
+ buf += 3;
+ write_s24le(buf, scale_sample_s24le(read_s24le(buf), r));
+ buf += 3;
+ }
+ } else if (l != SOFT_VOL_SCALE) {
+ while (frames--) {
+ write_s24le(buf, scale_sample_s24le(read_s24le(buf), l));
+ buf += 3 * 2;
+ }
+ } else if (r != SOFT_VOL_SCALE) {
+ buf += 3;
+ while (frames--) {
+ write_s24le(buf, scale_sample_s24le(read_s24le(buf), r));
+ buf += 3 * 2;
+ }
+ }
+}
+
+static void scale_samples(char *buffer, unsigned int *countp)
+{
+ unsigned int count = *countp;
+ int ch, bits, l, r;
+
+ BUG_ON(scale_pos < consumer_pos);
+
+ if (consumer_pos != scale_pos) {
+ unsigned int offs = scale_pos - consumer_pos;
+
+ if (offs >= count)
+ return;
+ buffer += offs;
+ count -= offs;
+ }
+ scale_pos += count;
+
+ if (replaygain_scale == 1.0 && soft_vol_l == 100 && soft_vol_r == 100)
+ return;
+
+ ch = sf_get_channels(buffer_sf);
+ bits = sf_get_bits(buffer_sf);
+ if (ch != 2 || (bits != 16 && bits != 24 && bits != 32))
+ return;
+
+ l = SOFT_VOL_SCALE;
+ r = SOFT_VOL_SCALE;
+ if (soft_vol && soft_vol_l != 100)
+ l = soft_vol_db[soft_vol_l];
+ if (soft_vol && soft_vol_r != 100)
+ r = soft_vol_db[soft_vol_r];
+
+ l *= replaygain_scale;
+ r *= replaygain_scale;
+
+ switch (bits) {
+ case 16:
+ SCALE_SAMPLES(int16_t, buffer, count, l, r, sf_need_swap(buffer_sf));
+ break;
+ case 24:
+ if (likely(!sf_get_bigendian(buffer_sf)))
+ scale_samples_s24le(buffer, count, l, r);
+ break;
+ case 32:
+ SCALE_SAMPLES(int32_t, buffer, count, l, r, sf_need_swap(buffer_sf));
+ break;
+ }
+}
+
+static void update_rg_scale(void)
+{
+ double gain, peak, db, scale, limit;
+
+ replaygain_scale = 1.0;
+ if (!player_info.ti || !replaygain)
+ return;
+
+ if (replaygain == RG_TRACK || replaygain == RG_TRACK_PREFERRED) {
+ gain = player_info.ti->rg_track_gain;
+ peak = player_info.ti->rg_track_peak;
+ } else {
+ gain = player_info.ti->rg_album_gain;
+ peak = player_info.ti->rg_album_peak;
+ }
+
+ if (isnan(gain)) {
+ if (replaygain == RG_TRACK_PREFERRED) {
+ gain = player_info.ti->rg_album_gain;
+ peak = player_info.ti->rg_album_peak;
+ } else if (replaygain == RG_ALBUM_PREFERRED) {
+ gain = player_info.ti->rg_track_gain;
+ peak = player_info.ti->rg_track_peak;
+ }
+ }
+
+ if (isnan(gain)) {
+ d_print("gain not available\n");
+ return;
+ }
+ if (!isnan(peak) && peak < 0.05) {
+ d_print("peak (%g) is too small\n", peak);
+ return;
+ }
+
+ db = replaygain_preamp + gain;
+
+ scale = pow(10.0, db / 20.0);
+ replaygain_scale = scale;
+ limit = 1.0 / peak;
+ if (replaygain_limit && !isnan(peak)) {
+ if (replaygain_scale > limit)
+ replaygain_scale = limit;
+ }
+
+ d_print("gain = %f, peak = %f, db = %f, scale = %f, limit = %f, replaygain_scale = %f\n",
+ gain, peak, db, scale, limit, replaygain_scale);
+}
+
+static inline unsigned int buffer_second_size(void)
+{
+ return sf_get_second_size(buffer_sf);
+}
+
+static inline int get_next(struct track_info **ti)
+{
+ return player_cbs->get_next(ti);
+}
+
+/* updating player status {{{ */
+
+static inline void __file_changed(struct track_info *ti)
+{
+ if (player_info.ti)
+ track_info_unref(player_info.ti);
+
+ player_info.ti = ti;
+ update_rg_scale();
+ player_info.metadata[0] = 0;
+ player_info.file_changed = 1;
+}
+
+static inline void file_changed(struct track_info *ti)
+{
+ player_info_lock();
+ if (ti) {
+ d_print("file: %s\n", ti->filename);
+ } else {
+ d_print("unloaded\n");
+ }
+ __file_changed(ti);
+ player_info_unlock();
+}
+
+static inline void metadata_changed(void)
+{
+ struct keyval *comments;
+ int rc;
+
+ player_info_lock();
+ if (ip_get_metadata(ip)) {
+ d_print("metadata changed: %s\n", ip_get_metadata(ip));
+ memcpy(player_info.metadata, ip_get_metadata(ip),
+ 255 * 16 + 1);
+ }
+
+ rc = ip_read_comments(ip, &comments);
+ if (!rc) {
+ if (player_info.ti->comments)
+ keyvals_free(player_info.ti->comments);
+ track_info_set_comments(player_info.ti, comments);
+ }
+
+ player_info.metadata_changed = 1;
+ player_info_unlock();
+}
+
+static void player_error(const char *msg)
+{
+ player_info_lock();
+ player_info.status = (enum player_status)consumer_status;
+ player_info.pos = 0;
+ player_info.current_bitrate = -1;
+ player_info.buffer_fill = buffer_get_filled_chunks();
+ player_info.buffer_size = buffer_nr_chunks;
+ player_info.status_changed = 1;
+
+ free(player_info.error_msg);
+ player_info.error_msg = xstrdup(msg);
+ player_info_unlock();
+
+ d_print("ERROR: '%s'\n", msg);
+}
+
+static void __FORMAT(2, 3) player_ip_error(int rc, const char *format, ...)
+{
+ char buffer[1024];
+ va_list ap;
+ char *msg;
+ int save = errno;
+
+ va_start(ap, format);
+ vsnprintf(buffer, sizeof(buffer), format, ap);
+ va_end(ap);
+
+ errno = save;
+ msg = ip_get_error_msg(ip, rc, buffer);
+ player_error(msg);
+ free(msg);
+}
+
+static void __FORMAT(2, 3) player_op_error(int rc, const char *format, ...)
+{
+ char buffer[1024];
+ va_list ap;
+ char *msg;
+ int save = errno;
+
+ va_start(ap, format);
+ vsnprintf(buffer, sizeof(buffer), format, ap);
+ va_end(ap);
+
+ errno = save;
+ msg = op_get_error_msg(rc, buffer);
+ player_error(msg);
+ free(msg);
+}
+
+/*
+ * buffer-fill changed
+ */
+static void __producer_buffer_fill_update(void)
+{
+ int fill;
+
+ player_info_lock();
+ fill = buffer_get_filled_chunks();
+ if (fill != player_info.buffer_fill) {
+/* d_print("\n"); */
+ player_info.buffer_fill = fill;
+ player_info.buffer_fill_changed = 1;
+ }
+ player_info_unlock();
+}
+
+/*
+ * playing position changed
+ */
+static void __consumer_position_update(void)
+{
+ static unsigned int old_pos = -1;
+ unsigned int pos = 0;
+ long bitrate;
+
+ if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED)
+ pos = consumer_pos / buffer_second_size();
+ if (pos != old_pos) {
+ old_pos = pos;
+
+ player_info_lock();
+ player_info.pos = pos;
+
+ if (show_current_bitrate) {
+ bitrate = ip_current_bitrate(ip);
+ if (bitrate != -1)
+ player_info.current_bitrate = bitrate;
+ }
+ player_info.position_changed = 1;
+ player_info_unlock();
+ }
+}
+
+/*
+ * something big happened (stopped/paused/unpaused...)
+ */
+static void __player_status_changed(void)
+{
+ unsigned int pos = 0;
+
+/* d_print("\n"); */
+ if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED)
+ pos = consumer_pos / buffer_second_size();
+
+ player_info_lock();
+ player_info.status = (enum player_status)consumer_status;
+ player_info.pos = pos;
+ player_info.current_bitrate = -1;
+ player_info.buffer_fill = buffer_get_filled_chunks();
+ player_info.buffer_size = buffer_nr_chunks;
+ player_info.status_changed = 1;
+ player_info_unlock();
+}
+
+/* updating player status }}} */
+
+static void __prebuffer(void)
+{
+ int limit_chunks;
+
+ BUG_ON(producer_status != PS_PLAYING);
+ if (ip_is_remote(ip)) {
+ limit_chunks = buffer_nr_chunks;
+ } else {
+ int limit_ms, limit_size;
+
+ limit_ms = 250;
+ limit_size = limit_ms * buffer_second_size() / 1000;
+ limit_chunks = limit_size / CHUNK_SIZE;
+ if (limit_chunks < 1)
+ limit_chunks = 1;
+ }
+ while (1) {
+ int nr_read, size, filled;
+ char *wpos;
+
+ filled = buffer_get_filled_chunks();
+/* d_print("PREBUF: %2d / %2d\n", filled, limit_chunks); */
+
+ /* not fatal */
+ //BUG_ON(filled > limit_chunks);
+
+ if (filled >= limit_chunks)
+ break;
+
+ size = buffer_get_wpos(&wpos);
+ nr_read = ip_read(ip, wpos, size);
+ if (nr_read < 0) {
+ if (nr_read == -1 && errno == EAGAIN)
+ continue;
+ player_ip_error(nr_read, "reading file %s", ip_get_filename(ip));
+ /* ip_read sets eof */
+ nr_read = 0;
+ }
+ if (ip_metadata_changed(ip))
+ metadata_changed();
+
+ /* buffer_fill with 0 count marks current chunk filled */
+ buffer_fill(nr_read);
+
+ __producer_buffer_fill_update();
+ if (nr_read == 0) {
+ /* EOF */
+ break;
+ }
+ }
+}
+
+/* setting producer status {{{ */
+
+static void __producer_status_update(enum producer_status status)
+{
+ producer_status = status;
+ pthread_cond_broadcast(&producer_playing);
+}
+
+static void __producer_play(void)
+{
+ if (producer_status == PS_UNLOADED) {
+ struct track_info *ti;
+
+ if (get_next(&ti) == 0) {
+ int rc;
+
+ ip = ip_new(ti->filename);
+ rc = ip_open(ip);
+ if (rc) {
+ player_ip_error(rc, "opening file `%s'", ti->filename);
+ ip_delete(ip);
+ track_info_unref(ti);
+ file_changed(NULL);
+ } else {
+ ip_setup(ip);
+ __producer_status_update(PS_PLAYING);
+ file_changed(ti);
+ }
+ }
+ } else if (producer_status == PS_PLAYING) {
+ if (ip_seek(ip, 0.0) == 0) {
+ reset_buffer();
+ }
+ } else if (producer_status == PS_STOPPED) {
+ int rc;
+
+ rc = ip_open(ip);
+ if (rc) {
+ player_ip_error(rc, "opening file `%s'", ip_get_filename(ip));
+ ip_delete(ip);
+ __producer_status_update(PS_UNLOADED);
+ } else {
+ ip_setup(ip);
+ __producer_status_update(PS_PLAYING);
+ }
+ } else if (producer_status == PS_PAUSED) {
+ __producer_status_update(PS_PLAYING);
+ }
+}
+
+static void __producer_stop(void)
+{
+ if (producer_status == PS_PLAYING || producer_status == PS_PAUSED) {
+ ip_close(ip);
+ __producer_status_update(PS_STOPPED);
+ reset_buffer();
+ }
+}
+
+static void __producer_unload(void)
+{
+ __producer_stop();
+ if (producer_status == PS_STOPPED) {
+ ip_delete(ip);
+ __producer_status_update(PS_UNLOADED);
+ }
+}
+
+static void __producer_pause(void)
+{
+ if (producer_status == PS_PLAYING) {
+ __producer_status_update(PS_PAUSED);
+ } else if (producer_status == PS_PAUSED) {
+ __producer_status_update(PS_PLAYING);
+ }
+}
+
+static void __producer_set_file(struct track_info *ti)
+{
+ __producer_unload();
+ ip = ip_new(ti->filename);
+ __producer_status_update(PS_STOPPED);
+ file_changed(ti);
+}
+
+/* setting producer status }}} */
+
+/* setting consumer status {{{ */
+
+static void __consumer_status_update(enum consumer_status status)
+{
+ consumer_status = status;
+ pthread_cond_broadcast(&consumer_playing);
+}
+
+static void __consumer_play(void)
+{
+ if (consumer_status == CS_PLAYING) {
+ op_drop();
+ } else if (consumer_status == CS_STOPPED) {
+ int rc;
+
+ set_buffer_sf();
+ rc = op_open(buffer_sf, buffer_channel_map);
+ if (rc) {
+ player_op_error(rc, "opening audio device");
+ } else {
+ __consumer_status_update(CS_PLAYING);
+ }
+ } else if (consumer_status == CS_PAUSED) {
+ op_unpause();
+ __consumer_status_update(CS_PLAYING);
+ }
+}
+
+static void __consumer_drain_and_stop(void)
+{
+ if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
+ op_close();
+ __consumer_status_update(CS_STOPPED);
+ }
+}
+
+static void __consumer_stop(void)
+{
+ if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
+ op_drop();
+ op_close();
+ __consumer_status_update(CS_STOPPED);
+ }
+}
+
+static void __consumer_pause(void)
+{
+ if (consumer_status == CS_PLAYING) {
+ op_pause();
+ __consumer_status_update(CS_PAUSED);
+ } else if (consumer_status == CS_PAUSED) {
+ op_unpause();
+ __consumer_status_update(CS_PLAYING);
+ }
+}
+
+/* setting consumer status }}} */
+
+
+static int change_sf(int drop)
+{
+ int old_sf = buffer_sf;
+ CHANNEL_MAP(old_channel_map);
+ channel_map_copy(old_channel_map, buffer_channel_map);
+
+ set_buffer_sf();
+ if (buffer_sf != old_sf || !channel_map_equal(buffer_channel_map, old_channel_map, sf_get_channels(buffer_sf))) {
+ /* reopen */
+ int rc;
+
+ if (drop)
+ op_drop();
+ op_close();
+ rc = op_open(buffer_sf, buffer_channel_map);
+ if (rc) {
+ player_op_error(rc, "opening audio device");
+ __consumer_status_update(CS_STOPPED);
+ __producer_stop();
+ return rc;
+ }
+ } else if (consumer_status == CS_PAUSED) {
+ op_drop();
+ op_unpause();
+ }
+ __consumer_status_update(CS_PLAYING);
+ return 0;
+}
+
+static void __consumer_handle_eof(void)
+{
+ struct track_info *ti;
+
+ if (ip_is_remote(ip)) {
+ __producer_stop();
+ __consumer_drain_and_stop();
+ player_error("lost connection");
+ return;
+ }
+
+ if (player_repeat_current) {
+ if (player_cont) {
+ ip_seek(ip, 0);
+ reset_buffer();
+ } else {
+ __producer_stop();
+ __consumer_drain_and_stop();
+ __player_status_changed();
+ }
+ return;
+ }
+
+ if (get_next(&ti) == 0) {
+ __producer_unload();
+ ip = ip_new(ti->filename);
+ __producer_status_update(PS_STOPPED);
+ /* PS_STOPPED, CS_PLAYING */
+ if (player_cont) {
+ __producer_play();
+ if (producer_status == PS_UNLOADED) {
+ __consumer_stop();
+ track_info_unref(ti);
+ file_changed(NULL);
+ } else {
+ /* PS_PLAYING */
+ file_changed(ti);
+ if (!change_sf(0))
+ __prebuffer();
+ }
+ } else {
+ __consumer_drain_and_stop();
+ file_changed(ti);
+ }
+ } else {
+ __producer_unload();
+ __consumer_drain_and_stop();
+ file_changed(NULL);
+ }
+ __player_status_changed();
+}
+
+static void *consumer_loop(void *arg)
+{
+ while (1) {
+ int rc, space;
+ int size;
+ char *rpos;
+
+ consumer_lock();
+ if (!consumer_running)
+ break;
+
+ if (consumer_status == CS_PAUSED || consumer_status == CS_STOPPED) {
+ pthread_cond_wait(&consumer_playing, &consumer_mutex);
+ consumer_unlock();
+ continue;
+ }
+ space = op_buffer_space();
+ if (space < 0) {
+ d_print("op_buffer_space returned %d %s\n", space,
+ space == -1 ? strerror(errno) : "");
+
+ /* try to reopen */
+ op_close();
+ __consumer_status_update(CS_STOPPED);
+ __consumer_play();
+
+ consumer_unlock();
+ continue;
+ }
+/* d_print("BS: %6d %3d\n", space, space * 1000 / (44100 * 2 * 2)); */
+
+ while (1) {
+ if (space == 0) {
+ __consumer_position_update();
+ consumer_unlock();
+ ms_sleep(25);
+ break;
+ }
+ size = buffer_get_rpos(&rpos);
+ if (size == 0) {
+ producer_lock();
+ if (producer_status != PS_PLAYING) {
+ producer_unlock();
+ consumer_unlock();
+ break;
+ }
+ /* must recheck rpos */
+ size = buffer_get_rpos(&rpos);
+ if (size == 0) {
+ /* OK. now it's safe to check if we are at EOF */
+ if (ip_eof(ip)) {
+ /* EOF */
+ __consumer_handle_eof();
+ producer_unlock();
+ consumer_unlock();
+ break;
+ } else {
+ /* possible underrun */
+ producer_unlock();
+ __consumer_position_update();
+ consumer_unlock();
+/* d_print("possible underrun\n"); */
+ ms_sleep(10);
+ break;
+ }
+ }
+
+ /* player_buffer and ip.eof were inconsistent */
+ producer_unlock();
+ }
+ if (size > space)
+ size = space;
+ if (soft_vol || replaygain)
+ scale_samples(rpos, (unsigned int *)&size);
+ rc = op_write(rpos, size);
+ if (rc < 0) {
+ d_print("op_write returned %d %s\n", rc,
+ rc == -1 ? strerror(errno) : "");
+
+ /* try to reopen */
+ op_close();
+ __consumer_status_update(CS_STOPPED);
+ __consumer_play();
+
+ consumer_unlock();
+ break;
+ }
+ buffer_consume(rc);
+ consumer_pos += rc;
+ space -= rc;
+ }
+ }
+ __consumer_stop();
+ consumer_unlock();
+ return NULL;
+}
+
+static void *producer_loop(void *arg)
+{
+ while (1) {
+ /* number of chunks to fill
+ * too big => seeking is slow
+ * too small => underruns?
+ */
+ const int chunks = 1;
+ int size, nr_read, i;
+ char *wpos;
+
+ producer_lock();
+ if (!producer_running)
+ break;
+
+ if (producer_status == PS_UNLOADED ||
+ producer_status == PS_PAUSED ||
+ producer_status == PS_STOPPED || ip_eof(ip)) {
+ pthread_cond_wait(&producer_playing, &producer_mutex);
+ producer_unlock();
+ continue;
+ }
+ for (i = 0; ; i++) {
+ size = buffer_get_wpos(&wpos);
+ if (size == 0) {
+ /* buffer is full */
+ producer_unlock();
+ ms_sleep(50);
+ break;
+ }
+ nr_read = ip_read(ip, wpos, size);
+ if (nr_read < 0) {
+ if (nr_read != -1 || errno != EAGAIN) {
+ player_ip_error(nr_read, "reading file %s",
+ ip_get_filename(ip));
+ /* ip_read sets eof */
+ nr_read = 0;
+ } else {
+ producer_unlock();
+ ms_sleep(50);
+ break;
+ }
+ }
+ if (ip_metadata_changed(ip))
+ metadata_changed();
+
+ /* buffer_fill with 0 count marks current chunk filled */
+ buffer_fill(nr_read);
+ if (nr_read == 0) {
+ /* consumer handles EOF */
+ producer_unlock();
+ ms_sleep(50);
+ break;
+ }
+ if (i == chunks) {
+ producer_unlock();
+ /* don't sleep! */
+ break;
+ }
+ }
+ __producer_buffer_fill_update();
+ }
+ __producer_unload();
+ producer_unlock();
+ return NULL;
+}
+
+void player_init(const struct player_callbacks *callbacks)
+{
+ int rc;
+#ifdef REALTIME_SCHEDULING
+ pthread_attr_t attr;
+#endif
+ pthread_attr_t *attrp = NULL;
+
+ /* 1 s is 176400 B (0.168 MB)
+ * 10 s is 1.68 MB
+ */
+ buffer_nr_chunks = 10 * 44100 * 16 / 8 * 2 / CHUNK_SIZE;
+ buffer_init();
+
+ player_cbs = callbacks;
+
+#ifdef REALTIME_SCHEDULING
+ rc = pthread_attr_init(&attr);
+ BUG_ON(rc);
+ rc = pthread_attr_setschedpolicy(&attr, SCHED_RR);
+ if (rc) {
+ d_print("could not set real-time scheduling priority: %s\n", strerror(rc));
+ } else {
+ struct sched_param param;
+
+ d_print("using real-time scheduling\n");
+ param.sched_priority = sched_get_priority_max(SCHED_RR);
+ d_print("setting priority to %d\n", param.sched_priority);
+ rc = pthread_attr_setschedparam(&attr, ¶m);
+ BUG_ON(rc);
+ attrp = &attr;
+ }
+#endif
+
+ rc = pthread_create(&producer_thread, NULL, producer_loop, NULL);
+ BUG_ON(rc);
+
+ rc = pthread_create(&consumer_thread, attrp, consumer_loop, NULL);
+ if (rc && attrp) {
+ d_print("could not create thread using real-time scheduling: %s\n", strerror(rc));
+ rc = pthread_create(&consumer_thread, NULL, consumer_loop, NULL);
+ }
+ BUG_ON(rc);
+
+ /* update player_info.cont etc. */
+ player_lock();
+ __player_status_changed();
+ player_unlock();
+}
+
+void player_exit(void)
+{
+ int rc;
+
+ player_lock();
+ consumer_running = 0;
+ pthread_cond_broadcast(&consumer_playing);
+ producer_running = 0;
+ pthread_cond_broadcast(&producer_playing);
+ player_unlock();
+
+ rc = pthread_join(consumer_thread, NULL);
+ BUG_ON(rc);
+ rc = pthread_join(producer_thread, NULL);
+ BUG_ON(rc);
+ buffer_free();
+}
+
+void player_stop(void)
+{
+ player_lock();
+ __consumer_stop();
+ __producer_stop();
+ __player_status_changed();
+ player_unlock();
+}
+
+void player_play(void)
+{
+ int prebuffer;
+
+ player_lock();
+ if (producer_status == PS_PLAYING && ip_is_remote(ip)) {
+ /* seeking not allowed */
+ player_unlock();
+ return;
+ }
+ prebuffer = consumer_status == CS_STOPPED;
+ __producer_play();
+ if (producer_status == PS_PLAYING) {
+ __consumer_play();
+ if (consumer_status != CS_PLAYING)
+ __producer_stop();
+ } else {
+ __consumer_stop();
+ }
+ __player_status_changed();
+ if (consumer_status == CS_PLAYING && prebuffer)
+ __prebuffer();
+ player_unlock();
+}
+
+void player_pause(void)
+{
+ player_lock();
+
+ if (consumer_status == CS_STOPPED) {
+ __producer_play();
+ if (producer_status == PS_PLAYING) {
+ __consumer_play();
+ if (consumer_status != CS_PLAYING)
+ __producer_stop();
+ }
+ __player_status_changed();
+ if (consumer_status == CS_PLAYING)
+ __prebuffer();
+ player_unlock();
+ return;
+ }
+
+ if (ip && ip_is_remote(ip)) {
+ /* pausing not allowed */
+ player_unlock();
+ return;
+ }
+ __producer_pause();
+ __consumer_pause();
+ __player_status_changed();
+ player_unlock();
+}
+
+void player_set_file(struct track_info *ti)
+{
+ player_lock();
+ __producer_set_file(ti);
+ if (producer_status == PS_UNLOADED) {
+ __consumer_stop();
+ goto out;
+ }
+
+ /* PS_STOPPED */
+ if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
+ op_drop();
+ __producer_play();
+ if (producer_status == PS_UNLOADED) {
+ __consumer_stop();
+ goto out;
+ }
+ change_sf(1);
+ }
+out:
+ __player_status_changed();
+ if (producer_status == PS_PLAYING)
+ __prebuffer();
+ player_unlock();
+}
+
+void player_play_file(struct track_info *ti)
+{
+ player_lock();
+ __producer_set_file(ti);
+ if (producer_status == PS_UNLOADED) {
+ __consumer_stop();
+ goto out;
+ }
+
+ /* PS_STOPPED */
+ __producer_play();
+
+ /* PS_UNLOADED,PS_PLAYING */
+ if (producer_status == PS_UNLOADED) {
+ __consumer_stop();
+ goto out;
+ }
+
+ /* PS_PLAYING */
+ if (consumer_status == CS_STOPPED) {
+ __consumer_play();
+ if (consumer_status == CS_STOPPED)
+ __producer_stop();
+ } else {
+ op_drop();
+ change_sf(1);
+ }
+out:
+ __player_status_changed();
+ if (producer_status == PS_PLAYING)
+ __prebuffer();
+ player_unlock();
+}
+
+void player_file_changed(struct track_info *ti)
+{
+ __file_changed(ti);
+}
+
+void player_seek(double offset, int relative, int start_playing)
+{
+ int stopped = 0;
+ player_lock();
+ if (consumer_status == CS_STOPPED) {
+ stopped = 1;
+ __producer_play();
+ if (producer_status == PS_PLAYING) {
+ __consumer_play();
+ if (consumer_status != CS_PLAYING) {
+ __producer_stop();
+ player_unlock();
+ return;
+ } else
+ __player_status_changed();
+ }
+ }
+ if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
+ double pos, duration, new_pos;
+ int rc;
+
+ pos = (double)consumer_pos / (double)buffer_second_size();
+ duration = ip_duration(ip);
+ if (duration < 0) {
+ /* can't seek */
+ d_print("can't seek\n");
+ player_unlock();
+ return;
+ }
+ if (relative) {
+ new_pos = pos + offset;
+ if (new_pos < 0.0)
+ new_pos = 0.0;
+ if (offset > 0.0) {
+ /* seeking forward */
+ if (new_pos > duration - 5.0)
+ new_pos = duration - 5.0;
+ if (new_pos < 0.0)
+ new_pos = 0.0;
+ if (new_pos < pos - 0.5) {
+ /* must seek at least 0.5s */
+ d_print("must seek at least 0.5s\n");
+ player_unlock();
+ return;
+ }
+ }
+ } else {
+ new_pos = offset;
+ if (new_pos < 0.0) {
+ d_print("seek offset negative\n");
+ player_unlock();
+ return;
+ }
+ if (new_pos > duration - 5.0) {
+ new_pos = duration - 5.0;
+ if (new_pos < 0.0)
+ new_pos = 0.0;
+ }
+ }
+/* d_print("seeking %g/%g (%g from eof)\n", new_pos, duration, duration - new_pos); */
+ rc = ip_seek(ip, new_pos);
+ if (rc == 0) {
+ d_print("doing op_drop after seek\n");
+ op_drop();
+ reset_buffer();
+ consumer_pos = new_pos * buffer_second_size();
+ scale_pos = consumer_pos;
+ __consumer_position_update();
+ if (stopped && !start_playing) {
+ __producer_pause();
+ __consumer_pause();
+ __player_status_changed();
+ }
+ } else {
+ player_ip_error(rc, "seeking in file %s", ip_get_filename(ip));
+ d_print("error: ip_seek returned %d\n", rc);
+ }
+ }
+ player_unlock();
+}
+
+/*
+ * change output plugin without stopping playback
+ */
+void player_set_op(const char *name)
+{
+ int rc;
+
+ player_lock();
+
+ /* drop needed because close drains the buffer */
+ if (consumer_status == CS_PAUSED)
+ op_drop();
+
+ if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED)
+ op_close();
+
+ if (name) {
+ d_print("setting op to '%s'\n", name);
+ rc = op_select(name);
+ } else {
+ /* first initialized plugin */
+ d_print("selecting first initialized op\n");
+ rc = op_select_any();
+ }
+ if (rc) {
+ __consumer_status_update(CS_STOPPED);
+
+ __producer_stop();
+ if (name)
+ player_op_error(rc, "selecting output plugin '%s'", name);
+ else
+ player_op_error(rc, "selecting any output plugin");
+ player_unlock();
+ return;
+ }
+
+ if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
+ set_buffer_sf();
+ rc = op_open(buffer_sf, buffer_channel_map);
+ if (rc) {
+ __consumer_status_update(CS_STOPPED);
+ __producer_stop();
+ player_op_error(rc, "opening audio device");
+ player_unlock();
+ return;
+ }
+ if (consumer_status == CS_PAUSED)
+ op_pause();
+ }
+
+ player_unlock();
+}
+
+void player_set_buffer_chunks(unsigned int nr_chunks)
+{
+ player_lock();
+ __producer_stop();
+ __consumer_stop();
+
+ buffer_nr_chunks = nr_chunks;
+ buffer_init();
+
+ __player_status_changed();
+ player_unlock();
+}
+
+int player_get_buffer_chunks(void)
+{
+ return buffer_nr_chunks;
+}
+
+void player_set_soft_volume(int l, int r)
+{
+ consumer_lock();
+ soft_vol_l = l;
+ soft_vol_r = r;
+ consumer_unlock();
+}
+
+void player_set_soft_vol(int soft)
+{
+ consumer_lock();
+ /* don't mess with scale_pos if soft_vol or replaygain is already enabled */
+ if (!soft_vol && !replaygain)
+ scale_pos = consumer_pos;
+ soft_vol = soft;
+ consumer_unlock();
+}
+
+void player_set_rg(enum replaygain rg)
+{
+ player_lock();
+ /* don't mess with scale_pos if soft_vol or replaygain is already enabled */
+ if (!soft_vol && !replaygain)
+ scale_pos = consumer_pos;
+ replaygain = rg;
+
+ player_info_lock();
+ update_rg_scale();
+ player_info_unlock();
+
+ player_unlock();
+}
+
+void player_set_rg_limit(int limit)
+{
+ player_lock();
+ replaygain_limit = limit;
+
+ player_info_lock();
+ update_rg_scale();
+ player_info_unlock();
+
+ player_unlock();
+}
+
+void player_set_rg_preamp(double db)
+{
+ player_lock();
+ replaygain_preamp = db;
+
+ player_info_lock();
+ update_rg_scale();
+ player_info_unlock();
+
+ player_unlock();
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _PLAYER_H
+#define _PLAYER_H
+
+#include "locking.h"
+#include "track_info.h"
+
+#include <pthread.h>
+
+enum {
+ /* no error */
+ PLAYER_ERROR_SUCCESS,
+ /* system error (error code in errno) */
+ PLAYER_ERROR_ERRNO,
+ /* function not supported */
+ PLAYER_ERROR_NOT_SUPPORTED
+};
+
+extern const char * const player_status_names[];
+enum player_status {
+ PLAYER_STATUS_STOPPED,
+ PLAYER_STATUS_PLAYING,
+ PLAYER_STATUS_PAUSED,
+ NR_PLAYER_STATUS
+};
+
+enum replaygain {
+ RG_DISABLED,
+ RG_TRACK,
+ RG_ALBUM,
+ RG_TRACK_PREFERRED,
+ RG_ALBUM_PREFERRED
+};
+
+struct player_callbacks {
+ int (*get_next)(struct track_info **ti);
+};
+
+struct player_info {
+ pthread_mutex_t mutex;
+
+ /* current track */
+ struct track_info *ti;
+
+ /* stream metadata */
+ char metadata[255 * 16 + 1];
+
+ /* status */
+ enum player_status status;
+ int pos;
+ int current_bitrate;
+
+ int buffer_fill;
+ int buffer_size;
+
+ /* display this if not NULL */
+ char *error_msg;
+
+ unsigned int file_changed : 1;
+ unsigned int metadata_changed : 1;
+ unsigned int status_changed : 1;
+ unsigned int position_changed : 1;
+ unsigned int buffer_fill_changed : 1;
+};
+
+extern struct player_info player_info;
+extern int player_cont;
+extern int player_repeat_current;
+extern enum replaygain replaygain;
+extern int replaygain_limit;
+extern double replaygain_preamp;
+extern int soft_vol;
+extern int soft_vol_l;
+extern int soft_vol_r;
+
+void player_init(const struct player_callbacks *callbacks);
+void player_exit(void);
+
+/* set current file */
+void player_set_file(struct track_info *ti);
+
+/* set current file and start playing */
+void player_play_file(struct track_info *ti);
+
+/* update track info */
+void player_file_changed(struct track_info *ti);
+
+void player_play(void);
+void player_stop(void);
+void player_pause(void);
+void player_seek(double offset, int relative, int start_playing);
+void player_set_op(const char *name);
+void player_set_buffer_chunks(unsigned int nr_chunks);
+int player_get_buffer_chunks(void);
+
+void player_set_soft_volume(int l, int r);
+void player_set_soft_vol(int soft);
+void player_set_rg(enum replaygain rg);
+void player_set_rg_limit(int limit);
+void player_set_rg_preamp(double db);
+
+#define player_info_lock() cmus_mutex_lock(&player_info.mutex)
+#define player_info_unlock() cmus_mutex_unlock(&player_info.mutex)
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "prog.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+
+char *program_name = NULL;
+
+void warn(const char *format, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s: ", program_name);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+}
+
+void warn_errno(const char *format, ...)
+{
+ int e = errno;
+ va_list ap;
+
+ fprintf(stderr, "%s: ", program_name);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ fprintf(stderr, ": %s\n", strerror(e));
+}
+
+void __NORETURN die(const char *format, ...)
+{
+ va_list ap;
+
+ fprintf(stderr, "%s: ", program_name);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ exit(1);
+}
+
+void __NORETURN die_errno(const char *format, ...)
+{
+ int e = errno;
+ va_list ap;
+
+ fprintf(stderr, "%s: ", program_name);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
+ fprintf(stderr, ": %s\n", strerror(e));
+ exit(1);
+}
+
+static int short_option(int ch, const struct option *options)
+{
+ int i;
+
+ for (i = 0; ; i++) {
+ if (!options[i].short_opt) {
+ if (!options[i].long_opt)
+ die("unrecognized option `-%c'\n", ch);
+ continue;
+ }
+ if (options[i].short_opt != ch)
+ continue;
+ return i;
+ }
+}
+
+static int long_option(const char *opt, const struct option *options)
+{
+ int len, i, idx, num;
+
+ len = strlen(opt);
+ idx = -1;
+ num = 0;
+ for (i = 0; options[i].short_opt || options[i].long_opt; i++) {
+ if (options[i].long_opt && strncmp(opt, options[i].long_opt, len) == 0) {
+ idx = i;
+ num++;
+ if (options[i].long_opt[len] == 0) {
+ /* exact */
+ num = 1;
+ break;
+ }
+ }
+ }
+ if (num > 1)
+ die("option `--%s' is ambiguous\n", opt);
+ if (num == 0)
+ die("unrecognized option `--%s'\n", opt);
+ return idx;
+}
+
+int get_option(char **argvp[], const struct option *options, char **arg)
+{
+ char **argv = *argvp;
+ const char *opt = *argv;
+ int i;
+
+ *arg = NULL;
+ if (opt == NULL || opt[0] != '-' || opt[1] == 0)
+ return -1;
+
+ if (opt[1] == '-') {
+ if (opt[2] == 0) {
+ /* '--' => no more options */
+ *argvp = argv + 1;
+ return -1;
+ }
+ i = long_option(opt + 2, options);
+ } else if (opt[2]) {
+ return -1;
+ } else {
+ i = short_option(opt[1], options);
+ }
+ argv++;
+ if (options[i].has_arg) {
+ if (*argv == NULL)
+ die("option `%s' requires an argument\n", opt);
+ *arg = *argv++;
+ }
+ *argvp = argv;
+ return i;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PROG_H
+#define PROG_H
+
+#include "compiler.h"
+
+/* set in beginning of main */
+extern char *program_name;
+
+struct option {
+ /* short option or 0 */
+ int short_opt;
+
+ /* long option or NULL */
+ const char *long_opt;
+
+ /* does option have an argument */
+ int has_arg;
+};
+
+/*
+ * arg: returned argument if .has_arg is 1
+ *
+ * returns: index to options array or -1 of no more options
+ */
+int get_option(char **argv[], const struct option *options, char **arg);
+
+void warn(const char *format, ...) __FORMAT(1, 2);
+void warn_errno(const char *format, ...) __FORMAT(1, 2);
+void die(const char *format, ...) __FORMAT(1, 2) __NORETURN;
+void die_errno(const char *format, ...) __FORMAT(1, 2) __NORETURN;
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) 2008-2011 Various Authors
+ * Copyright (C) 2009 Gregory Petrosyan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include <pulse/pulseaudio.h>
+
+#include "op.h"
+#include "mixer.h"
+#include "debug.h"
+#include "utils.h"
+#include "xmalloc.h"
+
+static pa_threaded_mainloop *pa_ml;
+static pa_context *pa_ctx;
+static pa_stream *pa_s;
+static pa_channel_map pa_cmap;
+static pa_cvolume pa_vol;
+static pa_sample_spec pa_ss;
+
+/* configuration */
+static int pa_restore_volume = 1;
+
+#define ret_pa_error(err) \
+ do { \
+ d_print("PulseAudio error: %s\n", pa_strerror(err)); \
+ return -OP_ERROR_INTERNAL; \
+ } while (0)
+
+#define ret_pa_last_error() ret_pa_error(pa_context_errno(pa_ctx))
+
+static pa_proplist *__create_app_proplist(void)
+{
+ pa_proplist *pl;
+ int rc;
+
+ pl = pa_proplist_new();
+ BUG_ON(!pl);
+
+ rc = pa_proplist_sets(pl, PA_PROP_APPLICATION_NAME, "C* Music Player");
+ BUG_ON(rc);
+
+ rc = pa_proplist_sets(pl, PA_PROP_APPLICATION_VERSION, VERSION);
+ BUG_ON(rc);
+
+ return pl;
+}
+
+static pa_proplist *__create_stream_proplist(void)
+{
+ pa_proplist *pl;
+ int rc;
+
+ pl = pa_proplist_new();
+ BUG_ON(!pl);
+
+ rc = pa_proplist_sets(pl, PA_PROP_MEDIA_ROLE, "music");
+ BUG_ON(rc);
+
+ rc = pa_proplist_sets(pl, PA_PROP_MEDIA_ICON_NAME, "audio-x-generic");
+ BUG_ON(rc);
+
+ return pl;
+}
+
+static const char *__pa_context_state_str(pa_context_state_t s)
+{
+ switch (s) {
+ case PA_CONTEXT_AUTHORIZING:
+ return "PA_CONTEXT_AUTHORIZING";
+ case PA_CONTEXT_CONNECTING:
+ return "PA_CONTEXT_CONNECTING";
+ case PA_CONTEXT_FAILED:
+ return "PA_CONTEXT_FAILED";
+ case PA_CONTEXT_READY:
+ return "PA_CONTEXT_READY";
+ case PA_CONTEXT_SETTING_NAME:
+ return "PA_CONTEXT_SETTING_NAME";
+ case PA_CONTEXT_TERMINATED:
+ return "PA_CONTEXT_TERMINATED";
+ case PA_CONTEXT_UNCONNECTED:
+ return "PA_CONTEXT_UNCONNECTED";
+ }
+
+ return "unknown";
+}
+
+static void __pa_context_running_cb(pa_context *c, void *data)
+{
+ const pa_context_state_t cs = pa_context_get_state(c);
+
+ d_print("pulse: context state has changed to %s\n", __pa_context_state_str(cs));
+
+ switch (cs) {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ pa_threaded_mainloop_signal(pa_ml, 0);
+ default:
+ return;
+ }
+}
+
+static const char *__pa_stream_state_str(pa_stream_state_t s)
+{
+ switch (s) {
+ case PA_STREAM_CREATING:
+ return "PA_STREAM_CREATING";
+ case PA_STREAM_FAILED:
+ return "PA_STREAM_FAILED";
+ case PA_STREAM_READY:
+ return "PA_STREAM_READY";
+ case PA_STREAM_TERMINATED:
+ return "PA_STREAM_TERMINATED";
+ case PA_STREAM_UNCONNECTED:
+ return "PA_STREAM_UNCONNECTED";
+ }
+
+ return "unknown";
+}
+
+static void __pa_stream_running_cb(pa_stream *s, void *data)
+{
+ const pa_stream_state_t ss = pa_stream_get_state(s);
+
+ d_print("pulse: stream state has changed to %s\n", __pa_stream_state_str(ss));
+
+ switch (ss) {
+ case PA_STREAM_READY:
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ pa_threaded_mainloop_signal(pa_ml, 0);
+ default:
+ return;
+ }
+}
+
+static void __pa_sink_input_info_cb(pa_context *c,
+ const pa_sink_input_info *i,
+ int eol,
+ void *data)
+{
+ if (i) {
+ memcpy(&pa_vol, &i->volume, sizeof(pa_vol));
+ pa_threaded_mainloop_signal(pa_ml, 0);
+ }
+}
+
+static void __pa_stream_success_cb(pa_stream *s, int success, void *data)
+{
+ pa_threaded_mainloop_signal(pa_ml, 0);
+}
+
+static pa_sample_format_t __pa_sample_format(sample_format_t sf)
+{
+ const int signed_ = sf_get_signed(sf);
+ const int big_endian = sf_get_bigendian(sf);
+ const int sample_size = sf_get_sample_size(sf) * 8;
+
+ if (!signed_ && sample_size == 8)
+ return PA_SAMPLE_U8;
+
+ if (signed_) {
+ switch (sample_size) {
+ case 16:
+ return big_endian ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE;
+ case 24:
+ return big_endian ? PA_SAMPLE_S24BE : PA_SAMPLE_S24LE;
+ case 32:
+ return big_endian ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE;
+ }
+ }
+
+ return PA_SAMPLE_INVALID;
+}
+
+static int __pa_wait_unlock(pa_operation *o)
+{
+ pa_operation_state_t state;
+
+ if (!o) {
+ pa_threaded_mainloop_unlock(pa_ml);
+ ret_pa_last_error();
+ }
+
+ while ((state = pa_operation_get_state(o)) == PA_OPERATION_RUNNING)
+ pa_threaded_mainloop_wait(pa_ml);
+
+ pa_operation_unref(o);
+ pa_threaded_mainloop_unlock(pa_ml);
+
+ if (state == PA_OPERATION_DONE)
+ return OP_ERROR_SUCCESS;
+ else
+ ret_pa_last_error();
+}
+
+static int __pa_nowait_unlock(pa_operation *o)
+{
+ if (!o) {
+ pa_threaded_mainloop_unlock(pa_ml);
+ ret_pa_last_error();
+ }
+
+ pa_operation_unref(o);
+ pa_threaded_mainloop_unlock(pa_ml);
+
+ return OP_ERROR_SUCCESS;
+}
+
+static int __pa_stream_cork(int pause_)
+{
+ pa_threaded_mainloop_lock(pa_ml);
+
+ return __pa_wait_unlock(pa_stream_cork(pa_s, pause_, __pa_stream_success_cb, NULL));
+}
+
+static int __pa_stream_drain(void)
+{
+ pa_threaded_mainloop_lock(pa_ml);
+
+ return __pa_wait_unlock(pa_stream_drain(pa_s, __pa_stream_success_cb, NULL));
+}
+
+static int __pa_create_context(void)
+{
+ pa_mainloop_api *api;
+ pa_proplist *pl;
+ int rc;
+
+ pl = __create_app_proplist();
+
+ api = pa_threaded_mainloop_get_api(pa_ml);
+ BUG_ON(!api);
+
+ pa_threaded_mainloop_lock(pa_ml);
+
+ pa_ctx = pa_context_new_with_proplist(api, "C* Music Player", pl);
+ BUG_ON(!pa_ctx);
+ pa_proplist_free(pl);
+
+ pa_context_set_state_callback(pa_ctx, __pa_context_running_cb, NULL);
+
+ rc = pa_context_connect(pa_ctx, NULL, PA_CONTEXT_NOFAIL, NULL);
+ if (rc)
+ goto out_fail;
+
+ for (;;) {
+ pa_context_state_t state;
+ state = pa_context_get_state(pa_ctx);
+ if (state == PA_CONTEXT_READY)
+ break;
+ if (!PA_CONTEXT_IS_GOOD(state))
+ goto out_fail_connected;
+ pa_threaded_mainloop_wait(pa_ml);
+ }
+
+ pa_threaded_mainloop_unlock(pa_ml);
+
+ return OP_ERROR_SUCCESS;
+
+out_fail_connected:
+ pa_context_disconnect(pa_ctx);
+
+out_fail:
+ pa_context_unref(pa_ctx);
+ pa_ctx = NULL;
+
+ pa_threaded_mainloop_unlock(pa_ml);
+
+ ret_pa_last_error();
+}
+
+static int op_pulse_init(void)
+{
+ int rc;
+
+ pa_ml = pa_threaded_mainloop_new();
+ BUG_ON(!pa_ml);
+
+ rc = pa_threaded_mainloop_start(pa_ml);
+ if (rc) {
+ pa_threaded_mainloop_free(pa_ml);
+ ret_pa_error(rc);
+ }
+
+ return OP_ERROR_SUCCESS;
+}
+
+static int op_pulse_exit(void)
+{
+ if (pa_ml) {
+ pa_threaded_mainloop_stop(pa_ml);
+ pa_threaded_mainloop_free(pa_ml);
+ pa_ml = NULL;
+ }
+
+ return OP_ERROR_SUCCESS;
+}
+
+#define RET_IF(x) case CHANNEL_POSITION_ ## x: return PA_CHANNEL_POSITION_ ## x
+
+static pa_channel_position_t pulse_channel_position(channel_position_t p)
+{
+ switch (p) {
+ RET_IF(MONO);
+ RET_IF(FRONT_LEFT); RET_IF(FRONT_RIGHT); RET_IF(FRONT_CENTER);
+ RET_IF(REAR_CENTER); RET_IF(REAR_LEFT); RET_IF(REAR_RIGHT);
+ RET_IF(LFE);
+ RET_IF(FRONT_LEFT_OF_CENTER); RET_IF(FRONT_RIGHT_OF_CENTER);
+ RET_IF(SIDE_LEFT); RET_IF(SIDE_RIGHT);
+ RET_IF(TOP_CENTER);
+ RET_IF(TOP_FRONT_LEFT); RET_IF(TOP_FRONT_RIGHT); RET_IF(TOP_FRONT_CENTER);
+ RET_IF(TOP_REAR_LEFT); RET_IF(TOP_REAR_RIGHT); RET_IF(TOP_REAR_CENTER);
+ default:
+ return PA_CHANNEL_POSITION_INVALID;
+ }
+}
+
+static int op_pulse_open(sample_format_t sf, const channel_position_t *channel_map)
+{
+ pa_proplist *pl;
+ int rc, i;
+
+ const pa_sample_spec ss = {
+ .format = __pa_sample_format(sf),
+ .rate = sf_get_rate(sf),
+ .channels = sf_get_channels(sf)
+ };
+
+ if (!pa_sample_spec_valid(&ss))
+ return -OP_ERROR_SAMPLE_FORMAT;
+
+ pa_ss = ss;
+ if (channel_map && channel_map_valid(channel_map)) {
+ pa_channel_map_init(&pa_cmap);
+ pa_cmap.channels = ss.channels;
+ for (i = 0; i < pa_cmap.channels; i++)
+ pa_cmap.map[i] = pulse_channel_position(channel_map[i]);
+ } else
+ pa_channel_map_init_auto(&pa_cmap, ss.channels, PA_CHANNEL_MAP_ALSA);
+
+ rc = __pa_create_context();
+ if (rc)
+ return rc;
+
+ pl = __create_stream_proplist();
+
+ pa_threaded_mainloop_lock(pa_ml);
+
+ pa_s = pa_stream_new_with_proplist(pa_ctx, "playback", &ss, &pa_cmap, pl);
+ pa_proplist_free(pl);
+ if (!pa_s) {
+ pa_threaded_mainloop_unlock(pa_ml);
+ ret_pa_last_error();
+ }
+
+ pa_stream_set_state_callback(pa_s, __pa_stream_running_cb, NULL);
+
+ rc = pa_stream_connect_playback(pa_s,
+ NULL,
+ NULL,
+ PA_STREAM_NOFLAGS,
+ pa_restore_volume ? NULL : &pa_vol,
+ NULL);
+ if (rc)
+ goto out_fail;
+
+ pa_threaded_mainloop_wait(pa_ml);
+
+ if (pa_stream_get_state(pa_s) != PA_STREAM_READY)
+ goto out_fail;
+
+ pa_threaded_mainloop_unlock(pa_ml);
+
+ return OP_ERROR_SUCCESS;
+
+out_fail:
+ pa_stream_unref(pa_s);
+
+ pa_threaded_mainloop_unlock(pa_ml);
+
+ ret_pa_last_error();
+}
+
+static int op_pulse_close(void)
+{
+ /*
+ * If this __pa_stream_drain() will be moved below following
+ * pa_threaded_mainloop_lock(), PulseAudio 0.9.19 will hang.
+ */
+ if (pa_s)
+ __pa_stream_drain();
+
+ pa_threaded_mainloop_lock(pa_ml);
+
+ if (pa_s) {
+ pa_stream_disconnect(pa_s);
+ pa_stream_unref(pa_s);
+ pa_s = NULL;
+ }
+
+ if (pa_ctx) {
+ pa_context_disconnect(pa_ctx);
+ pa_context_unref(pa_ctx);
+ pa_ctx = NULL;
+ }
+
+ pa_threaded_mainloop_unlock(pa_ml);
+
+ return OP_ERROR_SUCCESS;
+}
+
+static int op_pulse_drop(void)
+{
+ pa_threaded_mainloop_lock(pa_ml);
+
+ return __pa_wait_unlock(pa_stream_flush(pa_s, __pa_stream_success_cb, NULL));
+}
+
+static int op_pulse_write(const char *buf, int count)
+{
+ int rc;
+
+ pa_threaded_mainloop_lock(pa_ml);
+ rc = pa_stream_write(pa_s, buf, count, NULL, 0, PA_SEEK_RELATIVE);
+ pa_threaded_mainloop_unlock(pa_ml);
+
+ if (rc)
+ ret_pa_error(rc);
+ else
+ return count;
+}
+
+static int op_pulse_buffer_space(void)
+{
+ int s;
+
+ pa_threaded_mainloop_lock(pa_ml);
+ s = (int)pa_stream_writable_size(pa_s);
+ pa_threaded_mainloop_unlock(pa_ml);
+
+ return s;
+}
+
+static int op_pulse_pause(void)
+{
+ return __pa_stream_cork(1);
+}
+
+static int op_pulse_unpause(void)
+{
+ return __pa_stream_cork(0);
+}
+
+static int op_pulse_set_option(int key, const char *val)
+{
+ return -OP_ERROR_NOT_OPTION;
+}
+
+static int op_pulse_get_option(int key, char **val)
+{
+ return -OP_ERROR_NOT_OPTION;
+}
+
+static int op_pulse_mixer_init(void)
+{
+ if (!pa_channel_map_init_stereo(&pa_cmap))
+ ret_pa_last_error();
+
+ pa_cvolume_reset(&pa_vol, 2);
+
+ return OP_ERROR_SUCCESS;
+}
+
+static int op_pulse_mixer_exit(void)
+{
+ return OP_ERROR_SUCCESS;
+}
+
+static int op_pulse_mixer_open(int *volume_max)
+{
+ *volume_max = PA_VOLUME_NORM;
+
+ return OP_ERROR_SUCCESS;
+}
+
+static int op_pulse_mixer_close(void)
+{
+ return OP_ERROR_SUCCESS;
+}
+
+static int op_pulse_mixer_get_fds(int *fds)
+{
+ return -OP_ERROR_NOT_SUPPORTED;
+}
+
+static int op_pulse_mixer_set_volume(int l, int r)
+{
+ if (!pa_s && pa_restore_volume)
+ return -OP_ERROR_NOT_OPEN;
+
+ pa_cvolume_set(&pa_vol, pa_ss.channels, (pa_volume_t) ((l + r) / 2));
+ pa_cvolume_set_position(&pa_vol,
+ &pa_cmap,
+ PA_CHANNEL_POSITION_FRONT_LEFT,
+ (pa_volume_t)l);
+
+ pa_cvolume_set_position(&pa_vol,
+ &pa_cmap,
+ PA_CHANNEL_POSITION_FRONT_RIGHT,
+ (pa_volume_t)r);
+
+ if (!pa_s) {
+ return OP_ERROR_SUCCESS;
+ } else {
+ pa_threaded_mainloop_lock(pa_ml);
+
+ return __pa_nowait_unlock(pa_context_set_sink_input_volume(pa_ctx,
+ pa_stream_get_index(pa_s),
+ &pa_vol,
+ NULL,
+ NULL));
+ }
+}
+
+static int op_pulse_mixer_get_volume(int *l, int *r)
+{
+ int rc = OP_ERROR_SUCCESS;
+
+ if (!pa_s && pa_restore_volume)
+ return -OP_ERROR_NOT_OPEN;
+
+ if (pa_s) {
+ pa_threaded_mainloop_lock(pa_ml);
+
+ rc = __pa_wait_unlock(pa_context_get_sink_input_info(pa_ctx,
+ pa_stream_get_index(pa_s),
+ __pa_sink_input_info_cb,
+ NULL));
+ }
+
+ *l = pa_cvolume_get_position(&pa_vol, &pa_cmap, PA_CHANNEL_POSITION_FRONT_LEFT);
+ *r = pa_cvolume_get_position(&pa_vol, &pa_cmap, PA_CHANNEL_POSITION_FRONT_RIGHT);
+
+ return rc;
+}
+
+static int op_pulse_mixer_set_option(int key, const char *val)
+{
+ switch (key) {
+ case 0:
+ pa_restore_volume = is_freeform_true(val);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+static int op_pulse_mixer_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ *val = xstrdup(pa_restore_volume ? "1" : "0");
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+const struct output_plugin_ops op_pcm_ops = {
+ .init = op_pulse_init,
+ .exit = op_pulse_exit,
+ .open = op_pulse_open,
+ .close = op_pulse_close,
+ .drop = op_pulse_drop,
+ .write = op_pulse_write,
+ .buffer_space = op_pulse_buffer_space,
+ .pause = op_pulse_pause,
+ .unpause = op_pulse_unpause,
+ .set_option = op_pulse_set_option,
+ .get_option = op_pulse_get_option
+};
+
+const struct mixer_plugin_ops op_mixer_ops = {
+ .init = op_pulse_mixer_init,
+ .exit = op_pulse_mixer_exit,
+ .open = op_pulse_mixer_open,
+ .close = op_pulse_mixer_close,
+ .get_fds = op_pulse_mixer_get_fds,
+ .set_volume = op_pulse_mixer_set_volume,
+ .get_volume = op_pulse_mixer_get_volume,
+ .set_option = op_pulse_mixer_set_option,
+ .get_option = op_pulse_mixer_get_option
+};
+
+const char * const op_pcm_options[] = {
+ NULL
+};
+
+const char * const op_mixer_options[] = {
+ "restore_volume",
+ NULL
+};
+
+const int op_priority = -2;
+
--- /dev/null
+/* Stolen from Linux 2.6.34 */
+
+/*
+ Red Black Trees
+ (C) 1999 Andrea Arcangeli <andrea@suse.de>
+ (C) 2002 David Woodhouse <dwmw2@infradead.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ linux/lib/rbtree.c
+*/
+
+#include "rbtree.h"
+
+static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
+{
+ struct rb_node *right = node->rb_right;
+ struct rb_node *parent = rb_parent(node);
+
+ if ((node->rb_right = right->rb_left))
+ rb_set_parent(right->rb_left, node);
+ right->rb_left = node;
+
+ rb_set_parent(right, parent);
+
+ if (parent)
+ {
+ if (node == parent->rb_left)
+ parent->rb_left = right;
+ else
+ parent->rb_right = right;
+ }
+ else
+ root->rb_node = right;
+ rb_set_parent(node, right);
+}
+
+static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
+{
+ struct rb_node *left = node->rb_left;
+ struct rb_node *parent = rb_parent(node);
+
+ if ((node->rb_left = left->rb_right))
+ rb_set_parent(left->rb_right, node);
+ left->rb_right = node;
+
+ rb_set_parent(left, parent);
+
+ if (parent)
+ {
+ if (node == parent->rb_right)
+ parent->rb_right = left;
+ else
+ parent->rb_left = left;
+ }
+ else
+ root->rb_node = left;
+ rb_set_parent(node, left);
+}
+
+void rb_insert_color(struct rb_node *node, struct rb_root *root)
+{
+ struct rb_node *parent, *gparent;
+
+ while ((parent = rb_parent(node)) && rb_is_red(parent))
+ {
+ gparent = rb_parent(parent);
+
+ if (parent == gparent->rb_left)
+ {
+ {
+ register struct rb_node *uncle = gparent->rb_right;
+ if (uncle && rb_is_red(uncle))
+ {
+ rb_set_black(uncle);
+ rb_set_black(parent);
+ rb_set_red(gparent);
+ node = gparent;
+ continue;
+ }
+ }
+
+ if (parent->rb_right == node)
+ {
+ register struct rb_node *tmp;
+ __rb_rotate_left(parent, root);
+ tmp = parent;
+ parent = node;
+ node = tmp;
+ }
+
+ rb_set_black(parent);
+ rb_set_red(gparent);
+ __rb_rotate_right(gparent, root);
+ } else {
+ {
+ register struct rb_node *uncle = gparent->rb_left;
+ if (uncle && rb_is_red(uncle))
+ {
+ rb_set_black(uncle);
+ rb_set_black(parent);
+ rb_set_red(gparent);
+ node = gparent;
+ continue;
+ }
+ }
+
+ if (parent->rb_left == node)
+ {
+ register struct rb_node *tmp;
+ __rb_rotate_right(parent, root);
+ tmp = parent;
+ parent = node;
+ node = tmp;
+ }
+
+ rb_set_black(parent);
+ rb_set_red(gparent);
+ __rb_rotate_left(gparent, root);
+ }
+ }
+
+ rb_set_black(root->rb_node);
+}
+
+static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
+ struct rb_root *root)
+{
+ struct rb_node *other;
+
+ while ((!node || rb_is_black(node)) && node != root->rb_node)
+ {
+ if (parent->rb_left == node)
+ {
+ other = parent->rb_right;
+ if (rb_is_red(other))
+ {
+ rb_set_black(other);
+ rb_set_red(parent);
+ __rb_rotate_left(parent, root);
+ other = parent->rb_right;
+ }
+ if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+ (!other->rb_right || rb_is_black(other->rb_right)))
+ {
+ rb_set_red(other);
+ node = parent;
+ parent = rb_parent(node);
+ }
+ else
+ {
+ if (!other->rb_right || rb_is_black(other->rb_right))
+ {
+ rb_set_black(other->rb_left);
+ rb_set_red(other);
+ __rb_rotate_right(other, root);
+ other = parent->rb_right;
+ }
+ rb_set_color(other, rb_color(parent));
+ rb_set_black(parent);
+ rb_set_black(other->rb_right);
+ __rb_rotate_left(parent, root);
+ node = root->rb_node;
+ break;
+ }
+ }
+ else
+ {
+ other = parent->rb_left;
+ if (rb_is_red(other))
+ {
+ rb_set_black(other);
+ rb_set_red(parent);
+ __rb_rotate_right(parent, root);
+ other = parent->rb_left;
+ }
+ if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+ (!other->rb_right || rb_is_black(other->rb_right)))
+ {
+ rb_set_red(other);
+ node = parent;
+ parent = rb_parent(node);
+ }
+ else
+ {
+ if (!other->rb_left || rb_is_black(other->rb_left))
+ {
+ rb_set_black(other->rb_right);
+ rb_set_red(other);
+ __rb_rotate_left(other, root);
+ other = parent->rb_left;
+ }
+ rb_set_color(other, rb_color(parent));
+ rb_set_black(parent);
+ rb_set_black(other->rb_left);
+ __rb_rotate_right(parent, root);
+ node = root->rb_node;
+ break;
+ }
+ }
+ }
+ if (node)
+ rb_set_black(node);
+}
+
+void rb_erase(struct rb_node *node, struct rb_root *root)
+{
+ struct rb_node *child, *parent;
+ int color;
+
+ if (!node->rb_left)
+ child = node->rb_right;
+ else if (!node->rb_right)
+ child = node->rb_left;
+ else
+ {
+ struct rb_node *old = node, *left;
+
+ node = node->rb_right;
+ while ((left = node->rb_left) != NULL)
+ node = left;
+
+ if (rb_parent(old)) {
+ if (rb_parent(old)->rb_left == old)
+ rb_parent(old)->rb_left = node;
+ else
+ rb_parent(old)->rb_right = node;
+ } else
+ root->rb_node = node;
+
+ child = node->rb_right;
+ parent = rb_parent(node);
+ color = rb_color(node);
+
+ if (parent == old) {
+ parent = node;
+ } else {
+ if (child)
+ rb_set_parent(child, parent);
+ parent->rb_left = child;
+
+ node->rb_right = old->rb_right;
+ rb_set_parent(old->rb_right, node);
+ }
+
+ node->rb_parent_color = old->rb_parent_color;
+ node->rb_left = old->rb_left;
+ rb_set_parent(old->rb_left, node);
+
+ goto color;
+ }
+
+ parent = rb_parent(node);
+ color = rb_color(node);
+
+ if (child)
+ rb_set_parent(child, parent);
+ if (parent)
+ {
+ if (parent->rb_left == node)
+ parent->rb_left = child;
+ else
+ parent->rb_right = child;
+ }
+ else
+ root->rb_node = child;
+
+ color:
+ if (color == RB_BLACK)
+ __rb_erase_color(child, parent, root);
+}
+
+/*
+ * This function returns the first node (in sort order) of the tree.
+ */
+struct rb_node *rb_first(const struct rb_root *root)
+{
+ struct rb_node *n;
+
+ n = root->rb_node;
+ if (!n)
+ return NULL;
+ while (n->rb_left)
+ n = n->rb_left;
+ return n;
+}
+
+struct rb_node *rb_last(const struct rb_root *root)
+{
+ struct rb_node *n;
+
+ n = root->rb_node;
+ if (!n)
+ return NULL;
+ while (n->rb_right)
+ n = n->rb_right;
+ return n;
+}
+
+struct rb_node *rb_next(const struct rb_node *node)
+{
+ struct rb_node *parent;
+
+ if (rb_parent(node) == node)
+ return NULL;
+
+ /* If we have a right-hand child, go down and then left as far
+ as we can. */
+ if (node->rb_right) {
+ node = node->rb_right;
+ while (node->rb_left)
+ node=node->rb_left;
+ return (struct rb_node *)node;
+ }
+
+ /* No right-hand children. Everything down and left is
+ smaller than us, so any 'next' node must be in the general
+ direction of our parent. Go up the tree; any time the
+ ancestor is a right-hand child of its parent, keep going
+ up. First time it's a left-hand child of its parent, said
+ parent is our 'next' node. */
+ while ((parent = rb_parent(node)) && node == parent->rb_right)
+ node = parent;
+
+ return parent;
+}
+
+struct rb_node *rb_prev(const struct rb_node *node)
+{
+ struct rb_node *parent;
+
+ if (rb_parent(node) == node)
+ return NULL;
+
+ /* If we have a left-hand child, go down and then right as far
+ as we can. */
+ if (node->rb_left) {
+ node = node->rb_left;
+ while (node->rb_right)
+ node=node->rb_right;
+ return (struct rb_node *)node;
+ }
+
+ /* No left-hand children. Go up till we find an ancestor which
+ is a right-hand child of its parent */
+ while ((parent = rb_parent(node)) && node == parent->rb_left)
+ node = parent;
+
+ return parent;
+}
+
+void rb_replace_node(struct rb_node *victim, struct rb_node *new,
+ struct rb_root *root)
+{
+ struct rb_node *parent = rb_parent(victim);
+
+ /* Set the surrounding nodes to point to the replacement */
+ if (parent) {
+ if (victim == parent->rb_left)
+ parent->rb_left = new;
+ else
+ parent->rb_right = new;
+ } else {
+ root->rb_node = new;
+ }
+ if (victim->rb_left)
+ rb_set_parent(victim->rb_left, new);
+ if (victim->rb_right)
+ rb_set_parent(victim->rb_right, new);
+
+ /* Copy the pointers/colour from the victim to the replacement */
+ *new = *victim;
+}
--- /dev/null
+/* Stolen from Linux 2.6.34 */
+
+/*
+ Red Black Trees
+ (C) 1999 Andrea Arcangeli <andrea@suse.de>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+ linux/include/linux/rbtree.h
+
+ To use rbtrees you'll have to implement your own insert and search cores.
+ This will avoid us to use callbacks and to drop drammatically performances.
+ I know it's not the cleaner way, but in C (not in C++) to get
+ performances and genericity...
+
+ Some example of insert and search follows here. The search is a plain
+ normal search over an ordered tree. The insert instead must be implemented
+ int two steps: as first thing the code must insert the element in
+ order as a red leaf in the tree, then the support library function
+ rb_insert_color() must be called. Such function will do the
+ not trivial work to rebalance the rbtree if necessary.
+
+-----------------------------------------------------------------------
+static inline struct page * rb_search_page_cache(struct inode * inode,
+ unsigned long offset)
+{
+ struct rb_node * n = inode->i_rb_page_cache.rb_node;
+ struct page * page;
+
+ while (n)
+ {
+ page = rb_entry(n, struct page, rb_page_cache);
+
+ if (offset < page->offset)
+ n = n->rb_left;
+ else if (offset > page->offset)
+ n = n->rb_right;
+ else
+ return page;
+ }
+ return NULL;
+}
+
+static inline struct page * __rb_insert_page_cache(struct inode * inode,
+ unsigned long offset,
+ struct rb_node * node)
+{
+ struct rb_node ** p = &inode->i_rb_page_cache.rb_node;
+ struct rb_node * parent = NULL;
+ struct page * page;
+
+ while (*p)
+ {
+ parent = *p;
+ page = rb_entry(parent, struct page, rb_page_cache);
+
+ if (offset < page->offset)
+ p = &(*p)->rb_left;
+ else if (offset > page->offset)
+ p = &(*p)->rb_right;
+ else
+ return page;
+ }
+
+ rb_link_node(node, parent, p);
+
+ return NULL;
+}
+
+static inline struct page * rb_insert_page_cache(struct inode * inode,
+ unsigned long offset,
+ struct rb_node * node)
+{
+ struct page * ret;
+ if ((ret = __rb_insert_page_cache(inode, offset, node)))
+ goto out;
+ rb_insert_color(node, &inode->i_rb_page_cache);
+ out:
+ return ret;
+}
+-----------------------------------------------------------------------
+*/
+
+#ifndef _LINUX_RBTREE_H
+#define _LINUX_RBTREE_H
+
+#include "compiler.h" /* container_of */
+#include <stddef.h>
+
+struct rb_node
+{
+ unsigned long rb_parent_color;
+#define RB_RED 0
+#define RB_BLACK 1
+ struct rb_node *rb_right;
+ struct rb_node *rb_left;
+} __attribute__((aligned(sizeof(long))));
+ /* The alignment might seem pointless, but allegedly CRIS needs it */
+
+struct rb_root
+{
+ struct rb_node *rb_node;
+};
+
+
+#define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3))
+#define rb_color(r) ((r)->rb_parent_color & 1)
+#define rb_is_red(r) (!rb_color(r))
+#define rb_is_black(r) rb_color(r)
+#define rb_set_red(r) do { (r)->rb_parent_color &= ~1; } while (0)
+#define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0)
+
+static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p)
+{
+ rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p;
+}
+static inline void rb_set_color(struct rb_node *rb, int color)
+{
+ rb->rb_parent_color = (rb->rb_parent_color & ~1) | color;
+}
+
+#define RB_ROOT (struct rb_root) { NULL, }
+#define rb_entry(ptr, type, member) container_of(ptr, type, member)
+
+#define RB_EMPTY_ROOT(root) ((root)->rb_node == NULL)
+#define RB_EMPTY_NODE(node) (rb_parent(node) == node)
+#define RB_CLEAR_NODE(node) (rb_set_parent(node, node))
+
+void rb_insert_color(struct rb_node *, struct rb_root *);
+void rb_erase(struct rb_node *, struct rb_root *);
+
+/* Find logical next and previous nodes in a tree */
+struct rb_node *rb_next(const struct rb_node *);
+struct rb_node *rb_prev(const struct rb_node *);
+struct rb_node *rb_first(const struct rb_root *);
+struct rb_node *rb_last(const struct rb_root *);
+
+/* Fast replacement of a single node without remove/rebalance/add/rebalance */
+void rb_replace_node(struct rb_node *victim, struct rb_node *new,
+ struct rb_root *root);
+
+static inline void rb_link_node(struct rb_node * node, struct rb_node * parent,
+ struct rb_node ** rb_link)
+{
+ node->rb_parent_color = (unsigned long )parent;
+ node->rb_left = node->rb_right = NULL;
+
+ *rb_link = node;
+}
+
+
+/* Cmus extensions */
+
+static inline void rb_root_init(struct rb_root *root)
+{
+ root->rb_node = NULL;
+}
+
+static inline int rb_root_empty(struct rb_root *root)
+{
+ return RB_EMPTY_ROOT(root);
+}
+
+/**
+ * rb_for_each - iterate over a rbtree
+ * @pos: the &struct rb_node to use as a loop counter.
+ * @root: the root for your rbtree.
+ */
+#define rb_for_each(pos, root) \
+ for (pos = rb_first(root); pos; pos = rb_next(pos))
+
+/**
+ * rb_for_each_prev - iterate over a rbtree backwards
+ * @pos: the &struct rb_node to use as a loop counter.
+ * @root: the root for your rbtree.
+ */
+#define rb_for_each_prev(pos, root) \
+ for (pos = rb_last(root); pos; pos = rb_prev(pos))
+
+/**
+ * rb_for_each_safe - iterate over a rbtree safe against removal of rbtree node
+ * @pos: the &struct rb_node to use as a loop counter.
+ * @n: another &struct rb_node to use as temporary storage
+ * @root: the root for your rbtree.
+ */
+#define rb_for_each_safe(pos, n, root) \
+ for (pos = rb_first(root), n = pos ? rb_next(pos) : NULL; pos; \
+ pos = n, n = pos ? rb_next(pos) : NULL)
+
+/**
+ * rb_for_each_entry - iterate over a rbtree of given type
+ * @pos: the &struct rb_node to use as a loop counter.
+ * @t: the type * to use as a loop counter.
+ * @root: the root for your rbtree.
+ * @member: the name of the rb_node-struct within the struct.
+ */
+#define rb_for_each_entry(t, pos, root, member) \
+ for (pos = rb_first(root), t = pos ? rb_entry(pos, __typeof__(*t), member) : NULL; \
+ pos; pos = rb_next(pos), t = pos ? rb_entry(pos, __typeof__(*t), member) : NULL)
+
+/**
+ * rb_for_each_entry_reverse - iterate backwards over a rbtree of given type
+ * @pos: the &struct rb_node to use as a loop counter.
+ * @t: the type * to use as a loop counter.
+ * @root: the root for your rbtree.
+ * @member: the name of the rb_node-struct within the struct.
+ */
+#define rb_for_each_entry_reverse(t, pos, root, member) \
+ for (pos = rb_last(root), t = pos ? rb_entry(pos, __typeof__(*t), member) : NULL; \
+ pos; pos = rb_prev(pos), t = pos ? rb_entry(pos, __typeof__(*t), member) : NULL)
+
+#endif /* _LINUX_RBTREE_H */
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "read_wrapper.h"
+#include "ip.h"
+#include "file.h"
+
+#include <unistd.h>
+
+ssize_t read_wrapper(struct input_plugin_data *ip_data, void *buffer, size_t count)
+{
+ int rc;
+
+ if (ip_data->metaint == 0) {
+ /* no metadata in the stream */
+ return read(ip_data->fd, buffer, count);
+ }
+
+ if (ip_data->counter == ip_data->metaint) {
+ /* read metadata */
+ unsigned char byte;
+ int len;
+
+ rc = read(ip_data->fd, &byte, 1);
+ if (rc == -1)
+ return -1;
+ if (rc == 0)
+ return 0;
+ if (byte != 0) {
+ len = ((int)byte) * 16;
+ ip_data->metadata[0] = 0;
+ rc = read_all(ip_data->fd, ip_data->metadata, len);
+ if (rc == -1)
+ return -1;
+ if (rc < len) {
+ ip_data->metadata[0] = 0;
+ return 0;
+ }
+ ip_data->metadata[len] = 0;
+ ip_data->metadata_changed = 1;
+ }
+ ip_data->counter = 0;
+ }
+ if (count + ip_data->counter > ip_data->metaint)
+ count = ip_data->metaint - ip_data->counter;
+ rc = read(ip_data->fd, buffer, count);
+ if (rc > 0)
+ ip_data->counter += rc;
+ return rc;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _READ_WRAPPER_H
+#define _READ_WRAPPER_H
+
+#include "ip.h"
+
+#include <stddef.h> /* size_t */
+#include <sys/types.h> /* ssize_t */
+
+ssize_t read_wrapper(struct input_plugin_data *ip_data, void *buffer, size_t count);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2010-2011 Philipp 'ph3-der-loewe' Schafft
+ * Copyright 2006 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <roaraudio.h>
+
+#include "op.h"
+#include "mixer.h"
+#include "xmalloc.h"
+#include "utils.h"
+#include "misc.h"
+#include "debug.h"
+
+// we do not use native 2^16-1 here as they use signed ints with 16 bit
+// so we use 2^(16-1)-1 here.
+#define MIXER_BASE_VOLUME 32767
+
+static struct roar_connection con;
+static roar_vs_t *vss = NULL;
+static int err;
+static sample_format_t format;
+
+/* configuration */
+static char *host = NULL;
+static char *role = NULL;
+
+static inline void _err_to_errno(void)
+{
+ roar_err_set(err);
+ roar_err_to_errno();
+}
+
+static int op_roar_dummy(void)
+{
+ return 0;
+}
+
+#if DEBUG > 1
+static ssize_t op_roar_debug_write(struct roar_vio_calls *vio, void *buf_, size_t count)
+{
+ char *buf = (char *) buf_;
+ int len = count;
+ if (len > 0 && buf[len-1] == '\n')
+ len--;
+ if (len > 0)
+ d_print("%*s\n", len, buf);
+ return count;
+}
+
+static struct roar_vio_calls op_roar_debug_cbs = {
+ .write = op_roar_debug_write
+};
+#endif
+
+static int op_roar_init(void)
+{
+#if DEBUG > 1
+ roar_debug_set_stderr_mode(ROAR_DEBUG_MODE_VIO);
+ roar_debug_set_stderr_vio(&op_roar_debug_cbs);
+#else
+ roar_debug_set_stderr_mode(ROAR_DEBUG_MODE_SYSLOG);
+#endif
+ return 0;
+}
+
+static int op_roar_exit(void)
+{
+ free(host);
+ free(role);
+
+ return 0;
+}
+
+static int _set_role(void)
+{
+ int roleid = ROAR_ROLE_UNKNOWN;
+
+ if (role == NULL)
+ return 0;
+
+ roleid = roar_str2role(role);
+
+ if (roleid == ROAR_ROLE_UNKNOWN) {
+ // TODO: warn if role is invalid.
+ return 0;
+ }
+
+ if (roar_vs_role(vss, roleid, &err) == -1) {
+ return -OP_ERROR_ERRNO;
+ }
+
+ return 0;
+}
+
+static int op_roar_open(sample_format_t sf, const channel_position_t *channel_map)
+{
+ struct roar_audio_info info;
+ int ret;
+
+ memset(&info, 0, sizeof(info));
+
+ ROAR_DBG("op_roar_open(*) = ?");
+
+ format = sf;
+
+ info.rate = sf_get_rate(sf);
+ info.channels = sf_get_channels(sf);
+ info.bits = sf_get_bits(sf);
+
+ if (sf_get_bigendian(sf)) {
+ if (sf_get_signed(sf)) {
+ info.codec = ROAR_CODEC_PCM_S_BE;
+ } else {
+ info.codec = ROAR_CODEC_PCM_U_BE;
+ }
+ } else {
+ if (sf_get_signed(sf)) {
+ info.codec = ROAR_CODEC_PCM_S_LE;
+ } else {
+ info.codec = ROAR_CODEC_PCM_U_LE;
+ }
+ }
+
+ ROAR_DBG("op_roar_open(*) = ?");
+
+ if (roar_libroar_set_server(host) == -1) {
+ ROAR_DBG("op_roar_open(*) = ?");
+
+ roar_err_to_errno();
+ return -OP_ERROR_ERRNO;
+ }
+
+ if ( roar_simple_connect2(&con, NULL, "C* Music Player (cmus)", ROAR_ENUM_FLAG_NONBLOCK, 0) == -1 ) {
+ ROAR_DBG("op_roar_open(*) = ?");
+
+ roar_err_to_errno();
+ return -OP_ERROR_ERRNO;
+ }
+
+ vss = roar_vs_new_from_con(&con, &err);
+ if (vss == NULL) {
+ ROAR_DBG("op_roar_open(*) = ?");
+
+ roar_disconnect(&con);
+
+ _err_to_errno();
+ return -OP_ERROR_ERRNO;
+ }
+
+ if (roar_vs_stream(vss, &info, ROAR_DIR_PLAY, &err) == -1) {
+ ROAR_DBG("op_roar_open(*) = ?");
+
+ roar_disconnect(&con);
+
+ _err_to_errno();
+ return -OP_ERROR_ERRNO;
+ }
+
+ ROAR_DBG("op_roar_open(*) = ?");
+
+ if (roar_vs_buffer(vss, 2048*8, &err) == -1) {
+ roar_vs_close(vss, ROAR_VS_TRUE, NULL);
+ roar_disconnect(&con);
+ _err_to_errno();
+ return -OP_ERROR_ERRNO;
+ }
+
+ ROAR_DBG("op_roar_open(*) = ?");
+
+ ret = _set_role();
+ if (ret != 0) {
+ roar_vs_close(vss, ROAR_VS_TRUE, NULL);
+ roar_disconnect(&con);
+ _err_to_errno();
+ return ret;
+ }
+
+ ROAR_DBG("op_roar_open(*) = ?");
+
+ roar_vs_blocking(vss, ROAR_VS_FALSE, &err);
+
+ ROAR_DBG("op_roar_open(*) = 0");
+
+ return 0;
+}
+
+static int op_roar_close(void)
+{
+ roar_vs_close(vss, ROAR_VS_FALSE, &err);
+ roar_disconnect(&con);
+ return 0;
+}
+
+static int op_roar_drop(void)
+{
+ roar_vs_reset_buffer(vss, ROAR_VS_TRUE, ROAR_VS_TRUE, &err);
+ return 0;
+}
+
+static int op_roar_write(const char *buffer, int count)
+{
+ int ret;
+ int i;
+ ret = roar_vs_write(vss, buffer, count, &err);
+ for (i = 0; i < 8; i++)
+ roar_vs_iterate(vss, ROAR_VS_NOWAIT, NULL);
+ return ret;
+}
+
+static int op_roar_buffer_space(void)
+{
+ ssize_t ret;
+ int i;
+ int fs = sf_get_frame_size(format);
+
+ for (i = 0; i < 8; i++)
+ roar_vs_iterate(vss, ROAR_VS_NOWAIT, NULL);
+
+ ret = roar_vs_get_avail_write(vss, &err);
+
+ ret -= ret % fs;
+
+ return ret;
+}
+
+static int op_roar_pause(void) {
+ if (roar_vs_pause(vss, ROAR_VS_TRUE, &err) == -1) {
+ _err_to_errno();
+ return -OP_ERROR_ERRNO;
+ }
+ return 0;
+}
+static int op_roar_unpause(void) {
+ if (roar_vs_pause(vss, ROAR_VS_FALSE, &err) == -1) {
+ _err_to_errno();
+ return -OP_ERROR_ERRNO;
+ }
+ return 0;
+}
+
+
+static int op_roar_set_option(int key, const char *val)
+{
+ switch (key) {
+ case 0:
+ free(host);
+ host = xstrdup(val);
+ break;
+ case 1:
+ free(role);
+ role = xstrdup(val);
+ _set_role();
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+static int op_roar_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ if (host != NULL)
+ *val = xstrdup(host);
+ break;
+ case 1:
+ if (role != NULL)
+ *val = xstrdup(role);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+static int op_roar_mixer_open(int *volume_max)
+{
+ *volume_max = MIXER_BASE_VOLUME;
+ return 0;
+}
+
+static int op_roar_mixer_set_volume(int l, int r)
+{
+ float lf, rf;
+
+ if (vss == NULL)
+ return -OP_ERROR_NOT_OPEN;
+
+ lf = (float)l / (float)MIXER_BASE_VOLUME;
+ rf = (float)r / (float)MIXER_BASE_VOLUME;
+
+ if (roar_vs_volume_stereo(vss, lf, rf, &err) == -1) {
+ _err_to_errno();
+ return -OP_ERROR_ERRNO;
+ }
+
+ return 0;
+}
+static int op_roar_mixer_get_volume(int *l, int *r)
+{
+ float lf, rf;
+
+ if (vss == NULL)
+ return -OP_ERROR_NOT_OPEN;
+
+ if (roar_vs_volume_get(vss, &lf, &rf, &err) == -1) {
+ _err_to_errno();
+ return -OP_ERROR_ERRNO;
+ }
+
+ lf *= (float)MIXER_BASE_VOLUME;
+ rf *= (float)MIXER_BASE_VOLUME;
+
+ *l = lf;
+ *r = rf;
+
+ return 0;
+}
+
+static int op_roar_mixer_set_option(int key, const char *val)
+{
+ return -OP_ERROR_NOT_OPTION;
+}
+static int op_roar_mixer_get_option(int key, char **val)
+{
+ return -OP_ERROR_NOT_OPTION;
+}
+
+const struct output_plugin_ops op_pcm_ops = {
+ .init = op_roar_init,
+ .exit = op_roar_exit,
+ .open = op_roar_open,
+ .close = op_roar_close,
+ .drop = op_roar_drop,
+ .write = op_roar_write,
+ .buffer_space = op_roar_buffer_space,
+ .pause = op_roar_pause,
+ .unpause = op_roar_unpause,
+ .set_option = op_roar_set_option,
+ .get_option = op_roar_get_option
+};
+
+const char * const op_pcm_options[] = {
+ "server",
+ "role",
+ NULL
+};
+
+const struct mixer_plugin_ops op_mixer_ops = {
+ .init = op_roar_dummy,
+ .exit = op_roar_dummy,
+ .open = op_roar_mixer_open,
+ .close = op_roar_dummy,
+ .get_fds = NULL,
+ .set_volume = op_roar_mixer_set_volume,
+ .get_volume = op_roar_mixer_get_volume,
+ .set_option = op_roar_mixer_set_option,
+ .get_option = op_roar_mixer_get_option
+};
+
+const char * const op_mixer_options[] = {
+ NULL
+};
+
+const int op_priority = -3;
--- /dev/null
+#!/bin/sh
+#
+# Copyright 2005-2006 Timo Hirvonen
+#
+# This file is licensed under the GPLv2.
+
+# C compiler
+# ----------
+# CC default gcc
+# LD default $CC
+# LDFLAGS common linker flags for CC
+#
+# C++ Compiler
+# ------------
+# CXX default g++
+# CXXLD default $CXX
+# CXXLDFLAGS common linker flags for CXX
+#
+# Common for C and C++
+# --------------------
+# SOFLAGS flags for compiling position independent code (-fPIC)
+# LDSOFLAGS flags for linking shared libraries
+# LDDLFLAGS flags for linking dynamically loadable modules
+
+msg_checking()
+{
+ printf "checking $@... "
+}
+
+msg_result()
+{
+ echo "$@"
+}
+
+msg_error()
+{
+ echo "*** $@"
+}
+
+# @program: program to check
+# @name: name of variable where to store the full program name (optional)
+#
+# returns 0 on success and 1 on failure
+check_program()
+{
+ argc check_program $# 1 2
+ msg_checking "for program $1"
+ __cp_file=`path_find "$1"`
+ if test $? -eq 0
+ then
+ msg_result $__cp_file
+ test $# -eq 2 && set_var $2 "$__cp_file"
+ return 0
+ else
+ msg_result "no"
+ return 1
+ fi
+}
+
+cc_supports()
+{
+ $CC $CFLAGS "$@" -S -o /dev/null -x c /dev/null 2> /dev/null
+ return $?
+}
+
+cxx_supports()
+{
+ $CXX $CXXFLAGS "$@" -S -o /dev/null -x c /dev/null 2> /dev/null
+ return $?
+}
+
+# @flag: option flag(s) to check
+#
+# add @flag to EXTRA_CFLAGS if CC accepts it
+# EXTRA_CFLAGS are added to CFLAGS in the end of configuration
+check_cc_flag()
+{
+ argc check_cc_flag $# 1
+
+ test -z "$CC" && die "check_cc_flag: CC not set"
+ msg_checking "for CFLAGS $*"
+ if cc_supports $*
+ then
+ EXTRA_CFLAGS="$EXTRA_CFLAGS $*"
+ msg_result "yes"
+ return 0
+ else
+ msg_result "no"
+ return 1
+ fi
+}
+
+# @flag: option flag(s) to check
+#
+# add @flag to EXTRA_CXXFLAGS if CXX accepts it
+# EXTRA_CXXFLAGS are added to CXXFLAGS in the end of configuration
+check_cxx_flag()
+{
+ argc check_cxx_flag $# 1
+
+ test -z "$CXX" && die "check_cxx_flag: CXX not set"
+ msg_checking "for CXXFLAGS $*"
+ if cxx_supports $*
+ then
+ EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS $*"
+ msg_result "yes"
+ return 0
+ else
+ msg_result "no"
+ return 1
+ fi
+}
+
+cc_cxx_common()
+{
+ test "$cc_cxx_common_done" && return 0
+ cc_cxx_common_done=yes
+
+ var_default SOFLAGS "-fPIC"
+ var_default LDSOFLAGS "-shared"
+ var_default LDDLFLAGS "-shared"
+
+ common_cf=
+ common_lf=
+
+ case `uname -s` in
+ *BSD)
+ common_cf="$common_cf -I/usr/local/include"
+ common_lf="$common_lf -L/usr/local/lib"
+ ;;
+ Darwin)
+ # fink
+ if test -d /sw/lib
+ then
+ common_cf="$common_cf -I/sw/include"
+ common_lf="$common_lf -L/sw/lib"
+ fi
+ # darwinports
+ if test -d /opt/local/lib
+ then
+ common_cf="$common_cf -I/opt/local/include"
+ common_lf="$common_lf -L/opt/local/lib"
+ fi
+ LDSOFLAGS="-dynamic"
+ case ${MACOSX_DEPLOYMENT_TARGET} in
+ 10.[012])
+ LDDLFLAGS="-bundle -flat_namespace -undefined suppress"
+ ;;
+ 10.*)
+ LDDLFLAGS="-bundle -undefined dynamic_lookup"
+ ;;
+ *)
+ LDDLFLAGS="-bundle -flat_namespace -undefined suppress"
+ ;;
+ esac
+ ;;
+ SunOS)
+ common_cf="$common_cf -D__EXTENSIONS__ -I/usr/local/include"
+ common_lf="$common_lf -R/usr/local/lib -L/usr/local/lib"
+ ;;
+ esac
+ makefile_vars SOFLAGS LDSOFLAGS LDDLFLAGS
+}
+
+# CC, LD, CFLAGS, LDFLAGS, SOFLAGS, LDSOFLAGS, LDDLFLAGS
+check_cc()
+{
+ var_default CC ${CROSS}gcc
+ var_default LD $CC
+ var_default CFLAGS "-g -O2 -Wall"
+ var_default LDFLAGS ""
+ check_program $CC || return 1
+
+ cc_cxx_common
+ CFLAGS="$CFLAGS $common_cf"
+ LDFLAGS="$LDFLAGS $common_lf"
+
+ makefile_vars CC LD CFLAGS LDFLAGS
+ __check_lang=c
+ return 0
+}
+
+# HOSTCC, HOSTLD, HOST_CFLAGS, HOST_LDFLAGS
+check_host_cc()
+{
+ var_default HOSTCC gcc
+ var_default HOSTLD $HOSTCC
+ var_default HOST_CFLAGS "-g -O2 -Wall"
+ var_default HOST_LDFLAGS ""
+ check_program $HOSTCC || return 1
+ makefile_vars HOSTCC HOSTLD HOST_CFLAGS HOST_LDFLAGS
+ __check_lang=c
+ return 0
+}
+
+# CXX, CXXLD, CXXFLAGS, CXXLDFLAGS, SOFLAGS, LDSOFLAGS, LDDLFLAGS
+check_cxx()
+{
+ var_default CXX ${CROSS}g++
+ var_default CXXLD $CXX
+ var_default CXXFLAGS "-g -O2 -Wall"
+ var_default CXXLDFLAGS ""
+ check_program $CXX || return 1
+
+ cc_cxx_common
+ CXXFLAGS="$CXXFLAGS $common_cf"
+ CXXLDFLAGS="$CXXLDFLAGS $common_lf"
+
+ makefile_vars CXX CXXLD CXXFLAGS CXXLDFLAGS
+ __check_lang=cxx
+ return 0
+}
+
+# check if CC can generate dependencies (.dep-*.o files)
+# always succeeds
+check_cc_depgen()
+{
+ msg_checking "if CC can generate dependency information"
+ if cc_supports -MMD -MP -MF /dev/null
+ then
+ EXTRA_CFLAGS="$EXTRA_CFLAGS -MMD -MP -MF .dep-\$(subst /,-,\$@)"
+ msg_result yes
+ else
+ msg_result no
+ fi
+ return 0
+}
+
+# check if CXX can generate dependencies (.dep-*.o files)
+# always succeeds
+check_cxx_depgen()
+{
+ msg_checking "if CXX can generate dependency information"
+ if cxx_supports -MMD -MP -MF /dev/null
+ then
+ EXTRA_CXXFLAGS="$EXTRA_CXXFLAGS -MMD -MP -MF .dep-\$(subst /,-,\$@)"
+ msg_result yes
+ else
+ msg_result no
+ fi
+ return 0
+}
+
+# adds AR to config.mk
+check_ar()
+{
+ var_default AR ${CROSS}ar
+ var_default ARFLAGS "-cr"
+ if check_program $AR
+ then
+ makefile_vars AR ARFLAGS
+ return 0
+ fi
+ return 1
+}
+
+# adds AS to config.mk
+check_as()
+{
+ var_default AS ${CROSS}gcc
+ if check_program $AS
+ then
+ makefile_vars AS
+ return 0
+ fi
+ return 1
+}
+
+check_pkgconfig()
+{
+ if test -z "$PKG_CONFIG"
+ then
+ if check_program pkg-config PKG_CONFIG
+ then
+ makefile_vars PKG_CONFIG
+ else
+ # don't check again
+ PKG_CONFIG="no"
+ return 1
+ fi
+ fi
+ return 0
+}
+
+# check for library FOO and add FOO_CFLAGS and FOO_LIBS to config.mk
+#
+# @name: variable prefix (e.g. CURSES -> CURSES_CFLAGS, CURSES_LIBS)
+# @cflags: CFLAGS for the lib
+# @libs: LIBS to check
+#
+# adds @name_CFLAGS and @name_LIBS to config.mk
+# CFLAGS are not checked, they are assumed to be correct
+check_library()
+{
+ argc check_library $# 3 3
+ msg_checking "for ${1}_LIBS ($3)"
+ if try_link $3
+ then
+ msg_result yes
+ makefile_var ${1}_CFLAGS "$2"
+ makefile_var ${1}_LIBS "$3"
+ return 0
+ else
+ msg_result no
+ return 1
+ fi
+}
+
+# run pkg-config
+#
+# @prefix: variable prefix (e.g. GLIB -> GLIB_CFLAGS, GLIB_LIBS)
+# @modules: the argument for pkg-config
+# @cflags: CFLAGS to use if pkg-config failed (optional)
+# @libs: LIBS to use if pkg-config failed (optional)
+#
+# if pkg-config fails and @libs are given check_library is called
+#
+# example:
+# ---
+# check_glib()
+# {
+# pkg_config GLIB "glib-2.0 >= 2.2"
+# return $?
+# }
+#
+# check check_cc
+# check check_glib
+# ---
+# GLIB_CFLAGS and GLIB_LIBS are automatically added to Makefile
+pkg_config()
+{
+ argc pkg_config $# 2 4
+
+ # optional
+ __pc_cflags="$3"
+ __pc_libs="$4"
+
+ check_pkgconfig
+ msg_checking "for ${1}_LIBS (pkg-config)"
+ if test "$PKG_CONFIG" != "no" && $PKG_CONFIG --exists "$2" >/dev/null 2>&1
+ then
+ # pkg-config is installed and the .pc file exists
+ __pc_libs="`$PKG_CONFIG --libs ""$2""`"
+ msg_result "$__pc_libs"
+
+ msg_checking "for ${1}_CFLAGS (pkg-config)"
+ __pc_cflags="`$PKG_CONFIG --cflags ""$2""`"
+ msg_result "$__pc_cflags"
+
+ makefile_var ${1}_CFLAGS "$__pc_cflags"
+ makefile_var ${1}_LIBS "$__pc_libs"
+ return 0
+ fi
+
+ # no pkg-config or .pc file
+ msg_result "no"
+
+ if test -z "$__pc_libs"
+ then
+ if test "$PKG_CONFIG" = "no"
+ then
+ # pkg-config not installed and no libs to check were given
+ msg_error "pkg-config required for $1"
+ else
+ # pkg-config is installed but the required .pc file wasn't found
+ $PKG_CONFIG --errors-to-stdout --print-errors "$2" | sed 's:^:*** :'
+ fi
+ return 1
+ fi
+
+ check_library "$1" "$__pc_cflags" "$__pc_libs"
+ return $?
+}
+
+# old name
+pkg_check_modules()
+{
+ pkg_config "$@"
+}
+
+# run *-config
+#
+# @prefix: variable prefix (e.g. ARTS -> ARTS_CFLAGS, ARTS_LIBS)
+# @program: the -config program
+#
+# example:
+# ---
+# check_arts()
+# {
+# app_config ARTS artsc-config
+# return $?
+# }
+#
+# check check_cc
+# check check_arts
+# ---
+# ARTS_CFLAGS and ARTS_LIBS are automatically added to config.mk
+app_config()
+{
+ argc app_config $# 2 2
+ check_program $2 || return 1
+
+ msg_checking "for ${1}_CFLAGS"
+ __ac_cflags="`$2 --cflags`"
+ msg_result "$__ac_cflags"
+
+ msg_checking "for ${1}_LIBS"
+ __ac_libs="`$2 --libs`"
+ msg_result "$__ac_libs"
+
+ makefile_var ${1}_CFLAGS "$__ac_cflags"
+ makefile_var ${1}_LIBS "$__ac_libs"
+ return 0
+}
+
+# @contents: file contents to compile
+# @cflags: extra cflags (optional)
+try_compile()
+{
+ argc try_compile $# 1
+ case $__check_lang in
+ c)
+ __src=`tmp_file prog.c`
+ __obj=`tmp_file prog.o`
+ echo "$1" > $__src || exit 1
+ shift
+ __cmd="$CC -c $CFLAGS $@ $__src -o $__obj"
+ $CC -c $CFLAGS "$@" $__src -o $__obj 2>/dev/null
+ ;;
+ cxx)
+ __src=`tmp_file prog.cc`
+ __obj=`tmp_file prog.o`
+ echo "$1" > $__src || exit 1
+ shift
+ __cmd="$CXX -c $CXXFLAGS $@ $__src -o $__obj"
+ $CXX -c $CXXFLAGS "$@" $__src -o $__obj 2>/dev/null
+ ;;
+ esac
+ return $?
+}
+
+# @contents: file contents to compile and link
+# @flags: extra flags (optional)
+try_compile_link()
+{
+ argc try_compile $# 1
+ case $__check_lang in
+ c)
+ __src=`tmp_file prog.c`
+ __exe=`tmp_file prog`
+ echo "$1" > $__src || exit 1
+ shift
+ __cmd="$CC $__src -o $__exe $CFLAGS $LDFLAGS $@"
+ $CC $__src -o $__exe $CFLAGS $LDFLAGS "$@" 2>/dev/null
+ ;;
+ cxx)
+ __src=`tmp_file prog.cc`
+ __exe=`tmp_file prog`
+ echo "$1" > $__src || exit 1
+ shift
+ __cmd="$CXX $__src -o $__exe $CXXFLAGS $CXXLDFLAGS $@"
+ $CXX $__src -o $__exe $CXXFLAGS $CXXLDFLAGS "$@" 2>/dev/null
+ ;;
+ esac
+ return $?
+}
+
+# optionally used after try_compile or try_compile_link
+__compile_failed()
+{
+ warn
+ warn "Failed to compile simple program:"
+ warn "---"
+ cat $__src >&2
+ warn "---"
+ warn "Command: $__cmd"
+ case $__check_lang in
+ c)
+ warn "Make sure your CC and CFLAGS are sane."
+ ;;
+ cxx)
+ warn "Make sure your CXX and CXXFLAGS are sane."
+ ;;
+ esac
+ exit 1
+}
+
+# tries to link against a lib
+#
+# @function: some function
+# @flags: extra flags (optional)
+check_function()
+{
+ argc check_function $# 1
+ __func="$1"
+ shift
+ msg_checking "for function $__func"
+ if try_compile_link "char $__func(); int main(int argc, char *argv[]) { return $__func; }" "$@"
+ then
+ msg_result yes
+ return 0
+ fi
+
+ msg_result no
+ return 1
+}
+
+# tries to link against a lib
+#
+# @ldadd: something like -L/usr/X11R6/lib -lX11
+try_link()
+{
+ try_compile_link "int main(int argc, char *argv[]) { return 0; }" "$@"
+ return $?
+}
+
+# compile and run
+#
+# @code: simple program code to run
+run_code()
+{
+ if test $CROSS
+ then
+ msg_error "cannot run code when cross compiling"
+ exit 1
+ fi
+ try_compile_link "$1" || __compile_failed
+ ./$__exe
+ return $?
+}
+
+# check if the architecture is big-endian
+# parts are from autoconf 2.67
+#
+# defines WORDS_BIGENDIAN=y/n
+check_endianness()
+{
+ msg_checking "byte order"
+ WORDS_BIGENDIAN=n
+ # See if sys/param.h defines the BYTE_ORDER macro.
+ if try_compile_link "
+#include <sys/types.h>
+#include <sys/param.h>
+int main() {
+#if ! (defined BYTE_ORDER && defined BIG_ENDIAN \
+ && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \
+ && LITTLE_ENDIAN)
+ bogus endian macros
+#endif
+ return 0;
+}"
+ then
+ # It does; now see whether it defined to BIG_ENDIAN or not.
+ if try_compile_link "
+#include <sys/types.h>
+#include <sys/param.h>
+int main() {
+#if BYTE_ORDER != BIG_ENDIAN
+ not big endian
+#endif
+ return 0;
+}"
+ then
+ WORDS_BIGENDIAN=y
+ fi
+ # See if <limits.h> defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris).
+ elif try_compile_link "
+#include <limits.h>
+int main() {
+#if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN)
+ bogus endian macros
+#endif
+ return 0;
+}"
+ then
+ # It does; now see whether it defined to _BIG_ENDIAN or not.
+ if try_compile_link "
+#include <limits.h>
+int main() {
+#ifndef _BIG_ENDIAN
+ not big endian
+#endif
+ return 0;
+}"
+ then
+ WORDS_BIGENDIAN=y
+ fi
+ elif run_code "
+int main(int argc, char *argv[])
+{
+ unsigned int i = 1;
+ return *(char *)&i;
+}"
+ then
+ WORDS_BIGENDIAN=y
+ fi
+ if test "$WORDS_BIGENDIAN" = y
+ then
+ msg_result "big-endian"
+ else
+ msg_result "little-endian"
+ fi
+ return 0
+}
+
+# check if @header can be included
+#
+# @header
+# @cflags -I/some/path (optional)
+check_header()
+{
+ argc check_header $# 1
+ __header="$1"
+ shift
+ msg_checking "for header <$__header>"
+ if try_compile "#include <$__header>" "$@"
+ then
+ msg_result yes
+ return 0
+ fi
+ msg_result no
+ return 1
+}
+
+# check X11 libs
+#
+# adds X11_LIBS (and empty X11_CFLAGS) to config.mk
+check_x11()
+{
+ for __libs in "-lX11" "-L/usr/X11R6/lib -lX11"
+ do
+ check_library X11 "" "$__libs" && return 0
+ done
+ return 1
+}
+
+# check posix threads
+#
+# adds PTHREAD_CFLAGS and PTHREAD_LIBS to config.mk
+check_pthread()
+{
+ case `uname -s` in
+ OpenBSD)
+ PTHREAD_LIBS="$PTHREAD_LIBS -pthread"
+ ;;
+ esac
+
+ for __libs in "$PTHREAD_LIBS" -lpthread -lc_r -lkse
+ do
+ test -z "$__libs" && continue
+ check_library PTHREAD "-D_REENTRANT" "$__libs" && return 0
+ done
+ echo "using -pthread gcc option"
+ makefile_var PTHREAD_CFLAGS "-pthread -D_THREAD_SAFE"
+ makefile_var PTHREAD_LIBS "-pthread"
+ return 0
+}
+
+# check dynamic linking loader
+#
+# adds DL_LIBS to config.mk
+check_dl()
+{
+ for DL_LIBS in "-ldl -Wl,--export-dynamic" "-ldl -rdynamic" "-Wl,--export-dynamic" "-rdynamic" "-ldl"
+ do
+ check_library DL "" "$DL_LIBS" && return 0
+ done
+ echo "assuming -ldl is not needed"
+ DL_LIBS=
+ makefile_vars DL_LIBS DL_CFLAGS
+ return 0
+}
+
+# check for iconv
+#
+# adds ICONV_CFLAGS and ICONV_LIBS to config.mk
+check_iconv()
+{
+ HAVE_ICONV=n
+ if check_library ICONV "" "-liconv"
+ then
+ echo "taking iconv from libiconv"
+ else
+ echo "assuming libc contains iconv"
+ makefile_var ICONV_CFLAGS ""
+ makefile_var ICONV_LIBS ""
+ fi
+ msg_checking "for working iconv"
+ if try_compile_link '
+#include <stdio.h>
+#include <string.h>
+#include <iconv.h>
+int main(int argc, char *argv[]) {
+ char buf[128], *out = buf, *in = argv[1];
+ size_t outleft = 127, inleft = strlen(in);
+ iconv_t cd = iconv_open("UTF-8", "ISO-8859-1");
+ iconv(cd, &in, &inleft, &out, &outleft);
+ *out = 0;
+ printf("%s", buf);
+ iconv_close(cd);
+ return 0;
+}' $ICONV_CFLAGS $ICONV_LIBS
+ then
+ msg_result "yes"
+ HAVE_ICONV=y
+ else
+ msg_result "no"
+ msg_error "Your system doesn't have iconv!"
+ msg_error "This means that no charset conversion can be done, so all"
+ msg_error "your tracks need to be encoded in your system charset!"
+ fi
+
+ return 0
+}
--- /dev/null
+#!/bin/sh
+#
+# Copyright 2005 Timo Hirvonen
+#
+# This file is licensed under the GPLv2.
+
+. scripts/utils.sh || exit 1
+. scripts/checks.sh || exit 1
+
+# Usage: parse_command_line "$@"
+# USAGE string must be defined in configure (used for --help)
+parse_command_line()
+{
+ while test $# -gt 0
+ do
+ case $1 in
+ --help)
+ show_usage
+ ;;
+ -f)
+ shift
+ test $# -eq 0 && die "-f requires an argument"
+ . "$1"
+ ;;
+ -*)
+ die "unrecognized option \`$1'"
+ ;;
+ *=*)
+ _var=`echo "$1" | sed "s/=.*//"`
+ _val=`echo "$1" | sed "s/${_var}=//"`
+ set_var "$_var" "$_val"
+ ;;
+ *)
+ die "unrecognized argument \`$1'"
+ ;;
+ esac
+ shift
+ done
+}
+
+# check function [variable]
+#
+# Example:
+# check check_cc
+# check check_vorbis CONFIG_VORBIS
+check()
+{
+ argc check $# 1 2
+ if test $# -eq 1
+ then
+ $1 || die "configure failed."
+ return
+ fi
+
+ # optional feature
+ case `get_var $2` in
+ n)
+ ;;
+ y)
+ $1 || die "configure failed."
+ ;;
+ a|'')
+ if $1
+ then
+ set_var $2 y
+ else
+ set_var $2 n
+ fi
+ ;;
+ *)
+ die "invalid value for $2. 'y', 'n', 'a' or '' expected"
+ ;;
+ esac
+}
+
+# Set and register variable to be added to config.mk
+#
+# @name name of the variable
+# @value value of the variable
+makefile_var()
+{
+ argc makefile_var $# 2 2
+ set_var $1 "$2"
+ makefile_vars $1
+}
+
+# Register variables to be added to config.mk
+makefile_vars()
+{
+ makefile_variables="$makefile_variables $*"
+}
+
+# generate config.mk
+generate_config_mk()
+{
+ CFLAGS="$CFLAGS $EXTRA_CFLAGS"
+ CXXFLAGS="$CXXFLAGS $EXTRA_CXXFLAGS"
+ if test -z "$GINSTALL"
+ then
+ GINSTALL=`path_find ginstall`
+ test "$GINSTALL" || GINSTALL=install
+ fi
+ # $PWD is useless!
+ topdir=`pwd`
+ makefile_vars GINSTALL topdir
+
+ __tmp=`tmp_file config.mk`
+ for __i in $makefile_variables
+ do
+ echo "$__i = `get_var $__i`"
+ done > $__tmp
+ update_file $__tmp config.mk
+}
+
+# -----------------------------------------------------------------------------
+# Config header generation
+
+# Simple interface
+#
+# Guesses variable types:
+# y or n -> bool
+# [0-9]+ -> int
+# anything else -> str
+#
+# Example:
+# CONFIG_FOO=y # bool
+# VERSION=2.0.1 # string
+# DEBUG=1 # int
+# config_header config.h CONFIG_FOO VERSION DEBUG
+config_header()
+{
+ argc config_header $# 2
+ config_header_begin "$1"
+ shift
+ while test $# -gt 0
+ do
+ __var=`get_var $1`
+ case "$__var" in
+ [yn])
+ config_bool $1
+ ;;
+ *)
+ if test "$__var" && test "$__var" = "`echo $__var | sed 's/[^0-9]//g'`"
+ then
+ config_int $1
+ else
+ config_str $1
+ fi
+ ;;
+ esac
+ shift
+ done
+ config_header_end
+}
+
+# Low-level interface
+#
+# Example:
+# config_header_begin config.h
+# config_str PACKAGE VERSION
+# config_bool CONFIG_ALSA
+# config_header_end
+
+config_header_begin()
+{
+ argc config_header_begin $# 1 1
+ config_header_file="$1"
+ config_header_tmp=`tmp_file config_header`
+
+ __def=`echo $config_header_file | to_upper | sed 's/[-\.\/]/_/g'`
+ cat <<EOF > "$config_header_tmp"
+#ifndef $__def
+#define $__def
+
+EOF
+}
+
+config_str()
+{
+ while test $# -gt 0
+ do
+ echo "#define $1 \"`get_var $1`\"" >> "$config_header_tmp"
+ shift
+ done
+}
+
+config_int()
+{
+ while test $# -gt 0
+ do
+ echo "#define $1 `get_var $1`" >> "$config_header_tmp"
+ shift
+ done
+}
+
+config_bool()
+{
+ while test $# -gt 0
+ do
+ case "`get_var $1`" in
+ n)
+ echo "/* #define $1 */" >> "$config_header_tmp"
+ ;;
+ y)
+ echo "#define $1 1" >> "$config_header_tmp"
+ ;;
+ *)
+ die "bool '$1' has invalid value '`get_var $1`'"
+ ;;
+ esac
+ shift
+ done
+}
+
+config_header_end()
+{
+ argc config_header_end $# 0 0
+ echo "" >> "$config_header_tmp"
+ echo "#endif" >> "$config_header_tmp"
+ mkdir -p `dirname "$config_header_file"`
+ update_file "$config_header_tmp" "$config_header_file"
+}
--- /dev/null
+#!/bin/bash
+# vim: set expandtab shiftwidth=4:
+#
+# Copyright 2010-2012 Various Authors
+# Copyright 2012 Johannes Weißl
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+# many (!) FFmpeg versions will be installed here, at least 25GB
+FFMPEG_BUILD_DIR=$HOME/cmus_ffmpeg_test/ffmpeg_builds
+
+# ffmpeg/libav source will be cloned into this directory
+FFMPEG_SRC_DIR=$HOME/cmus_ffmpeg_test/ffmpeg_src
+
+# source code of cmus is expected here
+CMUS_SRC_DIR=$HOME/cmus_ffmpeg_test/cmus_src
+
+# cmus versions will be installed here
+CMUS_BUILD_DIR=$HOME/cmus_ffmpeg_test/cmus_builds
+
+FFMPEG_CLONE_URL=git://source.ffmpeg.org/ffmpeg.git
+LIBAV_CLONE_URL=git://git.libav.org/libav.git
+
+# headers of ffmpeg that are relevant to cmus compilation
+HEADERS="avcodec.h avformat.h avio.h mathematics.h version.h"
+
+# argument to make -j
+MAKE_J=$(grep -c "^processor" /proc/cpuinfo 2>/dev/null || echo 1)
+
+print_usage () {
+ echo "Usage: $progname build_ffmpeg | build_libav | build_cmus | test_cmus"
+ echo
+ echo "build_{ffmpeg,libav}:"
+ echo " 1. clone/pull source into $FFMPEG_SRC_DIR/{ffmpeg,libav}"
+ echo " 2. build and install (necessary) revisions into $FFMPEG_BUILD_DIR"
+ echo " can take days and needs up to 25 GB hard disk (!)"
+ echo " you can use ctrl-c to stop the script and run it later to continue"
+ echo
+ echo "build_cmus:"
+ echo " 1. expects cmus source in $CMUS_SRC_DIR"
+ echo " 2. build cmus for every revision in $FFMPEG_BUILD_DIR and install"
+ echo " to $CMUS_BUILD_DIR"
+ echo
+ echo "test_cmus:"
+ echo " test ffmpeg plugin of every cmus build in $CMUS_BUILD_DIR"
+}
+
+function get_commits () {
+ for name in "$@" ; do
+ find -type f -name "$name" -exec git log --follow --pretty=format:"%H%n" {} \;
+ done
+ for tag in $(git tag) ; do
+ git show "$tag" | sed -n "s/^commit //p"
+ done
+}
+
+function uniq_stable () {
+ nl -ba | sort -suk2 | sort -n | cut -f2-
+}
+
+DONE=
+trap 'DONE=1' SIGINT
+
+function build_to_prefix () {
+ prefix=$1
+ cur=$2
+ all=$3
+ cur_name=$4
+ build_cmd=$5
+ echo -n "[$((cur*100/all))%] "
+ if [ -e "$prefix.broken" ] ; then
+ echo "skip $cur_name, broken"
+ elif [ -e "$prefix.part" ] ; then
+ echo "skip $cur_name, is being build"
+ else
+ if [ -e "$prefix" ] ; then
+ echo "skip $cur_name, already built"
+ else
+ echo -n "build and install to $prefix: "
+ echo $build_cmd >"$prefix.log"
+ (mkdir -p "$prefix.part" && eval $build_cmd && mv "$prefix.part/$prefix" "$prefix" && rm -rf "$prefix.part") >>"$prefix.log" 2>&1 && echo "ok" ||
+ (touch "$prefix.broken" ; echo "FAILED:" ; echo $build_cmd)
+ fi
+ fi
+ [ -n "$DONE" ] && rm -rvf "$prefix" "$prefix".part "$prefix".broken
+}
+
+function build_revisions () {
+ name=$1
+ url=$2
+ mkdir -p "$FFMPEG_SRC_DIR" "$FFMPEG_BUILD_DIR"
+ FFMPEG_SRC_DIR=$FFMPEG_SRC_DIR/$name
+ if [ -e "$FFMPEG_SRC_DIR" ] ; then
+ echo "pull $url in $FFMPEG_SRC_DIR"
+ pushd "$FFMPEG_SRC_DIR" >/dev/null
+ git reset --hard origin/master >/dev/null
+ git clean -fxd >/dev/null
+ git pull >/dev/null
+ else
+ echo "clone $url in $FFMPEG_SRC_DIR"
+ git clone "$url" "$FFMPEG_SRC_DIR" >/dev/null
+ pushd "$FFMPEG_SRC_DIR" >/dev/null
+ fi
+ commits=$(get_commits $HEADERS | uniq_stable)
+ commits_count=$(echo $commits | wc -w)
+ i=0
+ for c in $commits ; do
+ i=$((i+1))
+ git reset --hard "$c" >/dev/null
+ git clean -fxd >/dev/null
+ prefix="$FFMPEG_BUILD_DIR/$c"
+ build_to_prefix "$prefix" "$i" "$commits_count" "$c" \
+ "./configure --prefix=\"$prefix.part\" --enable-shared --disable-static && make -j$MAKE_J && make install"
+ [ -n "$DONE" ] && break
+ done
+ popd >/dev/null
+}
+
+build_cmus () {
+ pushd "$CMUS_SRC_DIR" >/dev/null
+ mkdir -p "$CMUS_BUILD_DIR"
+ revdirs=$(find "$FFMPEG_BUILD_DIR" -mindepth 1 -maxdepth 1 -type d ! -name "*.part")
+ revdirs_count=$(echo $revdirs | wc -w)
+ i=0
+ for revdir in $revdirs ; do
+ i=$((i+1))
+ rev=$(basename "$revdir")
+ prefix="$CMUS_BUILD_DIR/$rev"
+ make distclean >/dev/null 2>&1
+ build_to_prefix "$prefix" "$i" "$revdirs_count" "$rev" \
+ "CFLAGS=\"-I$revdir/include\" LDFLAGS=\"-L$revdir/lib\" ./configure prefix=\"$prefix\" CONFIG_FFMPEG=y DEBUG=2 && make -j$MAKE_J && make install DESTDIR=\"$prefix.part\""
+ [ -n "$DONE" ] && break
+ done
+ popd >/dev/null
+}
+
+test_cmus () {
+ mkdir -p "$CMUS_BUILD_DIR"
+ revdirs=$(find "$CMUS_BUILD_DIR" -mindepth 1 -maxdepth 1 -type d ! -name "*.part")
+ revdirs_count=$(echo $revdirs | wc -w)
+ i=0
+ for revdir in $revdirs ; do
+ i=$((i+1))
+ rev=$(basename "$revdir")
+ tmpdir=$(mktemp -d)
+ lib_prefix=$FFMPEG_BUILD_DIR/$rev
+ echo -n "[$((i*100/revdirs_count))%] test $revdir: "
+ if CMUS_HOME=$tmpdir LD_LIBRARY_PATH=$lib_prefix/lib:$LD_LIBRARY_PATH "$revdir"/bin/cmus --plugins | grep -q "^ *ffmpeg" ; then
+ echo "working"
+ else
+ echo "not working: "
+ echo "CMUS_HOME=$tmpdir LD_LIBRARY_PATH=$lib_prefix/lib:$LD_LIBRARY_PATH \"$revdir\"/bin/cmus --plugins"
+ cat $tmpdir/cmus-debug.txt
+ fi
+ rm "$tmpdir"/cmus-debug.txt
+ rmdir "$tmpdir"
+ [ -n "$DONE" ] && break
+ done
+}
+
+progname=$(basename "$0")
+
+while [ $# -gt 0 ] ; do
+ case "$1" in
+ -h | --help)
+ print_usage
+ exit 0
+ ;;
+ --)
+ shift ; break
+ ;;
+ -*)
+ echo >&2 "$progname: unrecognized option \`$1'"
+ echo >&2 "Try \`$0 --help' for more information."
+ exit 1
+ ;;
+ *)
+ break
+ ;;
+ esac
+done
+
+if [ $# -eq 0 ] ; then
+ print_usage
+ exit 0
+elif [ $# -gt 1 ] ; then
+ echo >&2 "$progname: too many arguments"
+ echo >&2 "Try \`$0 --help' for more information."
+ exit 1
+fi
+
+case "$1" in
+ build_ffmpeg)
+ build_revisions ffmpeg "$FFMPEG_CLONE_URL"
+ ;;
+ build_libav)
+ build_revisions libav "$LIBAV_CLONE_URL"
+ ;;
+ build_cmus)
+ build_cmus
+ ;;
+ test_cmus)
+ test_cmus
+ ;;
+ *)
+ echo >&2 "$progname: unrecognized command \`$1'"
+ echo >&2 "Try \`$0 --help' for more information."
+ exit 1
+esac
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright 2010-2011 Various Authors
+# Copyright 2010 Johannes Weißl
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import re
+import os.path
+import urllib2
+from optparse import OptionParser
+
+# Some letters don't have a decomposition, but can't be composed on all
+# keyboards. This dictionary maps them to an ASCII character which
+# *looks* similar.
+special_decompositions = {
+ u'Æ': u'A',
+ u'Ð': u'D',
+ u'×': u'x',
+ u'Ø': u'O',
+ u'Þ': u'P',
+ u'ß': u'B',
+ u'æ': u'a',
+ u'ð': u'd',
+ u'ø': u'o',
+ u'þ': u'p',
+# Various punctation/quotation characters
+ u'‐': u'-',
+ u'‒': u'-',
+ u'–': u'-',
+ u'−': u'-',
+ u'—': u'-',
+ u'―': u'-',
+ u'‘': u"'",
+ u'’': u"'",
+ u'′': u"'",
+ u'“': u'"',
+ u'”': u'"',
+ u'″': u'"',
+ u'〃': u'"',
+ u'…': u'.',
+}
+
+def parse_unidata(f):
+ u = {}
+ for line in f:
+ d = line.rstrip('\n').split(';')
+ cp = int(d[0], 16)
+ u[cp] = {}
+ u[cp]['name'] = d[1]
+ decomp = d[5]
+ if decomp:
+ m = re.match(r'<.*> (.*)', decomp)
+ u[cp]['compat'] = bool(m)
+ if m:
+ decomp = m.group(1)
+ u[cp]['decomp'] = [int(x, 16) for x in decomp.split(' ')]
+ else:
+ u[cp]['decomp'] = []
+ return u
+
+def unidata_expand_decomp(unidata):
+ def recurse(k):
+ if k not in unidata or not unidata[k]['decomp']:
+ return [k]
+ exp = []
+ for d in unidata[k]['decomp']:
+ exp += recurse(d)
+ return exp
+ for k in unidata.keys():
+ exp = recurse(k)
+ if exp != [k]:
+ unidata[k]['decomp'] = exp
+
+def unidata_add_mapping(unidata, mapping):
+ for k, v in mapping.items():
+ unidata[ord(k)]['decomp'] = [ord(v)]
+
+def is_diacritical_mark(c):
+ return c >= 0x0300 and c <= 0x036F
+
+def filter_unidata(unidata, include):
+ for k, v in unidata.items():
+ if k in include:
+ continue
+ if not v['decomp']:
+ del unidata[k]
+ continue
+ b = v['decomp'][0]
+ if unichr(b) == u' ' or is_diacritical_mark(b):
+ del unidata[k]
+ continue
+ has_accents = False
+ for d in v['decomp'][1:]:
+ if is_diacritical_mark(d):
+ has_accents = True
+ break
+ if not has_accents:
+ del unidata[k]
+
+def output(unidata, f):
+ buf = '''/* This file is automatically generated. DO NOT EDIT!
+Instead, edit %s and re-run. */
+
+static struct {
+ uchar composed;
+ uchar base;
+} unidecomp_map[] = {
+''' % os.path.basename(sys.argv[0])
+ for k in sorted(unidata.keys()):
+ b = unidata[k]['decomp'][0]
+ buf += '\t{ %#6x, %#6x },\t// %s -> %s,\t%s\n' % \
+ (k, b,
+ unichr(k).encode('utf-8'),
+ unichr(b).encode('utf-8'),
+ ', '.join([' %s (%x)' %
+ (unichr(d).encode('utf-8'), d)
+ for d in unidata[k]['decomp'][1:]]))
+ buf += '};'
+ f.write(buf+'\n')
+
+def main(argv=None):
+
+ if not argv:
+ argv = sys.argv
+
+ parser = OptionParser(usage='usage: %prog [-w] [-o unidecomp.h]')
+ parser.add_option('-w', '--wget', action='store_true',
+ help='get unicode data from unicode.org')
+ parser.add_option('-o', '--output',
+ help='output file, default stdout')
+ (options, args) = parser.parse_args(argv[1:])
+
+ urlbase = 'http://unicode.org/Public/UNIDATA/'
+ unidata_filename = 'UnicodeData.txt'
+
+ if not os.path.exists(unidata_filename) and not options.wget:
+ parser.error('''need %s in the current directory, download
+from unicode.org or use `--wget' option.''' % unidata_filename)
+
+ if options.wget:
+ unidata_file = urllib2.urlopen(urlbase+unidata_filename)
+ else:
+ unidata_file = open(unidata_filename, 'rb')
+
+ unidata = parse_unidata(unidata_file)
+ unidata_file.close()
+
+ unidata_add_mapping(unidata, special_decompositions)
+ unidata_expand_decomp(unidata)
+ filter_unidata(unidata, [ord(x) for x in special_decompositions])
+
+ outfile = sys.stdout
+ if options.output:
+ outfile = open(options.output, 'wb')
+ output(unidata, outfile)
+ if options.output:
+ outfile.close()
+
+if __name__ == '__main__':
+ sys.exit(main())
--- /dev/null
+#!/bin/sh
+#
+# Copyright 2005-2006 Timo Hirvonen
+#
+# This file is licensed under the GPLv2.
+
+flags=""
+while test $# -gt 0
+do
+ case $1 in
+ -*)
+ flags="$flags $1"
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+done
+
+test $# -lt 2 && exit 0
+
+to="${DESTDIR}${1}"
+shift
+$GINSTALL -d -m755 "${to}"
+for i in "$@"
+do
+ dest="${to}/`basename ${i}`"
+ test "$INSTALL_LOG" && echo "$dest" >> "$INSTALL_LOG"
+ echo "INSTALL ${dest}"
+ $GINSTALL $flags "$i" "${to}"
+done
--- /dev/null
+#
+# Copyright 2005-2006 Timo Hirvonen
+#
+# This file is licensed under the GPLv2.
+#
+# cmd macro copied from kbuild (Linux kernel build system)
+
+# Build verbosity:
+# make V=0 silent
+# make V=1 clean (default)
+# make V=2 verbose
+
+# build verbosity (0-2), default is 1
+ifneq ($(origin V),command line)
+ V := 1
+endif
+ifneq ($(findstring s,$(MAKEFLAGS)),)
+ V := 0
+endif
+
+ifeq ($(V),2)
+ quiet =
+ Q =
+else
+ ifeq ($(V),1)
+ quiet = quiet_
+ Q = @
+ else
+ quiet = silent_
+ Q = @
+ endif
+endif
+
+# simple wrapper around install(1)
+#
+# - creates directories automatically
+# - adds $(DESTDIR) to front of files
+INSTALL := @$(topdir)/scripts/install
+INSTALL_LOG := $(topdir)/.install.log
+
+dependencies := $(wildcard .dep-*)
+clean := $(dependencies)
+distclean :=
+
+LC_ALL := C
+LANG := C
+
+export INSTALL_LOG LC_ALL LANG GINSTALL
+
+# remove files generated by make
+clean:
+ rm -f $(clean)
+
+# remove files generated by make and configure
+distclean: clean
+ rm -f $(distclean)
+
+uninstall:
+ @$(topdir)/scripts/uninstall
+
+%.o: %.S
+ $(call cmd,as)
+
+# object files for programs and static libs
+%.o: %.c
+ $(call cmd,cc)
+
+%.o: %.cc
+ $(call cmd,cxx)
+
+%.o: %.cpp
+ $(call cmd,cxx)
+
+# object files for shared libs
+%.lo: %.c
+ $(call cmd,cc_lo)
+
+%.lo: %.cc
+ $(call cmd,cxx_lo)
+
+%.lo: %.cpp
+ $(call cmd,cxx_lo)
+
+# CC for program object files (.o)
+quiet_cmd_cc = CC $@
+ cmd_cc = $(CC) -c $(CFLAGS) -o $@ $<
+
+# HOSTCC for program object files (.o)
+quiet_cmd_hostcc = HOSTCC $@
+ cmd_hostcc = $(HOSTCC) -c $(HOST_CFLAGS) -o $@ $<
+
+# CC for shared library and dynamically loadable module objects (.lo)
+quiet_cmd_cc_lo = CC $@
+ cmd_cc_lo = $(CC) -c $(CFLAGS) $(SOFLAGS) -o $@ $<
+
+# LD for programs, optional parameter: libraries
+quiet_cmd_ld = LD $@
+ cmd_ld = $(LD) $(LDFLAGS) -o $@ $^ $(1)
+
+# HOSTLD for programs, optional parameter: libraries
+quiet_cmd_hostld = HOSTLD $@
+ cmd_hostld = $(HOSTLD) $(HOST_LDFLAGS) -o $@ $^ $(1)
+
+# LD for shared libraries, optional parameter: libraries
+quiet_cmd_ld_so = LD $@
+ cmd_ld_so = $(LD) $(LDSOFLAGS) $(LDFLAGS) -o $@ $^ $(1)
+
+# LD for dynamically loadable modules, optional parameter: libraries
+quiet_cmd_ld_dl = LD $@
+ cmd_ld_dl = $(LD) $(LDDLFLAGS) $(LDFLAGS) -o $@ $^ $(1)
+
+# CXX for program object files (.o)
+quiet_cmd_cxx = CXX $@
+ cmd_cxx = $(CXX) -c $(CXXFLAGS) -o $@ $<
+
+# CXX for shared library and dynamically loadable module objects (.lo)
+quiet_cmd_cxx_lo = CXX $@
+ cmd_cxx_lo = $(CXX) -c $(CXXFLAGS) $(SOFLAGS) -o $@ $<
+
+# CXXLD for programs, optional parameter: libraries
+quiet_cmd_cxxld = CXXLD $@
+ cmd_cxxld = $(CXXLD) $(CXXLDFLAGS) -o $@ $^ $(1)
+
+# CXXLD for shared libraries, optional parameter: libraries
+quiet_cmd_cxxld_so = CXXLD $@
+ cmd_cxxld_so = $(CXXLD) $(LDSOFLAGS) $(CXXLDFLAGS) -o $@ $^ $(1)
+
+# CXXLD for dynamically loadable modules, optional parameter: libraries
+quiet_cmd_cxxld_dl = CXXLD $@
+ cmd_cxxld_dl = $(CXXLD) $(LDDLFLAGS) $(CXXLDFLAGS) -o $@ $^ $(1)
+
+# create archive
+quiet_cmd_ar = AR $@
+ cmd_ar = $(AR) $(ARFLAGS) $@ $^
+
+# assembler
+quiet_cmd_as = AS $@
+ cmd_as = $(AS) -c $(ASFLAGS) -o $@ $<
+
+cmd = @$(if $($(quiet)cmd_$(1)),echo ' $(call $(quiet)cmd_$(1),$(2))' &&) $(call cmd_$(1),$(2))
+
+ifneq ($(dependencies),)
+-include $(dependencies)
+endif
+
+.SECONDARY:
+
+.PHONY: clean distclean uninstall
--- /dev/null
+#!/bin/sh
+
+test -f "$INSTALL_LOG" || exit 0
+
+sort "$INSTALL_LOG" | uniq | \
+while read file
+do
+ echo "RM $file"
+ rm -f "$file"
+ rmdir -p "`dirname $file`" 2>/dev/null
+done
+rm -f "$INSTALL_LOG"
--- /dev/null
+#!/bin/sh
+#
+# Copyright 2005 Timo Hirvonen
+#
+# This file is licensed under the GPLv2.
+
+# initialization {{{
+
+LC_ALL=C
+LANG=C
+
+export LC_ALL LANG
+
+if test "$CDPATH"
+then
+ echo "Exporting CDPATH is dangerous and unnecessary!"
+ echo
+fi
+unset CDPATH
+
+__cleanup()
+{
+ if test "$DEBUG_CONFIGURE" = y
+ then
+ echo
+ echo "DEBUG_CONFIGURE=y, not removing temporary files"
+ ls .tmp-[0-9]*-*
+ else
+ rm -f .tmp-[0-9]*-*
+ fi
+}
+
+__abort()
+{
+ # can't use "die" because stderr is often redirected to /dev/null
+ # (stdout could also be redirected but it's not so common)
+ echo
+ echo
+ echo "Aborting. configure failed."
+ # this executes __cleanup automatically
+ exit 1
+}
+
+# clean temporary files on exit
+trap '__cleanup' 0
+
+# clean temporary files and die with error message if interrupted
+trap '__abort' 1 2 3 13 15
+
+# }}}
+
+# config.mk variable names
+makefile_variables=""
+
+# cross compilation, prefix for CC, LD etc.
+# CROSS=
+
+# argc function_name $# min [max]
+argc()
+{
+ if test $# -lt 3 || test $# -gt 4
+ then
+ die "argc: expecting 3-4 arguments (got $*)"
+ fi
+
+ if test $# -eq 3
+ then
+ if test $2 -lt $3
+ then
+ die "$1: expecting at least $3 arguments"
+ fi
+ else
+ if test $2 -lt $3 || test $2 -gt $4
+ then
+ die "$1: expecting $3-$4 arguments"
+ fi
+ fi
+}
+
+# print warning message (all parameters)
+warn()
+{
+ echo "$@" >&2
+}
+
+# print error message (all parameters) and exit
+die()
+{
+ warn "$@"
+ exit 1
+}
+
+# usage: 'tmp_file .c'
+# get filename for temporary file
+tmp_file()
+{
+ if test -z "$__tmp_file_counter"
+ then
+ __tmp_file_counter=0
+ fi
+
+ while true
+ do
+ __tmp_filename=.tmp-${__tmp_file_counter}-${1}
+ __tmp_file_counter=`expr $__tmp_file_counter + 1`
+ test -f "$__tmp_filename" || break
+ done
+ echo "$__tmp_filename"
+}
+
+# get variable value
+#
+# @name: name of the variable
+get_var()
+{
+ eval echo '"$'${1}'"'
+}
+
+# set variable by name
+#
+# @name: name of the variable
+# @value: value of the variable
+set_var()
+{
+ eval $1='$2'
+}
+
+# set variable @name to @default IF NOT SET OR EMPTY
+#
+# @name: name of the variable
+# @value: value of the variable
+var_default()
+{
+ test "`get_var $1`" || set_var $1 "$2"
+}
+
+# usage: echo $foo | to_upper
+to_upper()
+{
+ # stupid solaris tr doesn't understand ranges
+ tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ
+}
+
+# portable which command
+path_find()
+{
+ case $1 in
+ */*)
+ if test -x "$1"
+ then
+ echo "$1"
+ return 0
+ fi
+ return 1
+ ;;
+ esac
+
+ _ifs="$IFS"
+ IFS=:
+ for __pf_i in $PATH
+ do
+ if test -x "$__pf_i/$1"
+ then
+ IFS="$_ifs"
+ echo "$__pf_i/$1"
+ return 0
+ fi
+ done
+ IFS="$_ifs"
+ return 1
+}
+
+show_usage()
+{
+ cat <<EOF
+Usage ./configure [-f FILE] [OPTION=VALUE]...
+
+ -f FILE Read OPTION=VALUE list from FILE (sh script)
+$USAGE
+EOF
+ exit 0
+}
+
+# @tmpfile: temporary file
+# @file: file to update
+#
+# replace @file with @tmpfile if their contents differ
+update_file()
+{
+ if test -f "$2"
+ then
+ if cmp "$2" "$1" 2>/dev/null 1>&2
+ then
+ return 0
+ fi
+ echo "updating $2"
+ else
+ echo "creating $2"
+ fi
+ mv -f "$1" "$2"
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "search.h"
+#include "editable.h"
+#include "xmalloc.h"
+#include "ui_curses.h"
+#include "convert.h"
+#include "options.h"
+
+struct searchable {
+ void *data;
+ struct iter head;
+ struct searchable_ops ops;
+};
+
+static void search_lock(void)
+{
+ editable_lock();
+}
+
+static void search_unlock(void)
+{
+ editable_unlock();
+}
+
+/* returns next matching track (can be current!) or NULL if not found */
+static int do_u_search(struct searchable *s, struct iter *iter, const char *text, int direction)
+{
+ struct iter start = *iter;
+ const char *msg = NULL;
+
+ while (1) {
+ if (s->ops.matches(s->data, iter, text)) {
+ if (msg)
+ info_msg("%s\n", msg);
+ return 1;
+ }
+ if (direction == SEARCH_FORWARD) {
+ if (!s->ops.get_next(iter)) {
+ if (!wrap_search)
+ return 0;
+ *iter = s->head;
+ if (!s->ops.get_next(iter))
+ return 0;
+ msg = "search hit BOTTOM, continuing at TOP";
+ }
+ } else {
+ if (!s->ops.get_prev(iter)) {
+ if (!wrap_search)
+ return 0;
+ *iter = s->head;
+ if (!s->ops.get_prev(iter))
+ return 0;
+ msg = "search hit TOP, continuing at BOTTOM";
+ }
+ }
+ if (iters_equal(iter, &start)) {
+ return 0;
+ }
+ }
+}
+
+static int do_search(struct searchable *s, struct iter *iter, const char *text, int direction)
+{
+ char *u_text = NULL;
+ int r;
+
+ /* search text is always in locale encoding (because cmdline is) */
+ if (!using_utf8 && utf8_encode(text, charset, &u_text) == 0)
+ text = u_text;
+
+ r = do_u_search(s, iter, text, direction);
+
+ free(u_text);
+ return r;
+}
+
+struct searchable *searchable_new(void *data, const struct iter *head, const struct searchable_ops *ops)
+{
+ struct searchable *s;
+
+ s = xnew(struct searchable, 1);
+ s->data = data;
+ s->head = *head;
+ s->ops = *ops;
+ return s;
+}
+
+void searchable_free(struct searchable *s)
+{
+ free(s);
+}
+
+int search(struct searchable *s, const char *text, enum search_direction dir, int beginning)
+{
+ struct iter iter;
+ int ret;
+
+ search_lock();
+ if (beginning) {
+ /* first or last item */
+ iter = s->head;
+ if (dir == SEARCH_FORWARD) {
+ ret = s->ops.get_next(&iter);
+ } else {
+ ret = s->ops.get_prev(&iter);
+ }
+ } else {
+ /* selected item */
+ ret = s->ops.get_current(s->data, &iter);
+ }
+ if (ret)
+ ret = do_search(s, &iter, text, dir);
+ search_unlock();
+ return ret;
+}
+
+int search_next(struct searchable *s, const char *text, enum search_direction dir)
+{
+ struct iter iter;
+ int ret;
+
+ search_lock();
+ if (!s->ops.get_current(s->data, &iter)) {
+ search_unlock();
+ return 0;
+ }
+ if (dir == SEARCH_FORWARD) {
+ ret = s->ops.get_next(&iter);
+ } else {
+ ret = s->ops.get_prev(&iter);
+ }
+ if (ret)
+ ret = do_search(s, &iter, text, dir);
+ search_unlock();
+ return ret;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SEARCH_H
+#define _SEARCH_H
+
+#include "iter.h"
+
+enum search_direction { SEARCH_FORWARD, SEARCH_BACKWARD };
+
+struct searchable_ops {
+ int (*get_prev)(struct iter *iter);
+ int (*get_next)(struct iter *iter);
+ int (*get_current)(void *data, struct iter *iter);
+ int (*matches)(void *data, struct iter *iter, const char *text);
+};
+
+struct searchable;
+
+struct searchable *searchable_new(void *data, const struct iter *head, const struct searchable_ops *ops);
+void searchable_free(struct searchable *s);
+
+int search(struct searchable *s, const char *text, enum search_direction dir, int beginning);
+int search_next(struct searchable *s, const char *text, enum search_direction dir);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "search_mode.h"
+#include "cmdline.h"
+#include "history.h"
+#include "ui_curses.h"
+#include "search.h"
+#include "xmalloc.h"
+#include "xstrjoin.h"
+#include "misc.h"
+#include "lib.h"
+
+#include <ctype.h>
+
+#if defined(__sun__)
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+/* this is set in ui_curses.c */
+enum search_direction search_direction = SEARCH_FORWARD;
+
+/* current search string, this is set _only_ when user presses enter
+ * this string is used when 'n' or 'N' is pressed
+ * incremental search does not use this, it uses cmdline.line directly
+ */
+char *search_str = NULL;
+int search_restricted = 0;
+
+static struct history search_history;
+static char *search_history_filename;
+static char *history_search_text = NULL;
+
+static void update_search_line(const char *text, int restricted)
+{
+ int len = strlen(text);
+ char ch = search_direction == SEARCH_FORWARD ? '/' : '?';
+ char *buf, *ptr;
+
+ buf = xnew(char, len + 2);
+ ptr = buf;
+ if (restricted)
+ *ptr++ = ch;
+ memcpy(ptr, text, len + 1);
+ cmdline_set_text(buf);
+ free(buf);
+}
+
+static int search_line_empty(void)
+{
+ char ch;
+
+ if (cmdline.clen == 0)
+ return 1;
+ if (cmdline.clen > 1)
+ return 0;
+ ch = search_direction == SEARCH_FORWARD ? '/' : '?';
+ return cmdline.line[0] == ch;
+}
+
+static void parse_line(const char **text, int *restricted)
+{
+ char ch = search_direction == SEARCH_FORWARD ? '/' : '?';
+ int r = 0;
+
+ if (cmdline.line[0] == ch) {
+ /* //WORDS or ??WORDS */
+ r = 1;
+ }
+ *text = cmdline.line + r;
+ *restricted = r;
+}
+
+static void reset_history_search(void)
+{
+ history_reset_search(&search_history);
+ free(history_search_text);
+ history_search_text = NULL;
+}
+
+static void backspace(void)
+{
+ if (cmdline.clen > 0) {
+ cmdline_backspace();
+ } else {
+ input_mode = NORMAL_MODE;
+ }
+}
+
+static void delete(void)
+{
+ /* save old value */
+ int restricted = search_restricted;
+ const char *text;
+
+ cmdline_delete_ch();
+ parse_line(&text, &search_restricted);
+ if (text[0])
+ search(searchable, text, search_direction, 0);
+
+ /* restore old value */
+ search_restricted = restricted;
+}
+
+void search_text(const char *text, int restricted, int beginning)
+{
+ if (text[0] == 0) {
+ /* cmdline is "/", "?", "//" or "??" */
+ if (search_str) {
+ /* use old search string */
+ search_restricted = restricted;
+ if (!search_next(searchable, search_str, search_direction))
+ search_not_found();
+ }
+ } else {
+ /* set new search string and add it to the history */
+ free(search_str);
+ search_str = xstrdup(text);
+ history_add_line(&search_history, text);
+
+ /* search not yet done if up or down arrow was pressed */
+ search_restricted = restricted;
+ if (!search(searchable, search_str, search_direction, beginning))
+ search_not_found();
+ }
+}
+
+void search_mode_ch(uchar ch)
+{
+ const char *text;
+ int restricted;
+
+ switch (ch) {
+ case 0x01: // ^A
+ cmdline_move_home();
+ break;
+ case 0x02: // ^B
+ cmdline_move_left();
+ break;
+ case 0x04: // ^D
+ delete();
+ break;
+ case 0x05: // ^E
+ cmdline_move_end();
+ break;
+ case 0x06: // ^F
+ cmdline_move_right();
+ break;
+ case 0x03: // ^C
+ case 0x07: // ^G
+ case 0x1B: // ESC
+ parse_line(&text, &restricted);
+ if (text[0]) {
+ history_add_line(&search_history, text);
+ cmdline_clear();
+ }
+ input_mode = NORMAL_MODE;
+ break;
+ case 0x0A:
+ parse_line(&text, &restricted);
+ search_text(text, restricted, 0);
+ cmdline_clear();
+ input_mode = NORMAL_MODE;
+ break;
+ case 0x0B:
+ cmdline_clear_end();
+ break;
+ case 0x15:
+ cmdline_backspace_to_bol();
+ break;
+ case 0x17: // ^W
+ cmdline_backward_delete_word(cmdline_word_delimiters);
+ break;
+ case 0x08: // ^H
+ case 127:
+ backspace();
+ break;
+ default:
+ if (ch < 0x20) {
+ return;
+ } else {
+ /* start from beginning if this is first char */
+ int beginning = search_line_empty();
+
+ /* save old value
+ *
+ * don't set search_{str,restricted} here because
+ * search can be cancelled by pressing ESC
+ */
+ restricted = search_restricted;
+
+ cmdline_insert_ch(ch);
+ parse_line(&text, &search_restricted);
+ search(searchable, text, search_direction, beginning);
+
+ /* restore old value */
+ search_restricted = restricted;
+ }
+ break;
+ }
+ reset_history_search();
+}
+
+void search_mode_escape(int c)
+{
+ switch (c) {
+ case 98:
+ cmdline_backward_word(cmdline_filename_delimiters);
+ break;
+ case 100:
+ cmdline_delete_word(cmdline_filename_delimiters);
+ break;
+ case 102:
+ cmdline_forward_word(cmdline_filename_delimiters);
+ break;
+ case 127:
+ case KEY_BACKSPACE:
+ cmdline_backward_delete_word(cmdline_filename_delimiters);
+ break;
+ }
+ reset_history_search();
+}
+
+void search_mode_key(int key)
+{
+ const char *text;
+ int restricted;
+
+ switch (key) {
+ case KEY_DC:
+ delete();
+ break;
+ case KEY_BACKSPACE:
+ backspace();
+ break;
+ case KEY_LEFT:
+ cmdline_move_left();
+ return;
+ case KEY_RIGHT:
+ cmdline_move_right();
+ return;
+ case KEY_HOME:
+ cmdline_move_home();
+ return;
+ case KEY_END:
+ cmdline_move_end();
+ return;
+ case KEY_UP:
+ parse_line(&text, &restricted);
+ if (history_search_text == NULL)
+ history_search_text = xstrdup(text);
+ text = history_search_forward(&search_history, history_search_text);
+ if (text)
+ update_search_line(text, restricted);
+ return;
+ case KEY_DOWN:
+ if (history_search_text) {
+ parse_line(&text, &restricted);
+ text = history_search_backward(&search_history, history_search_text);
+ if (text) {
+ update_search_line(text, restricted);
+ } else {
+ update_search_line(history_search_text, restricted);
+ }
+ }
+ return;
+ default:
+ return;
+ }
+ reset_history_search();
+}
+
+void search_mode_init(void)
+{
+ search_history_filename = xstrjoin(cmus_config_dir, "/search-history");
+ history_load(&search_history, search_history_filename, 100);
+}
+
+void search_mode_exit(void)
+{
+ history_save(&search_history);
+ history_free(&search_history);
+ free(search_history_filename);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SEARCH_MODE_H
+#define _SEARCH_MODE_H
+
+#include "search.h"
+#include "uchar.h"
+
+extern char *search_str;
+extern enum search_direction search_direction;
+
+/* //WORDS or ??WORDS search mode */
+extern int search_restricted;
+
+void search_mode_ch(uchar ch);
+void search_mode_escape(int c);
+void search_mode_key(int key);
+void search_mode_init(void);
+void search_mode_exit(void);
+
+void search_text(const char *text, int restricted, int beginning);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "server.h"
+#include "prog.h"
+#include "command_mode.h"
+#include "search_mode.h"
+#include "options.h"
+#include "output.h"
+#include "utils.h"
+#include "xmalloc.h"
+#include "player.h"
+#include "file.h"
+#include "compiler.h"
+#include "debug.h"
+#include "gbuf.h"
+#include "ui_curses.h"
+#include "misc.h"
+#include "keyval.h"
+
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+int server_socket;
+LIST_HEAD(client_head);
+
+static union {
+ struct sockaddr sa;
+ struct sockaddr_un un;
+ struct sockaddr_storage sas;
+} addr;
+
+#define MAX_CLIENTS 10
+
+static int cmd_status(struct client *client)
+{
+ const char *export_options[] = {
+ "aaa_mode",
+ "continue",
+ "play_library",
+ "play_sorted",
+ "replaygain",
+ "replaygain_limit",
+ "replaygain_preamp",
+ "repeat",
+ "repeat_current",
+ "shuffle",
+ "softvol",
+ NULL
+ };
+ const struct track_info *ti;
+ struct cmus_opt *opt;
+ char optbuf[OPTION_MAX_SIZE];
+ GBUF(buf);
+ int vol_left, vol_right;
+ int i, ret;
+
+ player_info_lock();
+ gbuf_addf(&buf, "status %s\n", player_status_names[player_info.status]);
+ ti = player_info.ti;
+ if (ti) {
+ gbuf_addf(&buf, "file %s\n", escape(ti->filename));
+ gbuf_addf(&buf, "duration %d\n", ti->duration);
+ gbuf_addf(&buf, "position %d\n", player_info.pos);
+ for (i = 0; ti->comments[i].key; i++)
+ gbuf_addf(&buf, "tag %s %s\n",
+ ti->comments[i].key,
+ escape(ti->comments[i].val));
+ }
+
+ /* output options */
+ for (i = 0; export_options[i]; i++) {
+ opt = option_find(export_options[i]);
+ if (opt) {
+ opt->get(opt->id, optbuf);
+ gbuf_addf(&buf, "set %s %s\n", opt->name, optbuf);
+ }
+ }
+
+ /* get volume (copied from ui_curses.c) */
+ if (soft_vol) {
+ vol_left = soft_vol_l;
+ vol_right = soft_vol_r;
+ } else if (!volume_max) {
+ vol_left = vol_right = -1;
+ } else {
+ vol_left = scale_to_percentage(volume_l, volume_max);
+ vol_right = scale_to_percentage(volume_r, volume_max);
+ }
+
+ /* output volume */
+ gbuf_addf(&buf, "set vol_left %d\n", vol_left);
+ gbuf_addf(&buf, "set vol_right %d\n", vol_right);
+
+ gbuf_add_str(&buf, "\n");
+ player_info_unlock();
+
+ ret = write_all(client->fd, buf.buffer, buf.len);
+ gbuf_free(&buf);
+ return ret;
+}
+
+static ssize_t send_answer(int fd, const char *format, ...)
+{
+ char buf[512];
+ va_list ap;
+
+ va_start(ap, format);
+ vsnprintf(buf, sizeof(buf), format, ap);
+ va_end(ap);
+
+ return write_all(fd, buf, strlen(buf));
+}
+
+static void read_commands(struct client *client)
+{
+ char buf[1024];
+ int pos = 0;
+ if (!client->authenticated)
+ client->authenticated = addr.sa.sa_family == AF_UNIX;
+
+ while (1) {
+ int rc, s, i;
+
+ rc = read(client->fd, buf + pos, sizeof(buf) - pos);
+ if (rc == -1) {
+ if (errno == EINTR)
+ continue;
+ if (errno == EAGAIN)
+ return;
+ goto close;
+ }
+ if (rc == 0)
+ goto close;
+ pos += rc;
+
+ s = 0;
+ for (i = 0; i < pos; i++) {
+ const char *line, *msg;
+ char *cmd, *arg;
+ int ret;
+
+ if (buf[i] != '\n')
+ continue;
+
+ buf[i] = 0;
+ line = buf + s;
+ s = i + 1;
+
+ if (!client->authenticated) {
+ if (!server_password) {
+ msg = "password is unset, tcp/ip disabled";
+ d_print("%s\n", msg);
+ ret = send_answer(client->fd, "%s\n\n", msg);
+ goto close;
+ }
+ if (strncmp(line, "passwd ", 7) == 0)
+ line += 7;
+ client->authenticated = !strcmp(line, server_password);
+ if (!client->authenticated) {
+ msg = "authentication failed";
+ d_print("%s\n", msg);
+ ret = send_answer(client->fd, "%s\n\n", msg);
+ goto close;
+ }
+ ret = write_all(client->fd, "\n", 1);
+ continue;
+ }
+
+ while (isspace((unsigned char)*line))
+ line++;
+
+ if (*line == '/') {
+ int restricted = 0;
+ line++;
+ search_direction = SEARCH_FORWARD;
+ if (*line == '/') {
+ line++;
+ restricted = 1;
+ }
+ search_text(line, restricted, 1);
+ ret = write_all(client->fd, "\n", 1);
+ } else if (*line == '?') {
+ int restricted = 0;
+ line++;
+ search_direction = SEARCH_BACKWARD;
+ if (*line == '?') {
+ line++;
+ restricted = 1;
+ }
+ search_text(line, restricted, 1);
+ ret = write_all(client->fd, "\n", 1);
+ } else if (parse_command(line, &cmd, &arg)) {
+ if (!strcmp(cmd, "status")) {
+ ret = cmd_status(client);
+ } else {
+ if (strcmp(cmd, "passwd") != 0) {
+ set_client_fd(client->fd);
+ run_parsed_command(cmd, arg);
+ set_client_fd(-1);
+ }
+ ret = write_all(client->fd, "\n", 1);
+ }
+ free(cmd);
+ free(arg);
+ } else {
+ // don't hang cmus-remote
+ ret = write_all(client->fd, "\n", 1);
+ }
+ if (ret < 0) {
+ d_print("write: %s\n", strerror(errno));
+ goto close;
+ }
+ }
+ memmove(buf, buf + s, pos - s);
+ pos -= s;
+ }
+ return;
+close:
+ close(client->fd);
+ list_del(&client->node);
+ free(client);
+}
+
+void server_accept(void)
+{
+ struct client *client;
+ struct sockaddr saddr;
+ socklen_t saddr_size = sizeof(saddr);
+ int fd;
+
+ fd = accept(server_socket, &saddr, &saddr_size);
+ if (fd == -1)
+ return;
+
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+
+ client = xnew(struct client, 1);
+ client->fd = fd;
+ client->authenticated = 0;
+ list_add_tail(&client->node, &client_head);
+}
+
+void server_serve(struct client *client)
+{
+ /* unix connection is secure, other insecure */
+ run_only_safe_commands = addr.sa.sa_family != AF_UNIX;
+ read_commands(client);
+ run_only_safe_commands = 0;
+}
+
+void server_init(char *address)
+{
+ const char *port = STRINGIZE(DEFAULT_PORT);
+ size_t addrlen;
+
+ if (strchr(address, '/')) {
+ addr.sa.sa_family = AF_UNIX;
+ strncpy(addr.un.sun_path, address, sizeof(addr.un.sun_path) - 1);
+
+ addrlen = sizeof(struct sockaddr_un);
+ } else {
+ const struct addrinfo hints = {
+ .ai_socktype = SOCK_STREAM
+ };
+ struct addrinfo *result;
+ char *s = strrchr(address, ':');
+ int rc;
+
+ if (s) {
+ *s++ = 0;
+ port = s;
+ }
+
+ rc = getaddrinfo(address, port, &hints, &result);
+ if (rc != 0)
+ die("getaddrinfo: %s\n", gai_strerror(rc));
+ memcpy(&addr.sa, result->ai_addr, result->ai_addrlen);
+ addrlen = result->ai_addrlen;
+ freeaddrinfo(result);
+ }
+
+ server_socket = socket(addr.sa.sa_family, SOCK_STREAM, 0);
+ if (server_socket == -1)
+ die_errno("socket");
+
+ if (bind(server_socket, &addr.sa, addrlen) == -1) {
+ int sock;
+
+ if (errno != EADDRINUSE)
+ die_errno("bind");
+
+ /* address already in use */
+ if (addr.sa.sa_family != AF_UNIX)
+ die("cmus is already listening on %s:%s\n", address, port);
+
+ /* try to connect to server */
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock == -1)
+ die_errno("socket");
+
+ if (connect(sock, &addr.sa, addrlen) == -1) {
+ if (errno != ENOENT && errno != ECONNREFUSED)
+ die_errno("connect");
+
+ /* server not running => dead socket */
+
+ /* try to remove dead socket */
+ if (unlink(addr.un.sun_path) == -1 && errno != ENOENT)
+ die_errno("unlink");
+ if (bind(server_socket, &addr.sa, addrlen) == -1)
+ die_errno("bind");
+ } else {
+ /* server already running */
+ die("cmus is already listening on socket %s\n", address);
+ }
+ close(sock);
+ }
+
+ if (listen(server_socket, MAX_CLIENTS) == -1)
+ die_errno("listen");
+}
+
+void server_exit(void)
+{
+ close(server_socket);
+ if (addr.sa.sa_family == AF_UNIX)
+ unlink(addr.un.sun_path);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SERVER_H
+#define _SERVER_H
+
+#include "list.h"
+
+struct client {
+ struct list_head node;
+ int fd;
+ unsigned int authenticated : 1;
+};
+
+extern int server_socket;
+extern struct list_head client_head;
+
+void server_init(char *address);
+void server_exit(void);
+void server_accept(void);
+void server_serve(struct client *client);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SF_H
+#define _SF_H
+
+/*
+ * 0 1 big_endian 0-1
+ * 1 1 is_signed 0-1
+ * 2 1 unused 0
+ * 3-5 3 bits >> 3 0-7 (* 8 = 0-56)
+ * 6-23 18 rate 0-262143
+ * 24-31 8 channels 0-255
+ */
+typedef unsigned int sample_format_t;
+
+#define SF_BIGENDIAN_MASK 0x00000001
+#define SF_SIGNED_MASK 0x00000002
+#define SF_BITS_MASK 0x00000038
+#define SF_RATE_MASK 0x00ffffc0
+#define SF_CHANNELS_MASK 0xff000000
+
+#define SF_BIGENDIAN_SHIFT 0
+#define SF_SIGNED_SHIFT 1
+#define SF_BITS_SHIFT 0
+#define SF_RATE_SHIFT 6
+#define SF_CHANNELS_SHIFT 24
+
+#define sf_get_bigendian(sf) (((sf) & SF_BIGENDIAN_MASK) >> SF_BIGENDIAN_SHIFT)
+#define sf_get_signed(sf) (((sf) & SF_SIGNED_MASK ) >> SF_SIGNED_SHIFT)
+#define sf_get_bits(sf) (((sf) & SF_BITS_MASK ) >> SF_BITS_SHIFT)
+#define sf_get_rate(sf) (((sf) & SF_RATE_MASK ) >> SF_RATE_SHIFT)
+#define sf_get_channels(sf) (((sf) & SF_CHANNELS_MASK ) >> SF_CHANNELS_SHIFT)
+
+#define sf_bigendian(val) (((val) << SF_BIGENDIAN_SHIFT) & SF_BIGENDIAN_MASK)
+#define sf_signed(val) (((val) << SF_SIGNED_SHIFT ) & SF_SIGNED_MASK)
+#define sf_bits(val) (((val) << SF_BITS_SHIFT ) & SF_BITS_MASK)
+#define sf_rate(val) (((val) << SF_RATE_SHIFT ) & SF_RATE_MASK)
+#define sf_channels(val) (((val) << SF_CHANNELS_SHIFT ) & SF_CHANNELS_MASK)
+
+#define sf_get_sample_size(sf) (sf_get_bits((sf)) >> 3)
+#define sf_get_frame_size(sf) (sf_get_sample_size((sf)) * sf_get_channels((sf)))
+#define sf_get_second_size(sf) (sf_get_rate((sf)) * sf_get_frame_size((sf)))
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "spawn.h"
+#include "file.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+int spawn(char *argv[], int *status, int do_wait)
+{
+ pid_t pid;
+ int err_pipe[2];
+
+ if (pipe(err_pipe) == -1)
+ return -1;
+
+ pid = fork();
+ if (pid == -1) {
+ /* error */
+ return -1;
+ } else if (pid == 0) {
+ /* child */
+ int dev_null, err, i;
+
+ /* create grandchild and exit child to avoid zombie processes */
+ if (!do_wait) {
+ switch (fork()) {
+ case 0:
+ /* grandchild */
+ break;
+ case -1:
+ /* error */
+ _exit(127);
+ default:
+ /* parent of grandchild */
+ _exit(0);
+ }
+ }
+
+ close(err_pipe[0]);
+ fcntl(err_pipe[1], F_SETFD, FD_CLOEXEC);
+
+ /* redirect stdout and stderr to /dev/null if possible */
+ dev_null = open("/dev/null", O_WRONLY);
+ if (dev_null != -1) {
+ dup2(dev_null, 1);
+ dup2(dev_null, 2);
+ }
+
+ /* not interactive, close stdin */
+ close(0);
+
+ /* close unused fds */
+ for (i = 3; i < 30; i++)
+ close(i);
+
+ execvp(argv[0], argv);
+
+ /* error */
+ err = errno;
+ write_all(err_pipe[1], &err, sizeof(int));
+ exit(1);
+ } else {
+ /* parent */
+ int rc, errno_save, child_errno, tmp;
+
+ close(err_pipe[1]);
+ rc = read_all(err_pipe[0], &child_errno, sizeof(int));
+ errno_save = errno;
+ close(err_pipe[0]);
+
+ if (!do_wait)
+ status = &tmp;
+ waitpid(pid, status, 0);
+
+ if (rc == -1) {
+ errno = errno_save;
+ return -1;
+ }
+ if (rc == sizeof(int)) {
+ errno = child_errno;
+ return -1;
+ }
+ if (rc != 0) {
+ errno = EMSGSIZE;
+ return -1;
+ }
+ return 0;
+ }
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SPAWN_H
+#define _SPAWN_H
+
+int spawn(char *argv[], int *status, int do_wait);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * sun.c by alex <pukpuk@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/audioio.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "op.h"
+#include "sf.h"
+#include "xmalloc.h"
+
+static sample_format_t sun_sf;
+static int sun_fd = -1;
+
+static char *sun_audio_device = NULL;
+
+static int sun_reset(void)
+{
+ if (ioctl(sun_fd, AUDIO_FLUSH, NULL) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int sun_set_sf(sample_format_t sf)
+{
+ struct audio_info ainf;
+
+ AUDIO_INITINFO(&ainf);
+
+ sun_reset();
+ sun_sf = sf;
+
+ ainf.play.channels = sf_get_channels(sun_sf);
+ ainf.play.sample_rate = sf_get_rate(sun_sf);
+ ainf.play.pause = 0;
+ ainf.mode = AUMODE_PLAY;
+
+ switch (sf_get_bits(sun_sf)) {
+ case 16:
+ ainf.play.precision = 16;
+ if (sf_get_signed(sun_sf)) {
+ if (sf_get_bigendian(sun_sf))
+ ainf.play.encoding = AUDIO_ENCODING_SLINEAR_BE;
+ else
+ ainf.play.encoding = AUDIO_ENCODING_SLINEAR_LE;
+ } else {
+ if (sf_get_bigendian(sun_sf))
+ ainf.play.encoding = AUDIO_ENCODING_ULINEAR_BE;
+ else
+ ainf.play.encoding = AUDIO_ENCODING_ULINEAR_LE;
+ }
+ break;
+ case 8:
+ ainf.play.precision = 8;
+ if (sf_get_signed(sun_sf))
+ ainf.play.encoding = AUDIO_ENCODING_SLINEAR;
+ else
+ ainf.play.encoding = AUDIO_ENCODING_ULINEAR;
+ break;
+ default:
+ return -1;
+ }
+
+ if (ioctl(sun_fd, AUDIO_SETINFO, &ainf) == -1)
+ return -1;
+
+ if (ioctl(sun_fd, AUDIO_GETINFO, &ainf) == -1)
+ return -1;
+
+ /* FIXME: check if sample rate is supported */
+ return 0;
+}
+
+static int sun_device_exists(const char *dev)
+{
+ struct stat s;
+
+ if (stat(dev, &s))
+ return 0;
+ return 1;
+}
+
+static int sun_init(void)
+{
+ const char *audio_dev = "/dev/audio";
+
+ if (sun_audio_device != NULL) {
+ if (sun_device_exists(sun_audio_device))
+ return 0;
+ free(sun_audio_device);
+ sun_audio_device = NULL;
+ return -1;
+ }
+ if (sun_device_exists(audio_dev)) {
+ sun_audio_device = xstrdup(audio_dev);
+ return 0;
+ }
+
+ return -1;
+}
+
+static int sun_exit(void)
+{
+ if (sun_audio_device != NULL) {
+ free(sun_audio_device);
+ sun_audio_device = NULL;
+ }
+
+ return 0;
+}
+
+static int sun_close(void)
+{
+ if (sun_fd != -1) {
+ close(sun_fd);
+ sun_fd = -1;
+ }
+
+ return 0;
+}
+
+static int sun_open(sample_format_t sf, const channel_position_t *channel_map)
+{
+ sun_fd = open(sun_audio_device, O_WRONLY);
+ if (sun_fd == -1)
+ return -1;
+ if (sun_set_sf(sf) == -1) {
+ sun_close();
+ return -1;
+ }
+
+ return 0;
+}
+
+static int sun_write(const char *buf, int cnt)
+{
+ const char *t;
+
+ cnt /= 4;
+ cnt *= 4;
+ t = buf;
+ while (cnt > 0) {
+ int rc = write(sun_fd, buf, cnt);
+ if (rc == -1) {
+ if (errno == EINTR)
+ continue;
+ else
+ return rc;
+ }
+ buf += rc;
+ cnt -= rc;
+ }
+
+ return (buf - t);
+}
+
+static int sun_pause(void)
+{
+ struct audio_info ainf;
+
+ AUDIO_INITINFO(&ainf);
+
+ ainf.play.pause = 1;
+ if (ioctl(sun_fd, AUDIO_SETINFO, &ainf) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int sun_unpause(void)
+{
+ struct audio_info ainf;
+
+ AUDIO_INITINFO(&ainf);
+
+ ainf.play.pause = 0;
+ if (ioctl(sun_fd, AUDIO_SETINFO, &ainf) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int sun_buffer_space(void)
+{
+ struct audio_info ainf;
+ int sp;
+
+ AUDIO_INITINFO(&ainf);
+
+ if (ioctl(sun_fd, AUDIO_GETINFO, &ainf) == -1)
+ return -1;
+ sp = ainf.play.buffer_size;
+
+ return sp;
+}
+
+static int op_sun_set_option(int key, const char *val)
+{
+ switch (key) {
+ case 0:
+ free(sun_audio_device);
+ sun_audio_device = xstrdup(val);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+
+ return 0;
+}
+
+static int op_sun_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ if (sun_audio_device)
+ *val = xstrdup(sun_audio_device);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+
+ return 0;
+}
+
+const struct output_plugin_ops op_pcm_ops = {
+ .init = sun_init,
+ .exit = sun_exit,
+ .open = sun_open,
+ .close = sun_close,
+ .write = sun_write,
+ .pause = sun_pause,
+ .unpause = sun_unpause,
+ .buffer_space = sun_buffer_space,
+ .set_option = op_sun_set_option,
+ .get_option = op_sun_get_option
+};
+
+const char * const op_pcm_options[] = {
+ "device",
+ NULL
+};
+
+const int op_priority = 0;
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tabexp.h"
+#include "xmalloc.h"
+#include "xstrjoin.h"
+#include "debug.h"
+
+#include <stdlib.h>
+
+struct tabexp tabexp = {
+ .head = NULL,
+ .tails = NULL,
+ .count = 0
+};
+
+char *tabexp_expand(const char *src, void (*load_matches)(const char *src), int direction)
+{
+ static int idx = -1;
+ char *expanded;
+
+ if (tabexp.tails == NULL) {
+ load_matches(src);
+ if (tabexp.tails == NULL) {
+ BUG_ON(tabexp.head != NULL);
+ return NULL;
+ }
+ BUG_ON(tabexp.head == NULL);
+ idx = -1;
+ }
+ idx += direction;
+
+ if (idx >= tabexp.count)
+ idx = 0;
+ else if (idx < 0)
+ idx = tabexp.count - 1;
+
+ expanded = xstrjoin(tabexp.head, tabexp.tails[idx]);
+ if (tabexp.count == 1)
+ tabexp_reset();
+ return expanded;
+}
+
+void tabexp_reset(void)
+{
+ int i;
+ for (i = 0; i < tabexp.count; i++)
+ free(tabexp.tails[i]);
+ free(tabexp.tails);
+ free(tabexp.head);
+ tabexp.tails = NULL;
+ tabexp.head = NULL;
+ tabexp.count = 0;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TABEXP_H
+#define _TABEXP_H
+
+struct tabexp {
+ char *head;
+ char **tails;
+ int count;
+};
+
+extern struct tabexp tabexp;
+
+/* return expanded src or NULL */
+char *tabexp_expand(const char *src, void (*load_matches)(const char *src), int direction);
+
+void tabexp_reset(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tabexp_file.h"
+#include "tabexp.h"
+#include "load_dir.h"
+#include "misc.h"
+#include "xmalloc.h"
+#include "xstrjoin.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <dirent.h>
+
+static char *get_home(const char *user)
+{
+ struct passwd *passwd;
+ char *home;
+ int len;
+
+ if (user[0] == 0) {
+ passwd = getpwuid(getuid());
+ } else {
+ passwd = getpwnam(user);
+ }
+ if (passwd == NULL)
+ return NULL;
+ len = strlen(passwd->pw_dir);
+ home = xnew(char, len + 2);
+ memcpy(home, passwd->pw_dir, len);
+ home[len] = '/';
+ home[len + 1] = 0;
+ return home;
+}
+
+static char *get_full_dir_name(const char *dir)
+{
+ char *full;
+
+ if (dir[0] == 0) {
+ full = xstrdup("./");
+ } else if (dir[0] == '~') {
+ char *first_slash, *tmp, *home;
+
+ first_slash = strchr(dir, '/');
+ tmp = xstrndup(dir, first_slash - dir);
+ home = get_home(tmp + 1);
+ free(tmp);
+ if (home == NULL)
+ return NULL;
+ full = xstrjoin(home, first_slash);
+ free(home);
+ } else {
+ full = xstrdup(dir);
+ }
+ return full;
+}
+
+static void load_dir(struct ptr_array *array,
+ const char *dirname, const char *start,
+ int (*filter)(const char *, const struct stat *))
+{
+ int start_len = strlen(start);
+ struct directory dir;
+ char *full_dir_name;
+ const char *name;
+
+ full_dir_name = get_full_dir_name(dirname);
+ if (!full_dir_name)
+ return;
+
+ if (dir_open(&dir, full_dir_name))
+ goto out;
+
+ while ((name = dir_read(&dir))) {
+ char *str;
+
+ if (!start_len) {
+ if (name[0] == '.')
+ continue;
+ } else {
+ if (strncmp(name, start, start_len))
+ continue;
+ }
+
+ if (!filter(name, &dir.st))
+ continue;
+
+ if (S_ISDIR(dir.st.st_mode)) {
+ int len = strlen(name);
+
+ str = xnew(char, len + 2);
+ memcpy(str, name, len);
+ str[len++] = '/';
+ str[len] = 0;
+ } else {
+ str = xstrdup(name);
+ }
+ ptr_array_add(array, str);
+ }
+ dir_close(&dir);
+out:
+ free(full_dir_name);
+}
+
+/*
+ * load all directory entries from directory 'dir' starting with 'start' and
+ * filtered with 'filter'
+ */
+static void tabexp_load_dir(const char *dirname, const char *start,
+ int (*filter)(const char *, const struct stat *))
+{
+ PTR_ARRAY(array);
+
+ /* tabexp is reseted */
+ load_dir(&array, dirname, start, filter);
+
+ if (array.count) {
+ ptr_array_sort(&array, strptrcmp);
+
+ tabexp.head = xstrdup(dirname);
+ tabexp.tails = array.ptrs;
+ tabexp.count = array.count;
+ }
+}
+
+static void tabexp_load_env_path(const char *env_path, const char *start,
+ int (*filter)(const char *, const struct stat *))
+{
+ char *path = xstrdup(env_path);
+ PTR_ARRAY(array);
+ char cwd[1024];
+ char *p = path, *n;
+
+ /* tabexp is reseted */
+ do {
+ n = strchr(p, ':');
+ if (n)
+ *n = '\0';
+ if (strcmp(p, "") == 0 && getcwd(cwd, sizeof(cwd)))
+ p = cwd;
+ load_dir(&array, p, start, filter);
+ p = n + 1;
+ } while (n);
+
+ if (array.count) {
+ ptr_array_sort(&array, strptrcoll);
+ ptr_array_unique(&array, strptrcmp);
+
+ tabexp.head = xstrdup("");
+ tabexp.tails = array.ptrs;
+ tabexp.count = array.count;
+ }
+
+ free(path);
+}
+
+void expand_files_and_dirs(const char *src,
+ int (*filter)(const char *name, const struct stat *s))
+{
+ char *slash;
+
+ /* split src to dir and file */
+ slash = strrchr(src, '/');
+ if (slash) {
+ char *dir;
+ const char *file;
+
+ /* split */
+ dir = xstrndup(src, slash - src + 1);
+ file = slash + 1;
+ /* get all dentries starting with file from dir */
+ tabexp_load_dir(dir, file, filter);
+ free(dir);
+ } else {
+ if (src[0] == '~') {
+ char *home = get_home(src + 1);
+
+ if (home) {
+ tabexp.head = xstrdup("");
+ tabexp.tails = xnew(char *, 1);
+ tabexp.tails[0] = home;
+ tabexp.count = 1;
+ }
+ } else {
+ tabexp_load_dir("", src, filter);
+ }
+ }
+}
+
+void expand_env_path(const char *src,
+ int (*filter)(const char *name, const struct stat *s))
+{
+ const char *env_path = getenv("PATH");
+
+ if (!env_path || strcmp(env_path, "") == 0)
+ return;
+
+ tabexp_load_env_path(env_path, src, filter);
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TABEXP_FILE_H
+#define _TABEXP_FILE_H
+
+#include <sys/stat.h>
+
+void expand_files_and_dirs(const char *src,
+ int (*filter)(const char *name, const struct stat *s));
+void expand_env_path(const char *src,
+ int (*filter)(const char *name, const struct stat *s));
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "track.h"
+#include "iter.h"
+#include "search_mode.h"
+#include "window.h"
+#include "options.h"
+#include "uchar.h"
+#include "xmalloc.h"
+
+#include <string.h>
+
+void simple_track_init(struct simple_track *track, struct track_info *ti)
+{
+ track->info = ti;
+ track->marked = 0;
+ RB_CLEAR_NODE(&track->tree_node);
+}
+
+struct simple_track *simple_track_new(struct track_info *ti)
+{
+ struct simple_track *t = xnew(struct simple_track, 1);
+
+ track_info_ref(ti);
+ simple_track_init(t, ti);
+ return t;
+}
+
+GENERIC_ITER_PREV(simple_track_get_prev, struct simple_track, node)
+GENERIC_ITER_NEXT(simple_track_get_next, struct simple_track, node)
+
+int simple_track_search_get_current(void *data, struct iter *iter)
+{
+ return window_get_sel(data, iter);
+}
+
+int simple_track_search_matches(void *data, struct iter *iter, const char *text)
+{
+ unsigned int flags = TI_MATCH_TITLE;
+ struct simple_track *track = iter_to_simple_track(iter);
+
+ if (!search_restricted)
+ flags |= TI_MATCH_ARTIST | TI_MATCH_ALBUM | TI_MATCH_ALBUMARTIST;
+
+ if (!track_info_matches(track->info, text, flags))
+ return 0;
+
+ window_set_sel(data, iter);
+ return 1;
+}
+
+struct shuffle_track *shuffle_list_get_next(struct rb_root *root, struct shuffle_track *cur,
+ int (*filter)(const struct simple_track *))
+{
+ struct rb_node *node;
+
+ if (!cur)
+ return tree_node_to_shuffle_track(rb_first(root));
+
+ node = rb_next(&cur->tree_node);
+again:
+ while (node) {
+ struct shuffle_track *track = tree_node_to_shuffle_track(node);
+
+ if (filter((struct simple_track *)track))
+ return track;
+ node = rb_next(node);
+ }
+ if (repeat) {
+ if (auto_reshuffle)
+ shuffle_list_reshuffle(root);
+ node = rb_first(root);
+ goto again;
+ }
+ return NULL;
+}
+
+struct shuffle_track *shuffle_list_get_prev(struct rb_root *root, struct shuffle_track *cur,
+ int (*filter)(const struct simple_track *))
+{
+ struct rb_node *node;
+
+ if (!cur)
+ return tree_node_to_shuffle_track(rb_last(root));
+
+ node = rb_prev(&cur->tree_node);
+again:
+ while (node) {
+ struct shuffle_track *track = tree_node_to_shuffle_track(node);
+
+ if (filter((struct simple_track *)track))
+ return track;
+ node = rb_prev(node);
+ }
+ if (repeat) {
+ if (auto_reshuffle)
+ shuffle_list_reshuffle(root);
+ node = rb_last(root);
+ goto again;
+ }
+ return NULL;
+}
+
+struct simple_track *simple_list_get_next(struct list_head *head, struct simple_track *cur,
+ int (*filter)(const struct simple_track *))
+{
+ struct list_head *item;
+
+ if (cur == NULL)
+ return to_simple_track(head->next);
+
+ item = cur->node.next;
+again:
+ while (item != head) {
+ struct simple_track *track = to_simple_track(item);
+
+ if (filter(track))
+ return track;
+ item = item->next;
+ }
+ item = head->next;
+ if (repeat)
+ goto again;
+ return NULL;
+}
+
+struct simple_track *simple_list_get_prev(struct list_head *head, struct simple_track *cur,
+ int (*filter)(const struct simple_track *))
+{
+ struct list_head *item;
+
+ if (cur == NULL)
+ return to_simple_track(head->next);
+
+ item = cur->node.prev;
+again:
+ while (item != head) {
+ struct simple_track *track = to_simple_track(item);
+
+ if (filter(track))
+ return track;
+ item = item->prev;
+ }
+ item = head->prev;
+ if (repeat)
+ goto again;
+ return NULL;
+}
+
+void sorted_list_add_track(struct list_head *head, struct rb_root *tree_root, struct simple_track *track,
+ const sort_key_t *keys, int tiebreak)
+{
+ struct rb_node **new = &(tree_root->rb_node), *parent = NULL, *curr, *next;
+ struct list_head *node;
+ int result = 0;
+
+ /* try to locate track in tree */
+ while (*new) {
+ const struct simple_track *t = tree_node_to_simple_track(*new);
+ result = track_info_cmp(track->info, t->info, keys);
+
+ parent = *new;
+ if (result < 0)
+ new = &(parent->rb_left);
+ else if (result > 0)
+ new = &(parent->rb_right);
+ else
+ break;
+ }
+
+ /* duplicate is present in the tree */
+ if (parent && result == 0) {
+ if (tiebreak < 0) {
+ node = &(tree_node_to_simple_track(parent)->node);
+ rb_replace_node(parent, &track->tree_node, tree_root);
+ RB_CLEAR_NODE(parent);
+ } else {
+ next = rb_next(parent);
+ node = next ? &(tree_node_to_simple_track(next)->node) : head;
+ }
+ } else {
+ rb_link_node(&track->tree_node, parent, new);
+ curr = *new;
+ rb_insert_color(&track->tree_node, tree_root);
+ if (result < 0) {
+ node = &(tree_node_to_simple_track(parent)->node);
+ } else if (result > 0) {
+ next = rb_next(curr);
+ node = next ? &(tree_node_to_simple_track(next)->node) : head;
+ } else {
+ /* rbtree was empty, just add after list head */
+ node = head;
+ }
+ }
+ list_add(&track->node, node->prev);
+}
+
+void sorted_list_remove_track(struct list_head *head, struct rb_root *tree_root, struct simple_track *track)
+{
+ struct simple_track *next_track;
+ struct rb_node *tree_next;
+
+ if (!RB_EMPTY_NODE(&track->tree_node)) {
+ next_track = (track->node.next != head) ? to_simple_track(track->node.next) : NULL;
+ tree_next = rb_next(&track->tree_node);
+
+ if (next_track && (!tree_next || tree_node_to_simple_track(tree_next) != next_track)) {
+ rb_replace_node(&track->tree_node, &next_track->tree_node, tree_root);
+ RB_CLEAR_NODE(&track->tree_node);
+ } else
+ rb_erase(&track->tree_node, tree_root);
+ }
+ list_del(&track->node);
+}
+
+void sorted_list_rebuild(struct list_head *head, struct rb_root *tree_root, const sort_key_t *keys)
+{
+ struct list_head *item, *tmp;
+ struct rb_root tmp_tree = RB_ROOT;
+ LIST_HEAD(tmp_head);
+
+ list_for_each_safe(item, tmp, head) {
+ struct simple_track *track = to_simple_track(item);
+ sorted_list_remove_track(head, tree_root, track);
+ sorted_list_add_track(&tmp_head, &tmp_tree, track, keys, 0);
+ }
+ tree_root->rb_node = tmp_tree.rb_node;
+ __list_add(head, tmp_head.prev, tmp_head.next);
+}
+
+static int compare_rand(const struct rb_node *a, const struct rb_node *b)
+{
+ struct shuffle_track *tr_a = tree_node_to_shuffle_track(a);
+ struct shuffle_track *tr_b = tree_node_to_shuffle_track(b);
+
+ if (tr_a->rand < tr_b->rand)
+ return -1;
+ if (tr_a->rand > tr_b->rand)
+ return +1;
+
+ return 0;
+}
+
+static void shuffle_track_init(struct shuffle_track *track)
+{
+ track->rand = rand() / ((double) RAND_MAX + 1);
+}
+
+void shuffle_list_add(struct shuffle_track *track, struct rb_root *tree_root)
+{
+ struct rb_node **new = &(tree_root->rb_node), *parent = NULL;
+
+ shuffle_track_init(track);
+
+ /* try to locate track in tree */
+ while (*new) {
+ int result = compare_rand(&track->tree_node, *new);
+
+ parent = *new;
+ if (result < 0)
+ new = &((*new)->rb_left);
+ else if (result > 0)
+ new = &((*new)->rb_right);
+ else {
+ /* very unlikely, try again! */
+ shuffle_list_add(track, tree_root);
+ return;
+ }
+ }
+
+ rb_link_node(&track->tree_node, parent, new);
+ rb_insert_color(&track->tree_node, tree_root);
+}
+
+void shuffle_list_reshuffle(struct rb_root *tree_root)
+{
+ struct rb_node *node, *tmp;
+ struct rb_root tmptree = RB_ROOT;
+
+ rb_for_each_safe(node, tmp, tree_root) {
+ struct shuffle_track *track = tree_node_to_shuffle_track(node);
+ rb_erase(node, tree_root);
+ shuffle_list_add(track, &tmptree);
+ }
+
+ tree_root->rb_node = tmptree.rb_node;
+}
+
+/* expensive */
+void list_add_rand(struct list_head *head, struct list_head *node, int nr)
+{
+ struct list_head *item;
+ int pos;
+
+ pos = rand() % (nr + 1);
+ item = head;
+ if (pos <= nr / 2) {
+ while (pos) {
+ item = item->next;
+ pos--;
+ }
+ /* add after item */
+ list_add(node, item);
+ } else {
+ pos = nr - pos;
+ while (pos) {
+ item = item->prev;
+ pos--;
+ }
+ /* add before item */
+ list_add_tail(node, item);
+ }
+}
+
+int simple_list_for_each_marked(struct list_head *head,
+ int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
+{
+ struct simple_track *t;
+ int rc = 0;
+
+ if (reverse) {
+ list_for_each_entry_reverse(t, head, node) {
+ if (t->marked) {
+ rc = cb(data, t->info);
+ if (rc)
+ break;
+ }
+ }
+ } else {
+ list_for_each_entry(t, head, node) {
+ if (t->marked) {
+ rc = cb(data, t->info);
+ if (rc)
+ break;
+ }
+ }
+ }
+ return rc;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TRACK_H
+#define TRACK_H
+
+#include "list.h"
+#include "rbtree.h"
+#include "iter.h"
+#include "track_info.h"
+
+struct simple_track {
+ struct list_head node;
+ struct rb_node tree_node;
+ struct track_info *info;
+ unsigned int marked : 1;
+};
+
+struct shuffle_track {
+ struct simple_track simple_track;
+ struct rb_node tree_node;
+ double rand;
+};
+
+static inline struct track_info *shuffle_track_info(const struct shuffle_track *track)
+{
+ return ((struct simple_track *)track)->info;
+}
+
+static inline struct simple_track *to_simple_track(const struct list_head *item)
+{
+ return container_of(item, struct simple_track, node);
+}
+
+static inline struct simple_track *iter_to_simple_track(const struct iter *iter)
+{
+ return iter->data1;
+}
+
+static inline struct simple_track *tree_node_to_simple_track(const struct rb_node *node)
+{
+ return container_of(node, struct simple_track, tree_node);
+}
+
+static inline struct shuffle_track *tree_node_to_shuffle_track(const struct rb_node *node)
+{
+ return container_of(node, struct shuffle_track, tree_node);
+}
+
+/* NOTE: does not ref ti */
+void simple_track_init(struct simple_track *track, struct track_info *ti);
+
+/* refs ti */
+struct simple_track *simple_track_new(struct track_info *ti);
+
+int simple_track_get_prev(struct iter *);
+int simple_track_get_next(struct iter *);
+
+/* data is window */
+int simple_track_search_get_current(void *data, struct iter *iter);
+int simple_track_search_matches(void *data, struct iter *iter, const char *text);
+
+struct shuffle_track *shuffle_list_get_next(struct rb_root *root, struct shuffle_track *cur,
+ int (*filter)(const struct simple_track *));
+
+struct shuffle_track *shuffle_list_get_prev(struct rb_root *root, struct shuffle_track *cur,
+ int (*filter)(const struct simple_track *));
+
+struct simple_track *simple_list_get_next(struct list_head *head, struct simple_track *cur,
+ int (*filter)(const struct simple_track *));
+
+struct simple_track *simple_list_get_prev(struct list_head *head, struct simple_track *cur,
+ int (*filter)(const struct simple_track *));
+
+void sorted_list_add_track(struct list_head *head, struct rb_root *tree_root, struct simple_track *track,
+ const sort_key_t *keys, int tiebreak);
+void sorted_list_remove_track(struct list_head *head, struct rb_root *tree_root, struct simple_track *track);
+void sorted_list_rebuild(struct list_head *head, struct rb_root *tree_root, const sort_key_t *keys);
+
+void list_add_rand(struct list_head *head, struct list_head *node, int nr);
+
+int simple_list_for_each_marked(struct list_head *head,
+ int (*cb)(void *data, struct track_info *ti), void *data, int reverse);
+
+void shuffle_list_add(struct shuffle_track *track, struct rb_root *tree_root);
+void shuffle_list_reshuffle(struct rb_root *tree_root);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "track_info.h"
+#include "comment.h"
+#include "uchar.h"
+#include "u_collate.h"
+#include "misc.h"
+#include "xmalloc.h"
+#include "utils.h"
+#include "debug.h"
+#include "path.h"
+
+#include <string.h>
+#include <math.h>
+
+static void track_info_free(struct track_info *ti)
+{
+ keyvals_free(ti->comments);
+ free(ti->filename);
+ free(ti->codec);
+ free(ti->codec_profile);
+ free(ti->collkey_artist);
+ free(ti->collkey_album);
+ free(ti->collkey_title);
+ free(ti->collkey_genre);
+ free(ti->collkey_comment);
+ free(ti->collkey_albumartist);
+ free(ti);
+}
+
+struct track_info *track_info_new(const char *filename)
+{
+ struct track_info *ti;
+ ti = xnew(struct track_info, 1);
+ ti->filename = xstrdup(filename);
+ ti->ref = 1;
+ ti->comments = NULL;
+ ti->codec = NULL;
+ ti->codec_profile = NULL;
+ return ti;
+}
+
+void track_info_set_comments(struct track_info *ti, struct keyval *comments) {
+ ti->comments = comments;
+ ti->artist = keyvals_get_val(comments, "artist");
+ ti->album = keyvals_get_val(comments, "album");
+ ti->title = keyvals_get_val(comments, "title");
+ ti->tracknumber = comments_get_int(comments, "tracknumber");
+ ti->discnumber = comments_get_int(comments, "discnumber");
+ ti->date = comments_get_date(comments, "date");
+ ti->originaldate = comments_get_date(comments, "originaldate");
+ ti->genre = keyvals_get_val(comments, "genre");
+ ti->comment = keyvals_get_val(comments, "comment");
+ ti->albumartist = comments_get_albumartist(comments);
+ ti->artistsort = comments_get_artistsort(comments);
+ ti->albumsort = keyvals_get_val(comments, "albumsort");
+ ti->is_va_compilation = track_is_va_compilation(comments);
+ ti->media = keyvals_get_val(comments, "media");
+
+ if (ti->artist == NULL && ti->albumartist != NULL) {
+ /* best guess */
+ ti->artist = ti->albumartist;
+ }
+
+ if (track_info_has_tag(ti) && ti->title == NULL) {
+ /* best guess */
+ ti->title = path_basename(ti->filename);
+ }
+
+ ti->rg_track_gain = comments_get_double(comments, "replaygain_track_gain");
+ ti->rg_track_peak = comments_get_double(comments, "replaygain_track_peak");
+ ti->rg_album_gain = comments_get_double(comments, "replaygain_album_gain");
+ ti->rg_album_peak = comments_get_double(comments, "replaygain_album_peak");
+
+ ti->collkey_artist = u_strcasecoll_key0(ti->artist);
+ ti->collkey_album = u_strcasecoll_key0(ti->album);
+ ti->collkey_title = u_strcasecoll_key0(ti->title);
+ ti->collkey_genre = u_strcasecoll_key0(ti->genre);
+ ti->collkey_comment = u_strcasecoll_key0(ti->comment);
+ ti->collkey_albumartist = u_strcasecoll_key0(ti->albumartist);
+}
+
+void track_info_ref(struct track_info *ti)
+{
+ BUG_ON(ti->ref < 1);
+ ti->ref++;
+}
+
+void track_info_unref(struct track_info *ti)
+{
+ BUG_ON(ti->ref < 1);
+ ti->ref--;
+ if (ti->ref == 0)
+ track_info_free(ti);
+}
+
+int track_info_has_tag(const struct track_info *ti)
+{
+ return ti->artist || ti->album || ti->title;
+}
+
+static inline int match_word(const struct track_info *ti, const char *word, unsigned int flags)
+{
+ return ((flags & TI_MATCH_ARTIST) && ti->artist && u_strcasestr_base(ti->artist, word)) ||
+ ((flags & TI_MATCH_ALBUM) && ti->album && u_strcasestr_base(ti->album, word)) ||
+ ((flags & TI_MATCH_TITLE) && ti->title && u_strcasestr_base(ti->title, word)) ||
+ ((flags & TI_MATCH_ALBUMARTIST) && ti->albumartist && u_strcasestr_base(ti->albumartist, word));
+}
+
+static inline int flags_set(const struct track_info *ti, unsigned int flags)
+{
+ return ((flags & TI_MATCH_ARTIST) && ti->artist) ||
+ ((flags & TI_MATCH_ALBUM) && ti->album) ||
+ ((flags & TI_MATCH_TITLE) && ti->title) ||
+ ((flags & TI_MATCH_ALBUMARTIST) && ti->albumartist);
+}
+
+int track_info_matches_full(const struct track_info *ti, const char *text,
+ unsigned int flags, unsigned int exclude_flags, int match_all_words)
+{
+ char **words;
+ int i, matched = 0;
+
+ words = get_words(text);
+ for (i = 0; words[i]; i++) {
+ const char *word = words[i];
+
+ matched = 0;
+ if (flags_set(ti, flags) && match_word(ti, word, flags)) {
+ matched = 1;
+ } else {
+ /* compare with url or filename without path */
+ const char *filename = ti->filename;
+
+ if (!is_url(filename))
+ filename = path_basename(filename);
+
+ if (u_strcasestr_filename(filename, word))
+ matched = 1;
+ }
+
+ if (flags_set(ti, exclude_flags) && match_word(ti, word, exclude_flags))
+ matched = 0;
+
+ if (match_all_words ? !matched : matched)
+ break;
+
+ }
+ free_str_array(words);
+ return matched;
+}
+
+int track_info_matches(const struct track_info *ti, const char *text, unsigned int flags)
+{
+ return track_info_matches_full(ti, text, flags, 0, 1);
+}
+
+static int doublecmp0(double a, double b)
+{
+ double x;
+ /* fast check for NaN */
+ int r = (b != b) - (a != a);
+ if (r)
+ return r;
+ x = a - b;
+ return (x > 0) - (x < 0);
+}
+
+/* this function gets called *alot*, it must be very fast */
+int track_info_cmp(const struct track_info *a, const struct track_info *b, const sort_key_t *keys)
+{
+ int i, res = 0;
+
+ for (i = 0; keys[i] != SORT_INVALID; i++) {
+ sort_key_t key = keys[i];
+ const char *av, *bv;
+
+ switch (key) {
+ case SORT_TRACKNUMBER:
+ case SORT_DISCNUMBER:
+ case SORT_DATE:
+ case SORT_ORIGINALDATE:
+ res = getentry(a, key, int) - getentry(b, key, int);
+ break;
+ case SORT_FILEMTIME:
+ res = a->mtime - b->mtime;
+ break;
+ case SORT_FILENAME:
+ /* NOTE: filenames are not necessarily UTF-8 */
+ res = strcoll(a->filename, b->filename);
+ break;
+ case SORT_RG_TRACK_GAIN:
+ case SORT_RG_TRACK_PEAK:
+ case SORT_RG_ALBUM_GAIN:
+ case SORT_RG_ALBUM_PEAK:
+ res = doublecmp0(getentry(a, key, double), getentry(b, key, double));
+ break;
+ case SORT_BITRATE:
+ res = getentry(a, key, long) - getentry(b, key, long);
+ break;
+ default:
+ av = getentry(a, key, const char *);
+ bv = getentry(b, key, const char *);
+ res = strcmp0(av, bv);
+ break;
+ }
+
+ if (res)
+ break;
+ }
+ return res;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _TRACK_INFO_H
+#define _TRACK_INFO_H
+
+#include <time.h>
+#include <stddef.h>
+
+struct track_info {
+ struct keyval *comments;
+
+ // next track_info in the hash table (cache.c)
+ struct track_info *next;
+
+ time_t mtime;
+ int duration;
+ long bitrate;
+ char *codec;
+ char *codec_profile;
+ int ref;
+ char *filename;
+
+ int tracknumber;
+ int discnumber;
+ int date;
+ int originaldate;
+ double rg_track_gain;
+ double rg_track_peak;
+ double rg_album_gain;
+ double rg_album_peak;
+ const char *artist;
+ const char *album;
+ const char *title;
+ const char *genre;
+ const char *comment;
+ const char *albumartist;
+ const char *artistsort;
+ const char *albumsort;
+ const char *media;
+
+ char *collkey_artist;
+ char *collkey_album;
+ char *collkey_title;
+ char *collkey_genre;
+ char *collkey_comment;
+ char *collkey_albumartist;
+
+ int is_va_compilation : 1;
+};
+
+typedef size_t sort_key_t;
+
+#define SORT_ARTIST offsetof(struct track_info, collkey_artist)
+#define SORT_ALBUM offsetof(struct track_info, collkey_album)
+#define SORT_TITLE offsetof(struct track_info, collkey_title)
+#define SORT_TRACKNUMBER offsetof(struct track_info, tracknumber)
+#define SORT_DISCNUMBER offsetof(struct track_info, discnumber)
+#define SORT_DATE offsetof(struct track_info, date)
+#define SORT_ORIGINALDATE offsetof(struct track_info, originaldate)
+#define SORT_RG_TRACK_GAIN offsetof(struct track_info, rg_track_gain)
+#define SORT_RG_TRACK_PEAK offsetof(struct track_info, rg_track_peak)
+#define SORT_RG_ALBUM_GAIN offsetof(struct track_info, rg_album_gain)
+#define SORT_RG_ALBUM_PEAK offsetof(struct track_info, rg_album_peak)
+#define SORT_GENRE offsetof(struct track_info, collkey_genre)
+#define SORT_COMMENT offsetof(struct track_info, collkey_comment)
+#define SORT_ALBUMARTIST offsetof(struct track_info, collkey_albumartist)
+#define SORT_FILENAME offsetof(struct track_info, filename)
+#define SORT_FILEMTIME offsetof(struct track_info, mtime)
+#define SORT_BITRATE offsetof(struct track_info, bitrate)
+#define SORT_CODEC offsetof(struct track_info, codec)
+#define SORT_CODEC_PROFILE offsetof(struct track_info, codec_profile)
+#define SORT_MEDIA offsetof(struct track_info, media)
+#define SORT_INVALID ((sort_key_t) (-1))
+
+#define TI_MATCH_ARTIST (1 << 0)
+#define TI_MATCH_ALBUM (1 << 1)
+#define TI_MATCH_TITLE (1 << 2)
+#define TI_MATCH_ALBUMARTIST (1 << 3)
+#define TI_MATCH_ALL (~0)
+
+/* initializes only filename and ref */
+struct track_info *track_info_new(const char *filename);
+void track_info_set_comments(struct track_info *ti, struct keyval *comments);
+
+void track_info_ref(struct track_info *ti);
+void track_info_unref(struct track_info *ti);
+
+/*
+ * returns: 1 if @ti has any of the following tags: artist, album, title
+ * 0 otherwise
+ */
+int track_info_has_tag(const struct track_info *ti);
+
+/*
+ * @flags fields to search in (TI_MATCH_*)
+ *
+ * returns: 1 if all words in @text are found to match defined fields (@flags) in @ti
+ * 0 otherwise
+ */
+int track_info_matches(const struct track_info *ti, const char *text, unsigned int flags);
+
+/*
+ * @flags fields to search in (TI_MATCH_*)
+ * @exclude_flags fields which must not match (TI_MATCH_*)
+ * @match_all_words if true, all words must be found in @ti
+ *
+ * returns: 1 if all/any words in @text are found to match defined fields (@flags) in @ti
+ * 0 otherwise
+ */
+int track_info_matches_full(const struct track_info *ti, const char *text, unsigned int flags,
+ unsigned int exclude_flags, int match_all_words);
+
+int track_info_cmp(const struct track_info *a, const struct track_info *b, const sort_key_t *keys);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "lib.h"
+#include "search_mode.h"
+#include "xmalloc.h"
+#include "utils.h"
+#include "debug.h"
+#include "mergesort.h"
+#include "options.h"
+#include "u_collate.h"
+#include "rbtree.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+
+struct searchable *tree_searchable;
+struct window *lib_tree_win;
+struct window *lib_track_win;
+struct window *lib_cur_win;
+struct rb_root lib_artist_root;
+
+static inline void tree_search_track_to_iter(struct tree_track *track, struct iter *iter)
+{
+ iter->data0 = &lib_artist_root;
+ iter->data1 = track;
+ iter->data2 = NULL;
+}
+
+static inline void album_to_iter(struct album *album, struct iter *iter)
+{
+ iter->data0 = &lib_artist_root;
+ iter->data1 = album->artist;
+ iter->data2 = album;
+}
+
+static inline void artist_to_iter(struct artist *artist, struct iter *iter)
+{
+ iter->data0 = &lib_artist_root;
+ iter->data1 = artist;
+ iter->data2 = NULL;
+}
+
+static inline void tree_track_to_iter(struct tree_track *track, struct iter *iter)
+{
+ iter->data0 = &track->album->track_root;
+ iter->data1 = track;
+ iter->data2 = NULL;
+}
+
+static void tree_set_expand_artist(struct artist *artist, int expand)
+{
+ struct iter sel;
+
+ if (expand) {
+ artist->expanded = 1;
+ } else {
+ /* deselect album, select artist */
+ artist_to_iter(artist, &sel);
+ window_set_sel(lib_tree_win, &sel);
+
+ artist->expanded = 0;
+ lib_cur_win = lib_tree_win;
+ }
+ window_changed(lib_tree_win);
+}
+
+/* tree (search) iterators {{{ */
+static int tree_search_get_prev(struct iter *iter)
+{
+ struct rb_root *root = iter->data0;
+ struct tree_track *track = iter->data1;
+ struct artist *artist;
+ struct album *album;
+
+ BUG_ON(iter->data2);
+ if (root == NULL)
+ return 0;
+ if (track == NULL) {
+ /* head, get last track */
+ if (rb_root_empty(root)) {
+ /* empty, iter points to the head already */
+ return 0;
+ }
+ artist = to_artist(rb_last(root));
+ album = to_album(rb_last(&artist->album_root));
+ iter->data1 = to_tree_track(rb_last(&album->track_root));
+ return 1;
+ }
+ /* prev track */
+ if (rb_prev(&track->tree_node) == NULL || search_restricted) {
+ /* prev album */
+ if (rb_prev(&track->album->tree_node) == NULL) {
+ /* prev artist */
+ if (rb_prev(&track->album->artist->tree_node) == NULL)
+ return 0;
+ artist = to_artist(rb_prev(&track->album->artist->tree_node));
+ album = to_album(rb_last(&artist->album_root));
+ track = to_tree_track(rb_last(&album->track_root));
+ } else {
+ album = to_album(rb_prev(&track->album->tree_node));
+ track = to_tree_track(rb_last(&album->track_root));
+ }
+ } else {
+ track = to_tree_track(rb_prev(&track->tree_node));
+ }
+ iter->data1 = track;
+ return 1;
+}
+
+static int tree_search_get_next(struct iter *iter)
+{
+ struct rb_root *root = iter->data0;
+ struct tree_track *track = iter->data1;
+ struct artist *artist;
+ struct album *album;
+
+ BUG_ON(iter->data2);
+ if (root == NULL)
+ return 0;
+ if (track == NULL) {
+ /* head, get first track */
+ if (rb_root_empty(root)) {
+ /* empty, iter points to the head already */
+ return 0;
+ }
+ artist = to_artist(rb_first(root));
+ album = to_album(rb_first(&artist->album_root));
+ iter->data1 = to_tree_track(rb_first(&album->track_root));
+ return 1;
+ }
+ /* next track */
+ if (rb_next(&track->tree_node) == NULL || search_restricted) {
+ /* next album */
+ if (rb_next(&track->album->tree_node) == NULL) {
+ /* next artist */
+ if (rb_next(&track->album->artist->tree_node) == NULL)
+ return 0;
+ artist = to_artist(rb_next(&track->album->artist->tree_node));
+ album = to_album(rb_first(&artist->album_root));
+ track = to_tree_track(rb_first(&album->track_root));
+ } else {
+ album = to_album(rb_next(&track->album->tree_node));
+ track = to_tree_track(rb_first(&album->track_root));
+ }
+ } else {
+ track = to_tree_track(rb_next(&track->tree_node));
+ }
+ iter->data1 = track;
+ return 1;
+}
+/* }}} */
+
+/* tree window iterators {{{ */
+static int tree_get_prev(struct iter *iter)
+{
+ struct rb_root *root = iter->data0;
+ struct artist *artist = iter->data1;
+ struct album *album = iter->data2;
+
+ BUG_ON(root == NULL);
+ BUG_ON(artist == NULL && album != NULL);
+ if (artist == NULL) {
+ /* head, get last artist and/or album */
+ if (rb_root_empty(root)) {
+ /* empty, iter points to the head already */
+ return 0;
+ }
+ artist = to_artist(rb_last(root));
+ if (artist->expanded) {
+ album = to_album(rb_last(&artist->album_root));
+ } else {
+ album = NULL;
+ }
+ iter->data1 = artist;
+ iter->data2 = album;
+ return 1;
+ }
+ if (artist->expanded && album) {
+ /* prev album */
+ if (rb_prev(&album->tree_node) == NULL) {
+ iter->data2 = NULL;
+ return 1;
+ } else {
+ iter->data2 = to_album(rb_prev(&album->tree_node));
+ return 1;
+ }
+ }
+
+ /* prev artist */
+ if (rb_prev(&artist->tree_node) == NULL) {
+ iter->data1 = NULL;
+ iter->data2 = NULL;
+ return 0;
+ }
+ artist = to_artist(rb_prev(&artist->tree_node));
+ iter->data1 = artist;
+ iter->data2 = NULL;
+ if (artist->expanded) {
+ /* last album */
+ iter->data2 = to_album(rb_last(&artist->album_root));
+ }
+ return 1;
+}
+
+static int tree_get_next(struct iter *iter)
+{
+ struct rb_root *root = iter->data0;
+ struct artist *artist = iter->data1;
+ struct album *album = iter->data2;
+
+ BUG_ON(root == NULL);
+ BUG_ON(artist == NULL && album != NULL);
+ if (artist == NULL) {
+ /* head, get first artist */
+ if (rb_root_empty(root)) {
+ /* empty, iter points to the head already */
+ return 0;
+ }
+ iter->data1 = to_artist(rb_first(root));
+ iter->data2 = NULL;
+ return 1;
+ }
+ if (artist->expanded) {
+ /* next album */
+ if (album == NULL) {
+ /* first album */
+ iter->data2 = to_album(rb_first(&artist->album_root));
+ return 1;
+ }
+ if (rb_next(&album->tree_node) != NULL) {
+ iter->data2 = to_album(rb_next(&album->tree_node));
+ return 1;
+ }
+ }
+
+ /* next artist */
+ if (rb_next(&artist->tree_node) == NULL) {
+ iter->data1 = NULL;
+ iter->data2 = NULL;
+ return 0;
+ }
+ iter->data1 = to_artist(rb_next(&artist->tree_node));
+ iter->data2 = NULL;
+ return 1;
+}
+/* }}} */
+
+static GENERIC_TREE_ITER_PREV(tree_track_get_prev, struct tree_track, tree_node)
+static GENERIC_TREE_ITER_NEXT(tree_track_get_next, struct tree_track, tree_node)
+
+/* search (tree) {{{ */
+static int tree_search_get_current(void *data, struct iter *iter)
+{
+ struct artist *artist;
+ struct album *album;
+ struct tree_track *track;
+ struct iter tmpiter;
+
+ if (rb_root_empty(&lib_artist_root))
+ return 0;
+ if (window_get_sel(lib_track_win, &tmpiter)) {
+ track = iter_to_tree_track(&tmpiter);
+ tree_search_track_to_iter(track, iter);
+ return 1;
+ }
+
+ /* artist not expanded. track_win is empty
+ * set tmp to the first track of the selected artist */
+ window_get_sel(lib_tree_win, &tmpiter);
+ artist = iter_to_artist(&tmpiter);
+ album = to_album(rb_first(&artist->album_root));
+ track = to_tree_track(rb_first(&album->track_root));
+ tree_search_track_to_iter(track, iter);
+ return 1;
+}
+
+static inline struct tree_track *iter_to_tree_search_track(const struct iter *iter)
+{
+ BUG_ON(iter->data0 != &lib_artist_root);
+ return iter->data1;
+}
+
+static int tree_search_matches(void *data, struct iter *iter, const char *text);
+
+static const struct searchable_ops tree_search_ops = {
+ .get_prev = tree_search_get_prev,
+ .get_next = tree_search_get_next,
+ .get_current = tree_search_get_current,
+ .matches = tree_search_matches
+};
+/* search (tree) }}} */
+
+static inline int album_selected(struct album *album)
+{
+ struct iter sel;
+
+ if (window_get_sel(lib_tree_win, &sel))
+ return album == iter_to_album(&sel);
+ return 0;
+}
+
+static void tree_sel_changed(void)
+{
+ struct iter sel;
+ struct album *album;
+
+ window_get_sel(lib_tree_win, &sel);
+ album = iter_to_album(&sel);
+ if (album == NULL) {
+ window_set_empty(lib_track_win);
+ } else {
+ window_set_contents(lib_track_win, &album->track_root);
+ }
+}
+
+static inline void tree_win_get_selected(struct artist **artist, struct album **album)
+{
+ struct iter sel;
+
+ *artist = NULL;
+ *album = NULL;
+ if (window_get_sel(lib_tree_win, &sel)) {
+ *artist = iter_to_artist(&sel);
+ *album = iter_to_album(&sel);
+ }
+}
+
+static char *auto_artist_sort_name(const char *name)
+{
+ const char *name_orig = name;
+ char *buf;
+
+ if (strncasecmp(name, "the ", 4) != 0)
+ return NULL;
+
+ name += 4;
+ while (isspace((int)*name))
+ ++name;
+
+ if (*name == '\0')
+ return NULL;
+
+ buf = xnew(char, strlen(name_orig) + 2);
+ sprintf(buf, "%s, %c%c%c", name, name_orig[0],
+ name_orig[1],
+ name_orig[2]);
+ return buf;
+}
+
+static struct artist *artist_new(const char *name, const char *sort_name, int is_compilation)
+{
+ struct artist *a = xnew(struct artist, 1);
+
+ a->name = xstrdup(name);
+ a->sort_name = sort_name ? xstrdup(sort_name) : NULL;
+ a->auto_sort_name = auto_artist_sort_name(name);
+ a->collkey_name = u_strcasecoll_key(a->name);
+ a->collkey_sort_name = u_strcasecoll_key0(a->sort_name);
+ a->collkey_auto_sort_name = u_strcasecoll_key0(a->auto_sort_name);
+ a->expanded = 0;
+ a->is_compilation = is_compilation;
+ rb_root_init(&a->album_root);
+
+ return a;
+}
+
+static struct artist *artist_copy(const struct artist *artist)
+{
+ return artist_new(artist->name, artist->sort_name, artist->is_compilation);
+}
+
+static void artist_free(struct artist *artist)
+{
+ free(artist->name);
+ free(artist->sort_name);
+ free(artist->auto_sort_name);
+ free(artist->collkey_name);
+ free(artist->collkey_sort_name);
+ free(artist->collkey_auto_sort_name);
+ free(artist);
+}
+
+static struct album *album_new(struct artist *artist, const char *name,
+ const char *sort_name, int date)
+{
+ struct album *album = xnew(struct album, 1);
+
+ album->name = xstrdup(name);
+ album->sort_name = sort_name ? xstrdup(sort_name) : NULL;
+ album->collkey_name = u_strcasecoll_key(name);
+ album->collkey_sort_name = u_strcasecoll_key0(sort_name);
+ album->date = date;
+ rb_root_init(&album->track_root);
+ album->artist = artist;
+
+ return album;
+}
+
+static void album_free(struct album *album)
+{
+ free(album->name);
+ free(album->sort_name);
+ free(album->collkey_name);
+ free(album->collkey_sort_name);
+ free(album);
+}
+
+void tree_init(void)
+{
+ struct iter iter;
+
+ rb_root_init(&lib_artist_root);
+
+ lib_tree_win = window_new(tree_get_prev, tree_get_next);
+ lib_track_win = window_new(tree_track_get_prev, tree_track_get_next);
+ lib_cur_win = lib_tree_win;
+
+ lib_tree_win->sel_changed = tree_sel_changed;
+
+ window_set_empty(lib_track_win);
+ window_set_contents(lib_tree_win, &lib_artist_root);
+
+ iter.data0 = &lib_artist_root;
+ iter.data1 = NULL;
+ iter.data2 = NULL;
+ tree_searchable = searchable_new(NULL, &iter, &tree_search_ops);
+}
+
+struct tree_track *tree_get_selected(void)
+{
+ struct artist *artist;
+ struct album *album;
+ struct tree_track *track;
+ struct iter sel;
+
+ if (rb_root_empty(&lib_artist_root))
+ return NULL;
+
+ tree_win_get_selected(&artist, &album);
+ if (album == NULL) {
+ /* only artist selected, track window is empty
+ * => get first album of the selected artist and first track of that album
+ */
+ album = to_album(rb_first(&artist->album_root));
+ track = to_tree_track(rb_first(&album->track_root));
+ } else {
+ window_get_sel(lib_track_win, &sel);
+ track = iter_to_tree_track(&sel);
+ }
+
+ return track;
+}
+
+struct track_info *tree_set_selected(void)
+{
+ struct track_info *info;
+
+ lib_cur_track = tree_get_selected();
+ if (!lib_cur_track)
+ return NULL;
+
+ lib_tree_win->changed = 1;
+ lib_track_win->changed = 1;
+
+ info = tree_track_info(lib_cur_track);
+ track_info_ref(info);
+ return info;
+}
+
+static int special_name_cmp(const char *a, const char *collkey_a,
+ const char *b, const char *collkey_b)
+{
+ /* keep <Stream> etc. top */
+ int cmp = (*a != '<') - (*b != '<');
+
+ if (cmp)
+ return cmp;
+ return strcmp(collkey_a, collkey_b);
+}
+
+static inline const char *album_sort_collkey(const struct album *a)
+{
+ if (a->sort_name)
+ return a->collkey_sort_name;
+
+ return a->collkey_name;
+}
+
+static int special_album_cmp(const struct album *a, const struct album *b)
+{
+ return special_name_cmp(a->name, album_sort_collkey(a), b->name, album_sort_collkey(b));
+}
+
+static int special_album_cmp_date(const struct album *a, const struct album *b)
+{
+ /* keep <Stream> etc. top */
+ int cmp = (*a->name != '<') - (*b->name != '<');
+ if (cmp)
+ return cmp;
+
+ cmp = a->date - b->date;
+ if (cmp)
+ return cmp;
+
+ return strcmp(album_sort_collkey(a), album_sort_collkey(b));
+}
+
+/* has to follow the same logic as artist_sort_name() */
+static inline const char *artist_sort_collkey(const struct artist *a)
+{
+ if (a->sort_name)
+ return a->collkey_sort_name;
+
+ if (smart_artist_sort && a->auto_sort_name)
+ return a->collkey_auto_sort_name;
+
+ return a->collkey_name;
+}
+
+static struct artist *do_find_artist(const struct artist *artist,
+ struct rb_root *root,
+ struct rb_node ***p_new,
+ struct rb_node **p_parent)
+{
+ struct rb_node **new = &(root->rb_node), *parent = NULL;
+ const char *a = artist_sort_name(artist);
+ const char *collkey_a = artist_sort_collkey(artist);
+
+ while (*new) {
+ struct artist *cur_artist = to_artist(*new);
+ const char *b = artist_sort_name(cur_artist);
+ const char *collkey_b = artist_sort_collkey(cur_artist);
+ int result = special_name_cmp(a, collkey_a, b, collkey_b);
+
+ parent = *new;
+ if (result < 0)
+ new = &((*new)->rb_left);
+ else if (result > 0)
+ new = &((*new)->rb_right);
+ else
+ return cur_artist;
+ }
+ if (p_new)
+ *p_new = new;
+ if (p_parent)
+ *p_parent = parent;
+ return NULL;
+}
+
+/* search (tree) {{{ */
+static struct artist *collapse_artist;
+
+static int tree_search_matches(void *data, struct iter *iter, const char *text)
+{
+ struct tree_track *track;
+ struct iter tmpiter;
+ unsigned int flags = TI_MATCH_ARTIST | TI_MATCH_ALBUM | TI_MATCH_ALBUMARTIST;
+
+ if (!search_restricted)
+ flags |= TI_MATCH_TITLE;
+ track = iter_to_tree_search_track(iter);
+ if (!track_info_matches(tree_track_info(track), text, flags))
+ return 0;
+
+ /* collapse old search result */
+ if (collapse_artist) {
+ struct artist *artist = do_find_artist(collapse_artist, &lib_artist_root, NULL, NULL);
+ if (artist && artist != track->album->artist) {
+ if (artist->expanded)
+ tree_set_expand_artist(artist, 0);
+ artist_free(collapse_artist);
+ collapse_artist = (!track->album->artist->expanded) ? artist_copy(track->album->artist) : NULL;
+ }
+ } else if (!track->album->artist->expanded)
+ collapse_artist = artist_copy(track->album->artist);
+
+ track->album->artist->expanded = 1;
+ album_to_iter(track->album, &tmpiter);
+ window_set_sel(lib_tree_win, &tmpiter);
+
+ tree_track_to_iter(track, &tmpiter);
+ window_set_sel(lib_track_win, &tmpiter);
+ return 1;
+}
+/* search (tree) }}} */
+
+static void insert_artist(struct artist *artist, struct rb_root *root)
+{
+ struct rb_node **new = &(root->rb_node), *parent = NULL;
+ struct artist *found;
+
+ found = do_find_artist(artist, root, &new, &parent);
+ if (!found) {
+ rb_link_node(&artist->tree_node, parent, new);
+ rb_insert_color(&artist->tree_node, root);
+ }
+}
+
+static void add_artist(struct artist *artist)
+{
+ insert_artist(artist, &lib_artist_root);
+}
+
+static struct artist *find_artist(const struct artist *artist)
+{
+ return do_find_artist(artist, &lib_artist_root, NULL, NULL);
+}
+
+static struct album *do_find_album(const struct album *album,
+ int (*cmp)(const struct album *, const struct album *),
+ struct rb_node ***p_new,
+ struct rb_node **p_parent)
+{
+ struct rb_node **new = &(album->artist->album_root.rb_node), *parent = NULL;
+
+ while (*new) {
+ struct album *a = to_album(*new);
+
+ int result = cmp(album, a);
+
+ parent = *new;
+ if (result < 0)
+ new = &((*new)->rb_left);
+ else if (result > 0)
+ new = &((*new)->rb_right);
+ else
+ return a;
+ }
+ if (p_new)
+ *p_new = new;
+ if (p_parent)
+ *p_parent = parent;
+ return NULL;
+}
+
+static struct album *find_album(const struct album *album)
+{
+ struct album *a;
+ struct rb_node *tmp;
+
+ /* do a linear search because we want find albums with different date */
+ rb_for_each_entry(a, tmp, &album->artist->album_root, tree_node) {
+ if (special_album_cmp(album, a) == 0)
+ return a;
+ }
+ return NULL;
+}
+
+static void add_album(struct album *album)
+{
+ struct rb_node **new = &(album->artist->album_root.rb_node), *parent = NULL;
+ struct album *found;
+
+ /*
+ * Sort regular albums by date, but sort compilations
+ * alphabetically.
+ */
+ found = do_find_album(album,
+ album->artist->is_compilation ? special_album_cmp
+ : special_album_cmp_date,
+ &new, &parent);
+ if (!found) {
+ rb_link_node(&album->tree_node, parent, new);
+ rb_insert_color(&album->tree_node, &album->artist->album_root);
+ }
+}
+
+static void album_add_track(struct album *album, struct tree_track *track)
+{
+ /*
+ * NOTE: This is not perfect. You should ignore track numbers if
+ * either is unset and use filename instead, but usually you
+ * have all track numbers set or all unset (within one album
+ * of course).
+ */
+ static const sort_key_t album_track_sort_keys[] = {
+ SORT_DISCNUMBER, SORT_TRACKNUMBER, SORT_FILENAME, SORT_INVALID
+ };
+ struct rb_node **new = &(album->track_root.rb_node), *parent = NULL;
+
+ track->album = album;
+ while (*new) {
+ const struct simple_track *a = (const struct simple_track *) track;
+ const struct simple_track *b = (const struct simple_track *) to_tree_track(*new);
+ int result = track_info_cmp(a->info, b->info, album_track_sort_keys);
+
+ parent = *new;
+ if (result < 0)
+ new = &((*new)->rb_left);
+ else if (result > 0)
+ new = &((*new)->rb_right);
+ else
+ /* only add to tree if not there already */
+ return;
+ }
+
+ rb_link_node(&track->tree_node, parent, new);
+ rb_insert_color(&track->tree_node, &album->track_root);
+}
+
+static const char *tree_artist_name(const struct track_info* ti)
+{
+ const char *val = ti->albumartist;
+
+ if (ti->is_va_compilation)
+ val = "<Various Artists>";
+ if (!val || strcmp(val, "") == 0)
+ val = "<No Name>";
+
+ return val;
+}
+
+static const char *tree_album_name(const struct track_info* ti)
+{
+ const char *val = ti->album;
+
+ if (!val || strcmp(val, "") == 0)
+ val = "<No Name>";
+
+ return val;
+}
+
+static void remove_album(struct album *album)
+{
+ if (album->artist->expanded) {
+ struct iter iter;
+
+ album_to_iter(album, &iter);
+ window_row_vanishes(lib_tree_win, &iter);
+ }
+ rb_erase(&album->tree_node, &album->artist->album_root);
+}
+
+static void remove_artist(struct artist *artist)
+{
+ struct iter iter;
+
+ artist_to_iter(artist, &iter);
+ window_row_vanishes(lib_tree_win, &iter);
+ rb_erase(&artist->tree_node, &lib_artist_root);
+}
+
+void tree_add_track(struct tree_track *track)
+{
+ const struct track_info *ti = tree_track_info(track);
+ const char *album_name, *artist_name, *artistsort_name = NULL;
+ const char *albumsort_name = NULL;
+ struct artist *artist, *new_artist;
+ struct album *album, *new_album;
+ int date;
+ int is_va_compilation = 0;
+
+ date = ti->originaldate;
+ if (date < 0)
+ date = ti->date;
+
+ if (is_http_url(ti->filename)) {
+ artist_name = "<Stream>";
+ album_name = "<Stream>";
+ } else {
+ album_name = tree_album_name(ti);
+ artist_name = tree_artist_name(ti);
+ artistsort_name = ti->artistsort;
+ albumsort_name = ti->albumsort;
+
+ is_va_compilation = ti->is_va_compilation;
+ }
+
+ new_artist = artist_new(artist_name, artistsort_name, is_va_compilation);
+ album = NULL;
+
+ artist = find_artist(new_artist);
+ if (artist) {
+ artist_free(new_artist);
+ new_album = album_new(artist, album_name, albumsort_name, date);
+ album = find_album(new_album);
+ if (album)
+ album_free(new_album);
+ } else
+ new_album = album_new(new_artist, album_name, albumsort_name, date);
+
+ if (artist) {
+ int changed = 0;
+ /* If it makes sense to update sort_name, do it */
+ if (!artist->sort_name && artistsort_name) {
+ artist->sort_name = xstrdup(artistsort_name);
+ artist->collkey_sort_name = u_strcasecoll_key(artistsort_name);
+ changed = 1;
+ }
+ /* If names differ, update */
+ if (!artist->auto_sort_name) {
+ char *auto_sort_name = auto_artist_sort_name(artist_name);
+ if (auto_sort_name) {
+ free(artist->name);
+ free(artist->collkey_name);
+ artist->name = xstrdup(artist_name);
+ artist->collkey_name = u_strcasecoll_key(artist_name);
+ artist->auto_sort_name = auto_sort_name;
+ artist->collkey_auto_sort_name = u_strcasecoll_key(auto_sort_name);
+ changed = 1;
+ }
+ }
+ if (changed) {
+ remove_artist(artist);
+ add_artist(artist);
+ window_changed(lib_tree_win);
+ }
+ }
+
+ if (album) {
+ album_add_track(album, track);
+
+ /* If it makes sense to update album date, do it */
+ if (album->date < date) {
+ album->date = date;
+
+ remove_album(album);
+ add_album(album);
+ if (artist->expanded)
+ window_changed(lib_tree_win);
+ }
+
+ if (album_selected(album))
+ window_changed(lib_track_win);
+ } else if (artist) {
+ add_album(new_album);
+ album_add_track(new_album, track);
+
+ if (artist->expanded)
+ window_changed(lib_tree_win);
+ } else {
+ add_artist(new_artist);
+ add_album(new_album);
+ album_add_track(new_album, track);
+
+ window_changed(lib_tree_win);
+ }
+}
+
+static void remove_sel_artist(struct artist *artist)
+{
+ struct rb_node *a_node, *a_tmp;
+
+ rb_for_each_safe(a_node, a_tmp, &artist->album_root) {
+ struct rb_node *t_node, *t_tmp;
+ struct album *album = to_album(a_node);
+
+ rb_for_each_safe(t_node, t_tmp, &album->track_root) {
+ struct tree_track *track = to_tree_track(t_node);
+
+ editable_remove_track(&lib_editable, (struct simple_track *)track);
+ }
+ /* all tracks removed => album removed
+ * if the last album was removed then the artist was removed too
+ */
+ }
+}
+
+static void remove_sel_album(struct album *album)
+{
+ struct rb_node *node, *tmp;
+
+ rb_for_each_safe(node, tmp, &album->track_root) {
+ struct tree_track *track = to_tree_track(node);
+
+ editable_remove_track(&lib_editable, (struct simple_track *)track);
+ }
+}
+
+static void tree_win_remove_sel(void)
+{
+ struct artist *artist;
+ struct album *album;
+
+ tree_win_get_selected(&artist, &album);
+ if (album) {
+ remove_sel_album(album);
+ } else if (artist) {
+ remove_sel_artist(artist);
+ }
+}
+
+static void track_win_remove_sel(void)
+{
+ struct iter sel;
+ struct tree_track *track;
+
+ if (window_get_sel(lib_track_win, &sel)) {
+ track = iter_to_tree_track(&sel);
+ BUG_ON(track == NULL);
+ editable_remove_track(&lib_editable, (struct simple_track *)track);
+ }
+}
+
+void tree_toggle_active_window(void)
+{
+ if (lib_cur_win == lib_tree_win) {
+ struct artist *artist;
+ struct album *album;
+
+ tree_win_get_selected(&artist, &album);
+ if (album) {
+ lib_cur_win = lib_track_win;
+ lib_tree_win->changed = 1;
+ lib_track_win->changed = 1;
+ }
+ } else if (lib_cur_win == lib_track_win) {
+ lib_cur_win = lib_tree_win;
+ lib_tree_win->changed = 1;
+ lib_track_win->changed = 1;
+ }
+}
+
+void tree_toggle_expand_artist(void)
+{
+ struct iter sel;
+ struct artist *artist;
+
+ window_get_sel(lib_tree_win, &sel);
+ artist = iter_to_artist(&sel);
+ if (artist)
+ tree_set_expand_artist(artist, !artist->expanded);
+}
+
+void tree_expand_matching(const char *text)
+{
+ struct artist *artist;
+ struct rb_node *tmp1;
+ int have_track_selected = 0;
+
+ rb_for_each_entry(artist, tmp1, &lib_artist_root, tree_node) {
+ struct album *album = NULL;
+ struct rb_node *tmp2;
+ int album_matched = 0;
+
+ rb_for_each_entry(album, tmp2, &artist->album_root, tree_node) {
+ struct tree_track *tree_track = to_tree_track(rb_first(&album->track_root));
+ struct track_info *ti = ((struct simple_track *) tree_track)->info;
+ album_matched = track_info_matches_full(ti, text, TI_MATCH_ALBUM, TI_MATCH_ARTIST | TI_MATCH_ALBUMARTIST, 0);
+ if (album_matched)
+ break;
+ }
+ artist->expanded = album_matched;
+ if (!have_track_selected) {
+ struct tree_track *tree_track;
+ int track_matched = 0;
+
+ if (!album)
+ album = to_album(rb_first(&artist->album_root));
+
+ rb_for_each_entry(tree_track, tmp2, &album->track_root, tree_node) {
+ struct track_info *ti = ((struct simple_track *) tree_track)->info;
+ track_matched = track_info_matches_full(ti, text, TI_MATCH_TITLE, 0, 0);
+ if (track_matched)
+ break;
+ }
+ if (album_matched || track_matched) {
+ if (!tree_track)
+ tree_track = to_tree_track(rb_first(&album->track_root));
+ tree_sel_track(tree_track);
+ have_track_selected = 1;
+ }
+ }
+ }
+ window_changed(lib_tree_win);
+}
+
+void tree_expand_all(void)
+{
+ struct artist *artist;
+ struct rb_node *tmp;
+
+ rb_for_each_entry(artist, tmp, &lib_artist_root, tree_node) {
+ artist->expanded = 1;
+ }
+ window_changed(lib_tree_win);
+}
+
+static void remove_track(struct tree_track *track)
+{
+ if (album_selected(track->album)) {
+ struct iter iter;
+
+ tree_track_to_iter(track, &iter);
+ window_row_vanishes(lib_track_win, &iter);
+ }
+ rb_erase(&track->tree_node, &track->album->track_root);
+}
+
+void tree_remove(struct tree_track *track)
+{
+ struct album *album = track->album;
+ struct artist *sel_artist;
+ struct album *sel_album;
+
+ tree_win_get_selected(&sel_artist, &sel_album);
+
+ remove_track(track);
+ /* don't free the track */
+
+ if (rb_root_empty(&album->track_root)) {
+ struct artist *artist = album->artist;
+
+ if (sel_album == album)
+ lib_cur_win = lib_tree_win;
+
+ remove_album(album);
+ album_free(album);
+
+ if (rb_root_empty(&artist->album_root)) {
+ artist->expanded = 0;
+ remove_artist(artist);
+ artist_free(artist);
+ }
+ }
+}
+
+void tree_remove_sel(void)
+{
+ if (lib_cur_win == lib_tree_win) {
+ tree_win_remove_sel();
+ } else {
+ track_win_remove_sel();
+ }
+}
+
+void tree_sort_artists(void)
+{
+ struct rb_node *a_node, *a_tmp;
+
+ rb_for_each_safe(a_node, a_tmp, &lib_artist_root) {
+ struct rb_node *l_node, *l_tmp;
+ struct artist *artist = to_artist(a_node);
+
+ rb_for_each_safe(l_node, l_tmp, &artist->album_root) {
+ struct rb_node *t_node, *t_tmp;
+ struct album *album = to_album(l_node);
+
+ rb_for_each_safe(t_node, t_tmp, &album->track_root) {
+ struct tree_track *track = to_tree_track(t_node);
+
+ tree_remove(track);
+ tree_add_track(track);
+ }
+ }
+ }
+}
+
+void tree_sel_current(void)
+{
+ tree_sel_track(lib_cur_track);
+}
+
+void tree_sel_first(void)
+{
+ if (!rb_root_empty(&lib_artist_root)) {
+ struct artist *artist = to_artist(rb_first(&lib_artist_root));
+ struct album *album = to_album(rb_first(&artist->album_root));
+ struct tree_track *tree_track = to_tree_track(rb_first(&album->track_root));
+ tree_sel_track(tree_track);
+ }
+}
+
+void tree_sel_track(struct tree_track *t)
+{
+ if (t) {
+ struct iter iter;
+
+ t->album->artist->expanded = 1;
+
+ if (lib_cur_win == lib_tree_win) {
+ lib_cur_win = lib_track_win;
+ lib_tree_win->changed = 1;
+ lib_track_win->changed = 1;
+ }
+
+ album_to_iter(t->album, &iter);
+ window_set_sel(lib_tree_win, &iter);
+
+ tree_track_to_iter(t, &iter);
+ window_set_sel(lib_track_win, &iter);
+ }
+}
+
+static int album_for_each_track(struct album *album, int (*cb)(void *data, struct track_info *ti),
+ void *data, int reverse)
+{
+ struct tree_track *track;
+ struct rb_node *tmp;
+ int rc = 0;
+
+ if (reverse) {
+ rb_for_each_entry_reverse(track, tmp, &album->track_root, tree_node) {
+ rc = cb(data, tree_track_info(track));
+ if (rc)
+ break;
+ }
+ } else {
+ rb_for_each_entry(track, tmp, &album->track_root, tree_node) {
+ rc = cb(data, tree_track_info(track));
+ if (rc)
+ break;
+ }
+ }
+ return rc;
+}
+
+static int artist_for_each_track(struct artist *artist, int (*cb)(void *data, struct track_info *ti),
+ void *data, int reverse)
+{
+ struct album *album;
+ struct rb_node *tmp;
+ int rc = 0;
+
+ if (reverse) {
+ rb_for_each_entry_reverse(album, tmp, &artist->album_root, tree_node) {
+ rc = album_for_each_track(album, cb, data, 1);
+ if (rc)
+ break;
+ }
+ } else {
+ rb_for_each_entry(album, tmp, &artist->album_root, tree_node) {
+ rc = album_for_each_track(album, cb, data, 0);
+ if (rc)
+ break;
+ }
+ }
+ return rc;
+}
+
+int __tree_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
+{
+ int rc = 0;
+
+ if (lib_cur_win == lib_tree_win) {
+ struct artist *artist;
+ struct album *album;
+
+ tree_win_get_selected(&artist, &album);
+ if (artist) {
+ if (album == NULL) {
+ rc = artist_for_each_track(artist, cb, data, reverse);
+ } else {
+ rc = album_for_each_track(album, cb, data, reverse);
+ }
+ }
+ } else {
+ struct iter sel;
+ struct tree_track *track;
+
+ if (window_get_sel(lib_track_win, &sel)) {
+ track = iter_to_tree_track(&sel);
+ rc = cb(data, tree_track_info(track));
+ }
+ }
+ return rc;
+}
+
+int tree_for_each_sel(int (*cb)(void *data, struct track_info *ti), void *data, int reverse)
+{
+ int rc = __tree_for_each_sel(cb, data, reverse);
+
+ window_down(lib_cur_win, 1);
+ return rc;
+}
--- /dev/null
+/*
+ * Copyright 2010-2011 Various Authors
+ * Copyright 2010 Johannes Weißl
+ *
+ * based on gunicollate.c from glib
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "u_collate.h"
+#include "uchar.h"
+#include "xmalloc.h"
+#include "ui_curses.h" /* using_utf8, charset */
+#include "convert.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+int u_strcoll(const char *str1, const char *str2)
+{
+ int result;
+
+ if (using_utf8) {
+ result = strcoll(str1, str2);
+ } else {
+ char *str1_locale, *str2_locale;
+
+ convert(str1, -1, &str1_locale, -1, charset, "UTF-8");
+ convert(str2, -1, &str2_locale, -1, charset, "UTF-8");
+
+ if (str1_locale && str2_locale)
+ result = strcoll(str1_locale, str2_locale);
+ else
+ result = strcmp(str1, str2);
+
+ if (str2_locale)
+ free(str2_locale);
+ if (str1_locale)
+ free(str1_locale);
+ }
+
+ return result;
+}
+
+int u_strcasecoll(const char *str1, const char *str2)
+{
+ char *cf_a, *cf_b;
+ int res;
+
+ cf_a = u_casefold(str1);
+ cf_b = u_casefold(str2);
+
+ res = u_strcoll(cf_a, cf_b);
+
+ free(cf_b);
+ free(cf_a);
+
+ return res;
+}
+
+int u_strcasecoll0(const char *str1, const char *str2)
+{
+ if (!str1)
+ return str2 ? -1 : 0;
+ if (!str2)
+ return 1;
+
+ return u_strcasecoll(str1, str2);
+}
+
+char *u_strcoll_key(const char *str)
+{
+ char *result = NULL;
+
+ if (using_utf8) {
+ size_t xfrm_len = strxfrm(NULL, str, 0);
+ if ((ssize_t) xfrm_len >= 0 && xfrm_len < INT_MAX - 2) {
+ result = xnew(char, xfrm_len + 1);
+ strxfrm(result, str, xfrm_len + 1);
+ }
+ }
+
+ if (!result) {
+ char *str_locale = NULL;
+
+ convert(str, -1, &str_locale, -1, charset, "UTF-8");
+
+ if (str_locale) {
+ size_t xfrm_len = strxfrm(NULL, str_locale, 0);
+ if ((ssize_t) xfrm_len >= 0 && xfrm_len < INT_MAX - 2) {
+ result = xnew(char, xfrm_len + 2);
+ result[0] = 'A';
+ strxfrm(result + 1, str_locale, xfrm_len + 1);
+ }
+ free(str_locale);
+ }
+ }
+
+ if (!result) {
+ size_t xfrm_len = strlen(str);
+ result = xmalloc(xfrm_len + 2);
+ result[0] = 'B';
+ memcpy(result + 1, str, xfrm_len);
+ result[xfrm_len+1] = '\0';
+ }
+
+ return result;
+}
+
+char *u_strcasecoll_key(const char *str)
+{
+ char *key, *cf_str;
+
+ cf_str = u_casefold(str);
+
+ key = u_strcoll_key(cf_str);
+
+ free(cf_str);
+
+ return key;
+}
+
+char *u_strcasecoll_key0(const char *str)
+{
+ return str ? u_strcasecoll_key(str) : NULL;
+}
--- /dev/null
+/*
+ * Copyright 2010-2011 Various Authors
+ * Copyright 2010 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef U_COLLATE_H
+#define U_COLLATE_H
+
+/*
+ * @str1 valid, normalized, null-terminated UTF-8 string
+ * @str2 valid, normalized, null-terminated UTF-8 string
+ *
+ * Compares two strings for ordering using the linguistically
+ * correct rules for the current locale.
+ *
+ * Returns -1 if @str1 compares before @str2, 0 if they compare equal,
+ * +1 if @str1 compares after @str2.
+ */
+int u_strcoll(const char *str1, const char *str2);
+
+/*
+ * @str1 valid, normalized, null-terminated UTF-8 string
+ * @str2 valid, normalized, null-terminated UTF-8 string
+ *
+ * Like u_strcoll(), but do casefolding before comparing.
+ */
+int u_strcasecoll(const char *str1, const char *str2);
+
+/*
+ * @str1 valid, normalized, null-terminated UTF-8 string or NULL
+ * @str2 valid, normalized, null-terminated UTF-8 string or NULL
+ *
+ * Like u_strcasecoll(), but handle NULL pointers gracefully.
+ */
+int u_strcasecoll0(const char *str1, const char *str2);
+
+/*
+ * @str valid, normalized, null-terminated UTF-8 string
+ *
+ * Converts a string into a collation key that can be compared
+ * with other collation keys produced by the same function using
+ * strcmp().
+ *
+ * Returns a newly allocated string.
+ */
+char *u_strcoll_key(const char *str);
+
+/*
+ * @str valid, normalized, null-terminated UTF-8 string
+ *
+ * Like u_strcoll_key(), but do casefolding before generating key.
+ *
+ * Returns a newly allocated string.
+ */
+char *u_strcasecoll_key(const char *str);
+
+/*
+ * @str valid, normalized, null-terminated UTF-8 string or NULL
+ *
+ * Like u_strcasecoll_key(), but handle NULL pointers gracefully.
+ */
+char *u_strcasecoll_key0(const char *str);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "uchar.h"
+#include "compiler.h"
+#include "gbuf.h"
+#include "utils.h" /* N_ELEMENTS */
+#include "ui_curses.h" /* using_utf8, charset */
+#include "convert.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+#include <wctype.h>
+#include <ctype.h>
+
+#include "unidecomp.h"
+
+const char hex_tab[16] = "0123456789abcdef";
+
+/*
+ * Byte Sequence Min Min Max
+ * ----------------------------------------------------------------------------------
+ * 0xxxxxxx 0000000 0x00000 0x00007f
+ * 110xxxxx 10xxxxxx 000 10000000 0x00080 0x0007ff
+ * 1110xxxx 10xxxxxx 10xxxxxx 00001000 00000000 0x00800 0x00ffff
+ * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 00001 00000000 00000000 0x10000 0x10ffff (not 0x1fffff)
+ *
+ * max: 100 001111 111111 111111 (0x10ffff)
+ */
+
+/* Length of UTF-8 byte sequence.
+ * Table index is the first byte of UTF-8 sequence.
+ */
+static const signed char len_tab[256] = {
+ /* 0-127 0xxxxxxx */
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+
+ /* 128-191 10xxxxxx (invalid first byte) */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+
+ /* 192-223 110xxxxx */
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+
+ /* 224-239 1110xxxx */
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+
+ /* 240-244 11110xxx (000 - 100) */
+ 4, 4, 4, 4, 4,
+
+ /* 11110xxx (101 - 111) (always invalid) */
+ -1, -1, -1,
+
+ /* 11111xxx (always invalid) */
+ -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+/* fault-tolerant equivalent to len_tab, from glib */
+static const char utf8_skip_data[256] = {
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+ 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
+};
+const char * const utf8_skip = utf8_skip_data;
+
+/* index is length of the UTF-8 sequence - 1 */
+static int min_val[4] = { 0x000000, 0x000080, 0x000800, 0x010000 };
+static int max_val[4] = { 0x00007f, 0x0007ff, 0x00ffff, 0x10ffff };
+
+/* get value bits from the first UTF-8 sequence byte */
+static unsigned int first_byte_mask[4] = { 0x7f, 0x1f, 0x0f, 0x07 };
+
+int u_is_valid(const char *str)
+{
+ const unsigned char *s = (const unsigned char *)str;
+ int i = 0;
+
+ while (s[i]) {
+ unsigned char ch = s[i++];
+ int len = len_tab[ch];
+
+ if (len <= 0)
+ return 0;
+
+ if (len > 1) {
+ /* len - 1 10xxxxxx bytes */
+ uchar u;
+ int c;
+
+ len--;
+ u = ch & first_byte_mask[len];
+ c = len;
+ do {
+ ch = s[i++];
+ if (len_tab[ch] != 0)
+ return 0;
+ u = (u << 6) | (ch & 0x3f);
+ } while (--c);
+
+ if (u < min_val[len] || u > max_val[len])
+ return 0;
+ }
+ }
+ return 1;
+}
+
+size_t u_strlen(const char *str)
+{
+ size_t len;
+ for (len = 0; *str; len++)
+ str = u_next_char(str);
+ return len;
+}
+
+size_t u_strlen_safe(const char *str)
+{
+ const unsigned char *s = (const unsigned char *)str;
+ size_t len = 0;
+
+ while (*s) {
+ int l = len_tab[*s];
+
+ if (unlikely(l > 1)) {
+ /* next l - 1 bytes must be 0x10xxxxxx */
+ int c = 1;
+ do {
+ if (len_tab[s[c]] != 0) {
+ /* invalid sequence */
+ goto single_char;
+ }
+ c++;
+ } while (c < l);
+
+ /* valid sequence */
+ s += l;
+ len++;
+ continue;
+ }
+single_char:
+ /* l is -1, 0 or 1
+ * invalid chars counted as single characters */
+ s++;
+ len++;
+ }
+ return len;
+}
+
+int u_char_width(uchar u)
+{
+ if (unlikely(u < 0x20))
+ goto control;
+
+ if (u < 0x1100U)
+ goto narrow;
+
+ /* Hangul Jamo init. consonants */
+ if (u <= 0x115fU)
+ goto wide;
+
+ /* angle brackets */
+ if (u == 0x2329U || u == 0x232aU)
+ goto wide;
+
+ if (u < 0x2e80U)
+ goto narrow;
+ /* CJK ... Yi */
+ if (u < 0x302aU)
+ goto wide;
+ if (u <= 0x302fU)
+ goto narrow;
+ if (u == 0x303fU)
+ goto narrow;
+ if (u == 0x3099U)
+ goto narrow;
+ if (u == 0x309aU)
+ goto narrow;
+ /* CJK ... Yi */
+ if (u <= 0xa4cfU)
+ goto wide;
+
+ /* Hangul Syllables */
+ if (u >= 0xac00U && u <= 0xd7a3U)
+ goto wide;
+
+ /* CJK Compatibility Ideographs */
+ if (u >= 0xf900U && u <= 0xfaffU)
+ goto wide;
+
+ /* CJK Compatibility Forms */
+ if (u >= 0xfe30U && u <= 0xfe6fU)
+ goto wide;
+
+ /* Fullwidth Forms */
+ if (u >= 0xff00U && u <= 0xff60U)
+ goto wide;
+
+ /* Fullwidth Forms */
+ if (u >= 0xffe0U && u <= 0xffe6U)
+ goto wide;
+
+ /* CJK extra stuff */
+ if (u >= 0x20000U && u <= 0x2fffdU)
+ goto wide;
+
+ /* ? */
+ if (u >= 0x30000U && u <= 0x3fffdU)
+ goto wide;
+
+ /* invalid bytes in unicode stream are rendered "<xx>" */
+ if (u & U_INVALID_MASK)
+ goto invalid;
+narrow:
+ return 1;
+wide:
+ return 2;
+control:
+ /* special case */
+ if (u == 0)
+ return 1;
+
+ /* print control chars as <xx> */
+invalid:
+ return 4;
+}
+
+int u_str_width(const char *str)
+{
+ int idx = 0, w = 0;
+
+ while (str[idx]) {
+ uchar u = u_get_char(str, &idx);
+ w += u_char_width(u);
+ }
+ return w;
+}
+
+int u_str_nwidth(const char *str, int len)
+{
+ int idx = 0;
+ int w = 0;
+
+ while (len > 0) {
+ uchar u = u_get_char(str, &idx);
+ if (u == 0)
+ break;
+ w += u_char_width(u);
+ len--;
+ }
+ return w;
+}
+
+char *u_strchr(const char *str, uchar uch)
+{
+ int idx = 0;
+
+ while (str[idx]) {
+ uchar u = u_get_char(str, &idx);
+ if (uch == u)
+ return (char *) (str + idx);
+ }
+ return NULL;
+}
+
+void u_prev_char_pos(const char *str, int *idx)
+{
+ const unsigned char *s = (const unsigned char *)str;
+ int c, len, i = *idx;
+ uchar ch;
+
+ ch = s[--i];
+ len = len_tab[ch];
+ if (len != 0) {
+ /* start of byte sequence or invelid uchar */
+ goto one;
+ }
+
+ c = 1;
+ while (1) {
+ if (i == 0) {
+ /* first byte of the sequence is missing */
+ break;
+ }
+
+ ch = s[--i];
+ len = len_tab[ch];
+ c++;
+
+ if (len == 0) {
+ if (c < 4)
+ continue;
+
+ /* too long sequence */
+ break;
+ }
+ if (len != c) {
+ /* incorrect length */
+ break;
+ }
+
+ /* ok */
+ *idx = i;
+ return;
+ }
+one:
+ *idx = *idx - 1;
+ return;
+}
+
+uchar u_get_char(const char *str, int *idx)
+{
+ const unsigned char *s = (const unsigned char *)str;
+ int len, i, x = 0;
+ uchar ch, u;
+
+ if (idx)
+ s += *idx;
+ else
+ idx = &x;
+ ch = s[0];
+
+ /* ASCII optimization */
+ if (ch < 128) {
+ *idx += 1;
+ return ch;
+ }
+
+ len = len_tab[ch];
+ if (unlikely(len < 1))
+ goto invalid;
+
+ u = ch & first_byte_mask[len - 1];
+ for (i = 1; i < len; i++) {
+ ch = s[i];
+ if (unlikely(len_tab[ch] != 0))
+ goto invalid;
+ u = (u << 6) | (ch & 0x3f);
+ }
+ *idx += len;
+ return u;
+invalid:
+ *idx += 1;
+ u = s[0];
+ return u | U_INVALID_MASK;
+}
+
+void u_set_char_raw(char *str, int *idx, uchar uch)
+{
+ int i = *idx;
+
+ if (uch <= 0x0000007fU) {
+ str[i++] = uch;
+ *idx = i;
+ } else if (uch <= 0x000007ffU) {
+ str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 0] = uch | 0x000000c0U;
+ i += 2;
+ *idx = i;
+ } else if (uch <= 0x0000ffffU) {
+ str[i + 2] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 0] = uch | 0x000000e0U;
+ i += 3;
+ *idx = i;
+ } else if (uch <= 0x0010ffffU) {
+ str[i + 3] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 2] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 0] = uch | 0x000000f0U;
+ i += 4;
+ *idx = i;
+ } else {
+ /* must be an invalid uchar */
+ str[i++] = uch & 0xff;
+ *idx = i;
+ }
+}
+
+/*
+ * Printing functions, these lose information
+ */
+
+void u_set_char(char *str, int *idx, uchar uch)
+{
+ int i = *idx;
+
+ if (unlikely(uch <= 0x0000001fU))
+ goto invalid;
+
+ if (uch <= 0x0000007fU) {
+ str[i++] = uch;
+ *idx = i;
+ return;
+ } else if (uch <= 0x000007ffU) {
+ str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 0] = uch | 0x000000c0U;
+ i += 2;
+ *idx = i;
+ return;
+ } else if (uch <= 0x0000ffffU) {
+ str[i + 2] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 0] = uch | 0x000000e0U;
+ i += 3;
+ *idx = i;
+ return;
+ } else if (uch <= 0x0010ffffU) {
+ str[i + 3] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 2] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 1] = (uch & 63) | 0x80; uch >>= 6;
+ str[i + 0] = uch | 0x000000f0U;
+ i += 4;
+ *idx = i;
+ return;
+ }
+invalid:
+ /* control character or invalid unicode */
+ if (uch == 0) {
+ /* handle this special case here to make the common case fast */
+ str[i++] = 0;
+ *idx = i;
+ } else {
+ str[i++] = '<';
+ str[i++] = hex_tab[(uch >> 4) & 0xf];
+ str[i++] = hex_tab[uch & 0xf];
+ str[i++] = '>';
+ *idx = i;
+ }
+}
+
+int u_copy_chars(char *dst, const char *src, int *width)
+{
+ int w = *width;
+ int si = 0, di = 0;
+ int cw;
+ uchar u;
+
+ while (w > 0) {
+ u = u_get_char(src, &si);
+ if (u == 0)
+ break;
+
+ cw = u_char_width(u);
+ w -= cw;
+
+ if (unlikely(w < 0)) {
+ if (cw == 2)
+ dst[di++] = ' ';
+ if (cw == 4) {
+ dst[di++] = '<';
+ if (w >= -2)
+ dst[di++] = hex_tab[(u >> 4) & 0xf];
+ if (w >= -1)
+ dst[di++] = hex_tab[u & 0xf];
+ }
+ w = 0;
+ break;
+ }
+ u_set_char(dst, &di, u);
+ }
+ *width -= w;
+ return di;
+}
+
+int u_to_ascii(char *dst, const char *src, int len)
+{
+ int i, idx = 0;
+ for (i = 0; i < len && src[idx]; i++) {
+ uchar u = u_get_char(src, &idx);
+ dst[i] = (u < 128) ? u : '?';
+ }
+ return i;
+}
+
+int u_skip_chars(const char *str, int *width)
+{
+ int w = *width;
+ int idx = 0;
+
+ while (w > 0) {
+ uchar u = u_get_char(str, &idx);
+ w -= u_char_width(u);
+ }
+ /* add 1..3 if skipped 'too much' (the last char was double width or invalid (<xx>)) */
+ *width -= w;
+ return idx;
+}
+
+/*
+ * Case-folding functions
+ */
+
+static inline uchar u_casefold_char(uchar ch)
+{
+ /* faster lookup for for A-Z, rest of ASCII unaffected */
+ if (ch < 0x0041)
+ return ch;
+ if (ch <= 0x005A)
+ return ch + 0x20;
+#if defined(_WIN32) || defined(__STDC_ISO_10646__) || defined(__APPLE__)
+ if (ch < 128)
+ return ch;
+ ch = towlower(ch);
+#endif
+ return ch;
+}
+
+char *u_casefold(const char *str)
+{
+ GBUF(out);
+ int i = 0;
+
+ while (str[i]) {
+ char buf[4];
+ int buflen = 0;
+ uchar ch = u_get_char(str, &i);
+
+ ch = u_casefold_char(ch);
+ u_set_char_raw(buf, &buflen, ch);
+ gbuf_add_bytes(&out, buf, buflen);
+ }
+
+ return gbuf_steal(&out);
+}
+
+/*
+ * Comparison functions
+ */
+
+int u_strcase_equal(const char *a, const char *b)
+{
+ int ai = 0, bi = 0;
+
+ while (a[ai]) {
+ uchar au, bu;
+
+ au = u_get_char(a, &ai);
+ bu = u_get_char(b, &bi);
+
+ if (u_casefold_char(au) != u_casefold_char(bu))
+ return 0;
+ }
+
+ return b[bi] ? 0 : 1;
+}
+
+static uchar get_base_from_composed(uchar ch)
+{
+ int begin = 0;
+ int end = N_ELEMENTS(unidecomp_map);
+
+ if (ch < unidecomp_map[begin].composed || ch > unidecomp_map[end - 1].composed)
+ return ch;
+
+ /* binary search */
+ while (1) {
+ int half = (begin + end) / 2;
+ if (ch == unidecomp_map[half].composed)
+ return unidecomp_map[half].base;
+ else if (half == begin)
+ break;
+ else if (ch > unidecomp_map[half].composed)
+ begin = half;
+ else
+ end = half;
+ }
+ return ch;
+}
+
+static inline int do_u_strncase_equal(const char *a, const char *b, size_t len, int only_base_chars)
+{
+ int ai = 0, bi = 0;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ uchar au, bu;
+
+ au = u_get_char(a, &ai);
+ bu = u_get_char(b, &bi);
+
+ if (only_base_chars) {
+ au = get_base_from_composed(au);
+ bu = get_base_from_composed(bu);
+ }
+
+ if (u_casefold_char(au) != u_casefold_char(bu))
+ return 0;
+ }
+
+ return 1;
+}
+
+int u_strncase_equal(const char *a, const char *b, size_t len)
+{
+ return do_u_strncase_equal(a, b, len, 0);
+}
+
+int u_strncase_equal_base(const char *a, const char *b, size_t len)
+{
+ return do_u_strncase_equal(a, b, len, 1);
+}
+
+static inline char *do_u_strcasestr(const char *haystack, const char *needle, int only_base_chars)
+{
+ /* strlen is faster and works here */
+ int haystack_len = strlen(haystack);
+ int needle_len = u_strlen(needle);
+
+ do {
+ int idx;
+
+ if (haystack_len < needle_len)
+ return NULL;
+ if (do_u_strncase_equal(needle, haystack, needle_len, only_base_chars))
+ return (char *)haystack;
+
+ /* skip one char */
+ idx = 0;
+ u_get_char(haystack, &idx);
+ haystack += idx;
+ haystack_len -= idx;
+ } while (1);
+}
+
+char *u_strcasestr(const char *haystack, const char *needle)
+{
+ return do_u_strcasestr(haystack, needle, 0);
+}
+
+char *u_strcasestr_base(const char *haystack, const char *needle)
+{
+ return do_u_strcasestr(haystack, needle, 1);
+}
+
+char *u_strcasestr_filename(const char *haystack, const char *needle)
+{
+ char *r = NULL, *ustr = NULL;
+ if (!using_utf8 && utf8_encode(haystack, charset, &ustr) == 0)
+ haystack = ustr;
+ r = u_strcasestr_base(haystack, needle);
+ free(ustr);
+ return r;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _UCHAR_H
+#define _UCHAR_H
+
+#include <stddef.h> /* size_t */
+
+typedef unsigned int uchar;
+
+extern const char hex_tab[16];
+
+/*
+ * Invalid bytes are or'ed with this
+ * for example 0xff -> 0x100000ff
+ */
+#define U_INVALID_MASK 0x10000000U
+
+/*
+ * @uch potential unicode character
+ *
+ * Returns 1 if @uch is valid unicode character, 0 otherwise
+ */
+static inline int u_is_unicode(uchar uch)
+{
+ return uch <= 0x0010ffffU;
+}
+
+/*
+ * Returns size of @uch in bytes
+ */
+static inline int u_char_size(uchar uch)
+{
+ if (uch <= 0x0000007fU) {
+ return 1;
+ } else if (uch <= 0x000007ffU) {
+ return 2;
+ } else if (uch <= 0x0000ffffU) {
+ return 3;
+ } else if (uch <= 0x0010ffffU) {
+ return 4;
+ } else {
+ return 1;
+ }
+}
+
+/*
+ * Returns width of @uch (normally 1 or 2, 4 for invalid chars (<xx>))
+ */
+int u_char_width(uchar uch);
+
+/*
+ * @str any null-terminated string
+ *
+ * Returns 1 if @str is valid UTF-8 string, 0 otherwise.
+ */
+int u_is_valid(const char *str);
+
+/*
+ * @str valid, null-terminated UTF-8 string
+ *
+ * Returns position of next unicode character in @str.
+ */
+extern const char * const utf8_skip;
+static inline char *u_next_char(const char *str)
+{
+ return (char *) (str + utf8_skip[*((const unsigned char *) str)]);
+}
+
+/*
+ * @str valid, null-terminated UTF-8 string
+ *
+ * Retuns length of @str in UTF-8 characters.
+ */
+size_t u_strlen(const char *str);
+
+/*
+ * @str null-terminated UTF-8 string
+ *
+ * Retuns length of @str in UTF-8 characters.
+ * Invalid chars are counted as single characters.
+ */
+size_t u_strlen_safe(const char *str);
+
+/*
+ * @str null-terminated UTF-8 string
+ *
+ * Retuns width of @str.
+ */
+int u_str_width(const char *str);
+
+/*
+ * @str null-terminated UTF-8 string
+ * @len number of characters to measure
+ *
+ * Retuns width of the first @len characters in @str.
+ */
+int u_str_nwidth(const char *str, int len);
+
+/*
+ * @str null-terminated UTF-8 string
+ * @uch unicode character
+ *
+ * Returns a pointer to the first occurrence of @uch in the @str.
+ */
+char *u_strchr(const char *str, uchar uch);
+
+void u_prev_char_pos(const char *str, int *idx);
+
+/*
+ * @str null-terminated UTF-8 string
+ * @idx pointer to byte index in @str (not UTF-8 character index!) or NULL
+ *
+ * Returns unicode character at @str[*@idx] or @str[0] if @idx is NULL.
+ * Stores byte index of the next char back to @idx if set.
+ */
+uchar u_get_char(const char *str, int *idx);
+
+/*
+ * @str destination buffer
+ * @idx pointer to byte index in @str (not UTF-8 character index!)
+ * @uch unicode character
+ */
+void u_set_char_raw(char *str, int *idx, uchar uch);
+void u_set_char(char *str, int *idx, uchar uch);
+
+/*
+ * @dst destination buffer
+ * @src null-terminated UTF-8 string
+ * @width how much to copy
+ *
+ * Copies at most @count characters, less if null byte was hit.
+ * Null byte is _never_ copied.
+ * Actual width of copied characters is stored to @width.
+ *
+ * Returns number of _bytes_ copied.
+ */
+int u_copy_chars(char *dst, const char *src, int *width);
+
+/*
+ * @dst destination buffer
+ * @src null-terminated UTF-8 string
+ * @len how many bytes are available in @dst
+ *
+ * Copies at most @len bytes, less if null byte was hit. Replaces every
+ * non-ascii character by '?'. Null byte is _never_ copied.
+ *
+ * Returns number of bytes written to @dst.
+ */
+int u_to_ascii(char *dst, const char *src, int len);
+
+/*
+ * @str null-terminated UTF-8 string, must be long enough
+ * @width how much to skip
+ *
+ * Skips @count UTF-8 characters.
+ * Total width of skipped characters is stored to @width.
+ * Returned @width can be the given @width + 1 if the last skipped
+ * character was double width.
+ *
+ * Returns number of _bytes_ skipped.
+ */
+int u_skip_chars(const char *str, int *width);
+
+/*
+ * @str valid null-terminated UTF-8 string
+ *
+ * Converts a string into a form that is independent of case.
+ *
+ * Returns a newly allocated string
+ */
+char *u_casefold(const char *str);
+
+/*
+ * @str1 valid, normalized, null-terminated UTF-8 string
+ * @str2 valid, normalized, null-terminated UTF-8 string
+ *
+ * Returns 1 if @str1 is equal to @str2, ignoring the case of the characters.
+ */
+int u_strcase_equal(const char *str1, const char *str2);
+
+/*
+ * @str1 valid, normalized, null-terminated UTF-8 string
+ * @str2 valid, normalized, null-terminated UTF-8 string
+ * @len number of characters to consider for comparison
+ *
+ * Returns 1 if the first @len characters of @str1 and @str2 are equal,
+ * ignoring the case of the characters (0 otherwise).
+ */
+int u_strncase_equal(const char *str1, const char *str2, size_t len);
+
+/*
+ * @str1 valid, normalized, null-terminated UTF-8 string
+ * @str2 valid, normalized, null-terminated UTF-8 string
+ * @len number of characters to consider for comparison
+ *
+ * Like u_strncase_equal(), but uses only base characters for comparison
+ * (e.g. "Trentemöller" matches "Trentemøller")
+ */
+int u_strncase_equal_base(const char *str1, const char *str2, size_t len);
+
+/*
+ * @haystack valid, normalized, null-terminated UTF-8 string
+ * @needle valid, normalized, null-terminated UTF-8 string
+ *
+ * Returns position of @needle in @haystack (case insensitive comparison).
+ */
+char *u_strcasestr(const char *haystack, const char *needle);
+
+/*
+ * @haystack valid, normalized, null-terminated UTF-8 string
+ * @needle valid, normalized, null-terminated UTF-8 string
+ *
+ * Like u_strcasestr(), but uses only base characters for comparison
+ * (e.g. "Trentemöller" matches "Trentemøller")
+ */
+char *u_strcasestr_base(const char *haystack, const char *needle);
+
+/*
+ * @haystack null-terminated string in local encoding
+ * @needle valid, normalized, null-terminated UTF-8 string
+ *
+ * Like u_strcasestr_base(), but converts @haystack to UTF-8 if necessary.
+ */
+char *u_strcasestr_filename(const char *haystack, const char *needle);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "convert.h"
+#include "ui_curses.h"
+#include "cmdline.h"
+#include "search_mode.h"
+#include "command_mode.h"
+#include "options.h"
+#include "play_queue.h"
+#include "browser.h"
+#include "filters.h"
+#include "cmus.h"
+#include "player.h"
+#include "output.h"
+#include "utils.h"
+#include "lib.h"
+#include "pl.h"
+#include "xmalloc.h"
+#include "xstrjoin.h"
+#include "window.h"
+#include "format_print.h"
+#include "comment.h"
+#include "misc.h"
+#include "prog.h"
+#include "uchar.h"
+#include "spawn.h"
+#include "server.h"
+#include "keys.h"
+#include "debug.h"
+#include "help.h"
+#include "worker.h"
+#include "input.h"
+#include "file.h"
+#include "path.h"
+#include "mixer.h"
+#ifdef HAVE_CONFIG
+#include "config/curses.h"
+#include "config/iconv.h"
+#endif
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <locale.h>
+#include <langinfo.h>
+#ifdef HAVE_ICONV
+#include <iconv.h>
+#endif
+#include <signal.h>
+#include <stdarg.h>
+#include <math.h>
+
+#if defined(__sun__) || defined(__CYGWIN__)
+/* TIOCGWINSZ */
+#include <termios.h>
+#include <ncurses.h>
+#else
+#include <curses.h>
+#endif
+
+/* defined in <term.h> but without const */
+char *tgetstr(const char *id, char **area);
+char *tgoto(const char *cap, int col, int row);
+
+/* globals. documented in ui_curses.h */
+
+int cmus_running = 1;
+int ui_initialized = 0;
+enum ui_input_mode input_mode = NORMAL_MODE;
+int cur_view = TREE_VIEW;
+struct searchable *searchable;
+char *lib_filename = NULL;
+char *lib_ext_filename = NULL;
+char *pl_filename = NULL;
+char *pl_ext_filename = NULL;
+char *play_queue_filename = NULL;
+char *play_queue_ext_filename = NULL;
+char *charset = NULL;
+int using_utf8 = 0;
+
+
+/* ------------------------------------------------------------------------- */
+
+static char *lib_autosave_filename;
+static char *pl_autosave_filename;
+static char *play_queue_autosave_filename;
+
+/* shown error message and time stamp
+ * error is cleared if it is older than 3s and key was pressed
+ */
+static char error_buf[512];
+static time_t error_time = 0;
+/* info messages are displayed in different color */
+static int msg_is_error;
+static int error_count = 0;
+
+static char *server_address = NULL;
+
+static char print_buffer[512];
+
+/* destination buffer for utf8_encode_to_buf and utf8_decode */
+static char conv_buffer[512];
+
+/* one character can take up to 4 bytes in UTF-8 */
+#define print_buffer_max_width (sizeof(print_buffer) / 4 - 1)
+
+/* used for messages to the client */
+static int client_fd = -1;
+
+static char tcap_buffer[64];
+static const char *t_ts;
+static const char *t_fs;
+
+static int tree_win_x = 0;
+static int tree_win_y = 0;
+static int tree_win_w = 0;
+
+static int track_win_x = 0;
+static int track_win_y = 0;
+static int track_win_w = 0;
+
+static int show_cursor;
+static int cursor_x;
+static int cursor_y;
+
+static char *title_buf = NULL;
+
+enum {
+ CURSED_WIN,
+ CURSED_WIN_CUR,
+ CURSED_WIN_SEL,
+ CURSED_WIN_SEL_CUR,
+
+ CURSED_WIN_ACTIVE,
+ CURSED_WIN_ACTIVE_CUR,
+ CURSED_WIN_ACTIVE_SEL,
+ CURSED_WIN_ACTIVE_SEL_CUR,
+
+ CURSED_SEPARATOR,
+ CURSED_WIN_TITLE,
+ CURSED_COMMANDLINE,
+ CURSED_STATUSLINE,
+
+ CURSED_TITLELINE,
+ CURSED_DIR,
+ CURSED_ERROR,
+ CURSED_INFO,
+
+ NR_CURSED
+};
+
+static unsigned char cursed_to_bg_idx[NR_CURSED] = {
+ COLOR_WIN_BG,
+ COLOR_WIN_BG,
+ COLOR_WIN_INACTIVE_SEL_BG,
+ COLOR_WIN_INACTIVE_CUR_SEL_BG,
+
+ COLOR_WIN_BG,
+ COLOR_WIN_BG,
+ COLOR_WIN_SEL_BG,
+ COLOR_WIN_CUR_SEL_BG,
+
+ COLOR_WIN_BG,
+ COLOR_WIN_TITLE_BG,
+ COLOR_CMDLINE_BG,
+ COLOR_STATUSLINE_BG,
+
+ COLOR_TITLELINE_BG,
+ COLOR_WIN_BG,
+ COLOR_CMDLINE_BG,
+ COLOR_CMDLINE_BG
+};
+
+static unsigned char cursed_to_fg_idx[NR_CURSED] = {
+ COLOR_WIN_FG,
+ COLOR_WIN_CUR,
+ COLOR_WIN_INACTIVE_SEL_FG,
+ COLOR_WIN_INACTIVE_CUR_SEL_FG,
+
+ COLOR_WIN_FG,
+ COLOR_WIN_CUR,
+ COLOR_WIN_SEL_FG,
+ COLOR_WIN_CUR_SEL_FG,
+
+ COLOR_SEPARATOR,
+ COLOR_WIN_TITLE_FG,
+ COLOR_CMDLINE_FG,
+ COLOR_STATUSLINE_FG,
+
+ COLOR_TITLELINE_FG,
+ COLOR_WIN_DIR,
+ COLOR_ERROR,
+ COLOR_INFO
+};
+
+static unsigned char cursed_to_attr_idx[NR_CURSED] = {
+ COLOR_WIN_ATTR,
+ COLOR_WIN_ATTR,
+ COLOR_WIN_INACTIVE_SEL_ATTR,
+ COLOR_WIN_INACTIVE_CUR_SEL_ATTR,
+
+ COLOR_WIN_ATTR,
+ COLOR_WIN_ATTR,
+ COLOR_WIN_SEL_ATTR,
+ COLOR_WIN_CUR_SEL_ATTR,
+
+ COLOR_WIN_ATTR,
+ COLOR_WIN_TITLE_ATTR,
+ COLOR_CMDLINE_ATTR,
+ COLOR_STATUSLINE_ATTR,
+
+ COLOR_TITLELINE_ATTR,
+ COLOR_WIN_ATTR,
+ COLOR_CMDLINE_ATTR,
+ COLOR_CMDLINE_ATTR
+};
+
+/* index is CURSED_*, value is fucking color pair */
+static int pairs[NR_CURSED];
+
+enum {
+ TF_ALBUMARTIST,
+ TF_ARTIST,
+ TF_ALBUM,
+ TF_DISC,
+ TF_TRACK,
+ TF_TITLE,
+ TF_YEAR,
+ TF_ORIGINALYEAR,
+ TF_GENRE,
+ TF_COMMENT,
+ TF_DURATION,
+ TF_BITRATE,
+ TF_CODEC,
+ TF_CODEC_PROFILE,
+ TF_PATHFILE,
+ TF_FILE,
+ TF_RG_TRACK_GAIN,
+ TF_RG_TRACK_PEAK,
+ TF_RG_ALBUM_GAIN,
+ TF_RG_ALBUM_PEAK,
+ TF_ARRANGER,
+ TF_COMPOSER,
+ TF_CONDUCTOR,
+ TF_LYRICIST,
+ TF_PERFORMER,
+ TF_REMIXER,
+ TF_LABEL,
+ TF_PUBLISHER,
+ TF_WORK,
+ TF_OPUS,
+ TF_PARTNUMBER,
+ TF_PART,
+ TF_SUBTITLE,
+ TF_MEDIA,
+ NR_TFS
+};
+
+static struct format_option track_fopts[NR_TFS + 1] = {
+ DEF_FO_STR('A', "albumartist", 0),
+ DEF_FO_STR('a', "artist", 0),
+ DEF_FO_STR('l', "album", 0),
+ DEF_FO_INT('D', "discnumber", 1),
+ DEF_FO_INT('n', "tracknumber", 1),
+ DEF_FO_STR('t', "title", 0),
+ DEF_FO_STR('y', "date", 1),
+ DEF_FO_STR('\0', "originaldate", 1),
+ DEF_FO_STR('g', "genre", 0),
+ DEF_FO_STR('c', "comment", 0),
+ DEF_FO_TIME('d', "duration", 0),
+ DEF_FO_INT('\0', "bitrate", 0),
+ DEF_FO_STR('\0', "codec", 0),
+ DEF_FO_STR('\0', "codec_profile", 0),
+ DEF_FO_STR('f', "path", 0),
+ DEF_FO_STR('F', "filename", 0),
+ DEF_FO_DOUBLE('\0', "rg_track_gain", 0),
+ DEF_FO_DOUBLE('\0', "rg_track_peak", 0),
+ DEF_FO_DOUBLE('\0', "rg_album_gain", 0),
+ DEF_FO_DOUBLE('\0', "rg_album_peak", 0),
+ DEF_FO_STR('\0', "arranger", 0),
+ DEF_FO_STR('\0', "composer", 0),
+ DEF_FO_STR('\0', "conductor", 0),
+ DEF_FO_STR('\0', "lyricist", 0),
+ DEF_FO_STR('\0', "performer", 0),
+ DEF_FO_STR('\0', "remixer", 0),
+ DEF_FO_STR('\0', "label", 0),
+ DEF_FO_STR('\0', "publisher", 0),
+ DEF_FO_STR('\0', "work", 0),
+ DEF_FO_STR('\0', "opus", 0),
+ DEF_FO_STR('\0', "partnumber", 0),
+ DEF_FO_STR('\0', "part", 0),
+ DEF_FO_STR('\0', "subtitle", 0),
+ DEF_FO_STR('\0', "media", 0),
+ DEF_FO_END
+};
+
+enum {
+ SF_STATUS,
+ SF_POSITION,
+ SF_DURATION,
+ SF_TOTAL,
+ SF_VOLUME,
+ SF_LVOLUME,
+ SF_RVOLUME,
+ SF_BUFFER,
+ SF_REPEAT,
+ SF_CONTINUE,
+ SF_SHUFFLE,
+ SF_PLAYLISTMODE,
+ SF_BITRATE,
+ NR_SFS
+};
+
+static struct format_option status_fopts[NR_SFS + 1] = {
+ DEF_FO_STR('s', NULL, 0),
+ DEF_FO_TIME('p', NULL, 0),
+ DEF_FO_TIME('d', NULL, 0),
+ DEF_FO_TIME('t', NULL, 0),
+ DEF_FO_INT('v', NULL, 0),
+ DEF_FO_INT('l', NULL, 0),
+ DEF_FO_INT('r', NULL, 0),
+ DEF_FO_INT('b', NULL, 0),
+ DEF_FO_STR('R', NULL, 0),
+ DEF_FO_STR('C', NULL, 0),
+ DEF_FO_STR('S', NULL, 0),
+ DEF_FO_STR('L', NULL, 0),
+ DEF_FO_INT('B', NULL, 0),
+ DEF_FO_END
+};
+
+int track_format_valid(const char *format)
+{
+ return format_valid(format, track_fopts);
+}
+
+static void utf8_encode_to_buf(const char *buffer)
+{
+ int n;
+#ifdef HAVE_ICONV
+ static iconv_t cd = (iconv_t)-1;
+ size_t is, os;
+ const char *i;
+ char *o;
+ int rc;
+
+ if (cd == (iconv_t)-1) {
+ d_print("iconv_open(UTF-8, %s)\n", charset);
+ cd = iconv_open("UTF-8", charset);
+ if (cd == (iconv_t)-1) {
+ d_print("iconv_open failed: %s\n", strerror(errno));
+ goto fallback;
+ }
+ }
+ i = buffer;
+ o = conv_buffer;
+ is = strlen(i);
+ os = sizeof(conv_buffer) - 1;
+ rc = iconv(cd, (void *)&i, &is, &o, &os);
+ *o = 0;
+ if (rc == -1) {
+ d_print("iconv failed: %s\n", strerror(errno));
+ goto fallback;
+ }
+ return;
+fallback:
+#endif
+ n = min(sizeof(conv_buffer) - 1, strlen(buffer));
+ memmove(conv_buffer, buffer, n);
+ conv_buffer[n] = '\0';
+}
+
+static void utf8_decode(const char *buffer)
+{
+ int n;
+#ifdef HAVE_ICONV
+ static iconv_t cd = (iconv_t)-1;
+ size_t is, os;
+ const char *i;
+ char *o;
+ int rc;
+
+ if (cd == (iconv_t)-1) {
+ d_print("iconv_open(%s, UTF-8)\n", charset);
+ cd = iconv_open(charset, "UTF-8");
+ if (cd == (iconv_t)-1) {
+ d_print("iconv_open failed: %s\n", strerror(errno));
+ goto fallback;
+ }
+ }
+ i = buffer;
+ o = conv_buffer;
+ is = strlen(i);
+ os = sizeof(conv_buffer) - 1;
+ rc = iconv(cd, (void *)&i, &is, &o, &os);
+ *o = 0;
+ if (rc == -1) {
+ d_print("iconv failed: %s\n", strerror(errno));
+ goto fallback;
+ }
+ return;
+fallback:
+#endif
+ n = u_to_ascii(conv_buffer, buffer, sizeof(conv_buffer) - 1);
+ conv_buffer[n] = '\0';
+}
+
+/* screen updates {{{ */
+
+static void dump_print_buffer(int row, int col)
+{
+ if (using_utf8) {
+ (void) mvaddstr(row, col, print_buffer);
+ } else {
+ utf8_decode(print_buffer);
+ (void) mvaddstr(row, col, conv_buffer);
+ }
+}
+
+/* print @str into @buf
+ *
+ * if @str is shorter than @width pad with spaces
+ * if @str is wider than @width truncate and add "..."
+ */
+static int format_str(char *buf, const char *str, int width)
+{
+ int s = 0, d = 0, ellipsis_pos = 0, cut_double_width = 0;
+
+ while (1) {
+ uchar u;
+ int w;
+
+ u = u_get_char(str, &s);
+ if (u == 0) {
+ memset(buf + d, ' ', width);
+ d += width;
+ break;
+ }
+
+ w = u_char_width(u);
+ if (width == 3)
+ ellipsis_pos = d;
+ if (width == 4 && w == 2) {
+ /* can't cut double-width char */
+ ellipsis_pos = d + 1;
+ cut_double_width = 1;
+ }
+
+ width -= w;
+ if (width < 0) {
+ /* does not fit */
+ d = ellipsis_pos;
+ if (cut_double_width) {
+ /* first half of the double-width char */
+ buf[d - 1] = ' ';
+ }
+ buf[d++] = '.';
+ buf[d++] = '.';
+ buf[d++] = '.';
+ break;
+ }
+ u_set_char(buf, &d, u);
+ }
+ return d;
+}
+
+static void sprint(int row, int col, const char *str, int width)
+{
+ int pos = 0;
+
+ print_buffer[pos++] = ' ';
+ pos += format_str(print_buffer + pos, str, width - 2);
+ print_buffer[pos++] = ' ';
+ print_buffer[pos] = 0;
+ dump_print_buffer(row, col);
+}
+
+static void sprint_ascii(int row, int col, const char *str, int len)
+{
+ int l;
+
+ l = strlen(str);
+ len -= 2;
+
+ print_buffer[0] = ' ';
+ if (l > len) {
+ memcpy(print_buffer + 1, str, len - 3);
+ print_buffer[len - 2] = '.';
+ print_buffer[len - 1] = '.';
+ print_buffer[len - 0] = '.';
+ } else {
+ memcpy(print_buffer + 1, str, l);
+ memset(print_buffer + 1 + l, ' ', len - l);
+ }
+ print_buffer[len + 1] = ' ';
+ print_buffer[len + 2] = 0;
+ (void) mvaddstr(row, col, print_buffer);
+}
+
+static void print_tree(struct window *win, int row, struct iter *iter)
+{
+ const char *str;
+ struct artist *artist;
+ struct album *album;
+ struct iter sel;
+ int current, selected, active, pos;
+
+ artist = iter_to_artist(iter);
+ album = iter_to_album(iter);
+ current = 0;
+ if (lib_cur_track) {
+ if (album) {
+ current = CUR_ALBUM == album;
+ } else {
+ current = CUR_ARTIST == artist;
+ }
+ }
+ window_get_sel(win, &sel);
+ selected = iters_equal(iter, &sel);
+ active = lib_cur_win == lib_tree_win;
+ bkgdset(pairs[(active << 2) | (selected << 1) | current]);
+
+ if (active && selected) {
+ cursor_x = 0;
+ cursor_y = 1 + row;
+ }
+
+ pos = 0;
+ print_buffer[pos++] = ' ';
+ if (album) {
+ print_buffer[pos++] = ' ';
+ print_buffer[pos++] = ' ';
+ str = album->name;
+ } else {
+ if (display_artist_sort_name)
+ str = artist_sort_name(artist);
+ else
+ str = artist->name;
+ }
+ pos += format_str(print_buffer + pos, str, tree_win_w - pos - 1);
+ print_buffer[pos++] = ' ';
+ print_buffer[pos++] = 0;
+ dump_print_buffer(tree_win_y + row + 1, tree_win_x);
+}
+
+static inline void fopt_set_str(struct format_option *fopt, const char *str)
+{
+ BUG_ON(fopt->type != FO_STR);
+ if (str) {
+ fopt->fo_str = str;
+ fopt->empty = 0;
+ } else {
+ fopt->empty = 1;
+ }
+}
+
+static inline void fopt_set_int(struct format_option *fopt, int value, int empty)
+{
+ BUG_ON(fopt->type != FO_INT);
+ fopt->fo_int = value;
+ fopt->empty = empty;
+}
+
+static inline void fopt_set_double(struct format_option *fopt, double value, int empty)
+{
+ BUG_ON(fopt->type != FO_DOUBLE);
+ fopt->fo_double = value;
+ fopt->empty = empty;
+}
+
+static inline void fopt_set_time(struct format_option *fopt, int value, int empty)
+{
+ BUG_ON(fopt->type != FO_TIME);
+ fopt->fo_time = value;
+ fopt->empty = empty;
+}
+
+static void fill_track_fopts_track_info(struct track_info *info)
+{
+ char *filename;
+
+ if (using_utf8) {
+ filename = info->filename;
+ } else {
+ utf8_encode_to_buf(info->filename);
+ filename = conv_buffer;
+ }
+
+ fopt_set_str(&track_fopts[TF_ALBUMARTIST], info->albumartist);
+ fopt_set_str(&track_fopts[TF_ARTIST], info->artist);
+ fopt_set_str(&track_fopts[TF_ALBUM], info->album);
+ fopt_set_int(&track_fopts[TF_DISC], info->discnumber, info->discnumber == -1);
+ fopt_set_int(&track_fopts[TF_TRACK], info->tracknumber, info->tracknumber == -1);
+ fopt_set_str(&track_fopts[TF_TITLE], info->title);
+ fopt_set_str(&track_fopts[TF_YEAR], keyvals_get_val(info->comments, "date"));
+ fopt_set_str(&track_fopts[TF_GENRE], info->genre);
+ fopt_set_str(&track_fopts[TF_COMMENT], info->comment);
+ fopt_set_time(&track_fopts[TF_DURATION], info->duration, info->duration == -1);
+ fopt_set_double(&track_fopts[TF_RG_TRACK_GAIN], info->rg_track_gain, isnan(info->rg_track_gain));
+ fopt_set_double(&track_fopts[TF_RG_TRACK_PEAK], info->rg_track_peak, isnan(info->rg_track_peak));
+ fopt_set_double(&track_fopts[TF_RG_ALBUM_GAIN], info->rg_album_gain, isnan(info->rg_album_gain));
+ fopt_set_double(&track_fopts[TF_RG_ALBUM_PEAK], info->rg_album_peak, isnan(info->rg_album_peak));
+ fopt_set_str(&track_fopts[TF_ORIGINALYEAR], keyvals_get_val(info->comments, "originaldate"));
+ fopt_set_int(&track_fopts[TF_BITRATE], (int) (info->bitrate / 1000. + 0.5), info->bitrate == -1);
+ fopt_set_str(&track_fopts[TF_CODEC], info->codec);
+ fopt_set_str(&track_fopts[TF_CODEC_PROFILE], info->codec_profile);
+ fopt_set_str(&track_fopts[TF_PATHFILE], filename);
+ fopt_set_str(&track_fopts[TF_ARRANGER], keyvals_get_val(info->comments, "arranger"));
+ fopt_set_str(&track_fopts[TF_COMPOSER], keyvals_get_val(info->comments, "composer"));
+ fopt_set_str(&track_fopts[TF_CONDUCTOR], keyvals_get_val(info->comments, "conductor"));
+ fopt_set_str(&track_fopts[TF_LYRICIST], keyvals_get_val(info->comments, "lyricist"));
+ fopt_set_str(&track_fopts[TF_PERFORMER], keyvals_get_val(info->comments, "performer"));
+ fopt_set_str(&track_fopts[TF_REMIXER], keyvals_get_val(info->comments, "remixer"));
+ fopt_set_str(&track_fopts[TF_LABEL], keyvals_get_val(info->comments, "label"));
+ fopt_set_str(&track_fopts[TF_PUBLISHER], keyvals_get_val(info->comments, "publisher"));
+ fopt_set_str(&track_fopts[TF_WORK], keyvals_get_val(info->comments, "work"));
+ fopt_set_str(&track_fopts[TF_OPUS], keyvals_get_val(info->comments, "opus"));
+ fopt_set_str(&track_fopts[TF_PARTNUMBER], keyvals_get_val(info->comments, "partnumber"));
+ fopt_set_str(&track_fopts[TF_PART], keyvals_get_val(info->comments, "part"));
+ fopt_set_str(&track_fopts[TF_SUBTITLE], keyvals_get_val(info->comments, "subtitle"));
+ fopt_set_str(&track_fopts[TF_MEDIA], keyvals_get_val(info->comments, "media"));
+ if (is_http_url(info->filename)) {
+ fopt_set_str(&track_fopts[TF_FILE], filename);
+ } else {
+ fopt_set_str(&track_fopts[TF_FILE], path_basename(filename));
+ }
+}
+
+static void print_track(struct window *win, int row, struct iter *iter)
+{
+ struct tree_track *track;
+ struct track_info *ti;
+ struct iter sel;
+ int current, selected, active;
+ const char *format;
+
+ track = iter_to_tree_track(iter);
+ current = lib_cur_track == track;
+ window_get_sel(win, &sel);
+ selected = iters_equal(iter, &sel);
+ active = lib_cur_win == lib_track_win;
+ bkgdset(pairs[(active << 2) | (selected << 1) | current]);
+
+ if (active && selected) {
+ cursor_x = track_win_x;
+ cursor_y = 1 + row;
+ }
+
+ ti = tree_track_info(track);
+ fill_track_fopts_track_info(ti);
+
+ if (track_info_has_tag(ti)) {
+ if (track_is_compilation(ti->comments))
+ format = track_win_format_va;
+ else
+ format = track_win_format;
+
+ format_print(print_buffer, track_win_w, format, track_fopts);
+ } else {
+ format_print(print_buffer, track_win_w, track_win_alt_format, track_fopts);
+ }
+ dump_print_buffer(track_win_y + row + 1, track_win_x);
+}
+
+/* used by print_editable only */
+static struct simple_track *current_track;
+
+static void print_editable(struct window *win, int row, struct iter *iter)
+{
+ struct simple_track *track;
+ struct iter sel;
+ int current, selected, active;
+ const char *format;
+
+ track = iter_to_simple_track(iter);
+ current = current_track == track;
+ window_get_sel(win, &sel);
+ selected = iters_equal(iter, &sel);
+
+ if (selected) {
+ cursor_x = 0;
+ cursor_y = 1 + row;
+ }
+
+ active = 1;
+ if (!selected && track->marked) {
+ selected = 1;
+ active = 0;
+ }
+
+ bkgdset(pairs[(active << 2) | (selected << 1) | current]);
+
+ fill_track_fopts_track_info(track->info);
+
+ if (track_info_has_tag(track->info)) {
+ if (track_is_compilation(track->info->comments))
+ format = list_win_format_va;
+ else
+ format = list_win_format;
+
+ format_print(print_buffer, COLS, format, track_fopts);
+ } else {
+ format_print(print_buffer, COLS, list_win_alt_format, track_fopts);
+ }
+ dump_print_buffer(row + 1, 0);
+}
+
+static void print_browser(struct window *win, int row, struct iter *iter)
+{
+ struct browser_entry *e;
+ struct iter sel;
+ int selected;
+
+ e = iter_to_browser_entry(iter);
+ window_get_sel(win, &sel);
+ selected = iters_equal(iter, &sel);
+ if (selected) {
+ int active = 1;
+ int current = 0;
+
+ bkgdset(pairs[(active << 2) | (selected << 1) | current]);
+ } else {
+ if (e->type == BROWSER_ENTRY_DIR) {
+ bkgdset(pairs[CURSED_DIR]);
+ } else {
+ bkgdset(pairs[CURSED_WIN]);
+ }
+ }
+
+ if (selected) {
+ cursor_x = 0;
+ cursor_y = 1 + row;
+ }
+
+ /* file name encoding == terminal encoding. no need to convert */
+ if (using_utf8) {
+ sprint(row + 1, 0, e->name, COLS);
+ } else {
+ sprint_ascii(row + 1, 0, e->name, COLS);
+ }
+}
+
+static void print_filter(struct window *win, int row, struct iter *iter)
+{
+ char buf[256];
+ struct filter_entry *e = iter_to_filter_entry(iter);
+ struct iter sel;
+ /* window active? */
+ int active = 1;
+ /* row selected? */
+ int selected;
+ /* is the filter currently active? */
+ int current = !!e->act_stat;
+ const char stat_chars[3] = " *!";
+ int ch1, ch2, ch3, pos;
+ const char *e_filter;
+
+ window_get_sel(win, &sel);
+ selected = iters_equal(iter, &sel);
+ bkgdset(pairs[(active << 2) | (selected << 1) | current]);
+
+ if (selected) {
+ cursor_x = 0;
+ cursor_y = 1 + row;
+ }
+
+ ch1 = ' ';
+ ch3 = ' ';
+ if (e->sel_stat != e->act_stat) {
+ ch1 = '[';
+ ch3 = ']';
+ }
+ ch2 = stat_chars[e->sel_stat];
+
+ e_filter = e->filter;
+ if (!using_utf8) {
+ utf8_encode_to_buf(e_filter);
+ e_filter = conv_buffer;
+ }
+
+ snprintf(buf, sizeof(buf), "%c%c%c%-15s %s", ch1, ch2, ch3, e->name, e_filter);
+ pos = format_str(print_buffer, buf, COLS - 1);
+ print_buffer[pos++] = ' ';
+ print_buffer[pos] = 0;
+ dump_print_buffer(row + 1, 0);
+}
+
+static void print_help(struct window *win, int row, struct iter *iter)
+{
+ struct iter sel;
+ int selected;
+ int pos;
+ int active = 1;
+ char buf[512];
+ const struct help_entry *e = iter_to_help_entry(iter);
+ const struct cmus_opt *opt;
+
+ window_get_sel(win, &sel);
+ selected = iters_equal(iter, &sel);
+ bkgdset(pairs[(active << 2) | (selected << 1)]);
+
+ if (selected) {
+ cursor_x = 0;
+ cursor_y = 1 + row;
+ }
+
+ switch (e->type) {
+ case HE_TEXT:
+ snprintf(buf, sizeof(buf), " %s", e->text);
+ break;
+ case HE_BOUND:
+ snprintf(buf, sizeof(buf), " %-8s %-14s %s",
+ key_context_names[e->binding->ctx],
+ e->binding->key->name,
+ e->binding->cmd);
+ break;
+ case HE_UNBOUND:
+ snprintf(buf, sizeof(buf), " %s", e->command->name);
+ break;
+ case HE_OPTION:
+ opt = e->option;
+ snprintf(buf, sizeof(buf), " %-29s ", opt->name);
+ opt->get(opt->id, buf + strlen(buf));
+ break;
+ }
+ pos = format_str(print_buffer, buf, COLS - 1);
+ print_buffer[pos++] = ' ';
+ print_buffer[pos] = 0;
+ dump_print_buffer(row + 1, 0);
+}
+
+static void update_window(struct window *win, int x, int y, int w, const char *title,
+ void (*print)(struct window *, int, struct iter *))
+{
+ struct iter iter;
+ int nr_rows;
+ int c, i;
+
+ win->changed = 0;
+
+ bkgdset(pairs[CURSED_WIN_TITLE]);
+ c = snprintf(print_buffer, w + 1, " %s", title);
+ if (c > w)
+ c = w;
+ memset(print_buffer + c, ' ', w - c + 1);
+ print_buffer[w] = 0;
+ dump_print_buffer(y, x);
+ nr_rows = window_get_nr_rows(win);
+ i = 0;
+ if (window_get_top(win, &iter)) {
+ while (i < nr_rows) {
+ print(win, i, &iter);
+ i++;
+ if (!window_get_next(win, &iter))
+ break;
+ }
+ }
+
+ bkgdset(pairs[0]);
+ memset(print_buffer, ' ', w);
+ print_buffer[w] = 0;
+ while (i < nr_rows) {
+ dump_print_buffer(y + i + 1, x);
+ i++;
+ }
+}
+
+static void update_tree_window(void)
+{
+ update_window(lib_tree_win, tree_win_x, tree_win_y,
+ tree_win_w, "Artist / Album", print_tree);
+}
+
+static void update_track_window(void)
+{
+ char title[512];
+
+ /* it doesn't matter what format options we use because the format
+ * string does not contain any format charaters */
+ format_print(title, track_win_w - 2, "Track%=Library", track_fopts);
+ update_window(lib_track_win, track_win_x, track_win_y,
+ track_win_w, title, print_track);
+}
+
+static const char *pretty(const char *path)
+{
+ static int home_len = -1;
+ static char buf[256];
+
+ if (home_len == -1)
+ home_len = strlen(home_dir);
+
+ if (strncmp(path, home_dir, home_len) || path[home_len] != '/')
+ return path;
+
+ buf[0] = '~';
+ strcpy(buf + 1, path + home_len);
+ return buf;
+}
+
+static const char * const sorted_names[2] = { "", "sorted by " };
+
+static void update_editable_window(struct editable *e, const char *title, const char *filename)
+{
+ char buf[512];
+ int pos;
+
+ if (filename) {
+ if (using_utf8) {
+ /* already UTF-8 */
+ } else {
+ utf8_encode_to_buf(filename);
+ filename = conv_buffer;
+ }
+ snprintf(buf, sizeof(buf), "%s %s - %d tracks", title,
+ pretty(filename), e->nr_tracks);
+ } else {
+ snprintf(buf, sizeof(buf), "%s - %d tracks", title, e->nr_tracks);
+ }
+
+ if (e->nr_marked) {
+ pos = strlen(buf);
+ snprintf(buf + pos, sizeof(buf) - pos, " (%d marked)", e->nr_marked);
+ }
+ pos = strlen(buf);
+ snprintf(buf + pos, sizeof(buf) - pos, " %s%s",
+ sorted_names[e->sort_str[0] != 0], e->sort_str);
+
+ update_window(e->win, 0, 0, COLS, buf, &print_editable);
+}
+
+static void update_sorted_window(void)
+{
+ current_track = (struct simple_track *)lib_cur_track;
+ update_editable_window(&lib_editable, "Library", lib_filename);
+}
+
+static void update_pl_window(void)
+{
+ current_track = pl_cur_track;
+ update_editable_window(&pl_editable, "Playlist", pl_filename);
+}
+
+static void update_play_queue_window(void)
+{
+ current_track = NULL;
+ update_editable_window(&pq_editable, "Play Queue", NULL);
+}
+
+static void update_browser_window(void)
+{
+ char title[512];
+ char *dirname;
+
+ if (using_utf8) {
+ /* already UTF-8 */
+ dirname = browser_dir;
+ } else {
+ utf8_encode_to_buf(browser_dir);
+ dirname = conv_buffer;
+ }
+ snprintf(title, sizeof(title), "Browser - %s", dirname);
+ update_window(browser_win, 0, 0, COLS, title, print_browser);
+}
+
+static void update_filters_window(void)
+{
+ update_window(filters_win, 0, 0, COLS, "Library Filters", print_filter);
+}
+
+static void update_help_window(void)
+{
+ update_window(help_win, 0, 0, COLS, "Settings", print_help);
+}
+
+static void draw_separator(void)
+{
+ int row;
+
+ bkgdset(pairs[CURSED_WIN_TITLE]);
+ (void) mvaddch(0, tree_win_w, ' ');
+ bkgdset(pairs[CURSED_SEPARATOR]);
+ for (row = 1; row < LINES - 3; row++)
+ (void) mvaddch(row, tree_win_w, ACS_VLINE);
+}
+
+static void do_update_view(int full)
+{
+ cursor_x = -1;
+ cursor_y = -1;
+
+ switch (cur_view) {
+ case TREE_VIEW:
+ editable_lock();
+ if (full || lib_tree_win->changed)
+ update_tree_window();
+ if (full || lib_track_win->changed)
+ update_track_window();
+ editable_unlock();
+ draw_separator();
+ update_filterline();
+ break;
+ case SORTED_VIEW:
+ editable_lock();
+ update_sorted_window();
+ editable_unlock();
+ update_filterline();
+ break;
+ case PLAYLIST_VIEW:
+ editable_lock();
+ update_pl_window();
+ editable_unlock();
+ break;
+ case QUEUE_VIEW:
+ editable_lock();
+ update_play_queue_window();
+ editable_unlock();
+ break;
+ case BROWSER_VIEW:
+ update_browser_window();
+ break;
+ case FILTERS_VIEW:
+ update_filters_window();
+ break;
+ case HELP_VIEW:
+ update_help_window();
+ break;
+ }
+}
+
+static void do_update_statusline(void)
+{
+ static const char *status_strs[] = { ".", ">", "|" };
+ static const char *cont_strs[] = { " ", "C" };
+ static const char *repeat_strs[] = { " ", "R" };
+ static const char *shuffle_strs[] = { " ", "S" };
+ int buffer_fill, vol, vol_left, vol_right;
+ int duration = -1;
+ char *msg;
+ char format[80];
+
+ editable_lock();
+ fopt_set_time(&status_fopts[SF_TOTAL], play_library ? lib_editable.total_time :
+ pl_editable.total_time, 0);
+ editable_unlock();
+
+ fopt_set_str(&status_fopts[SF_REPEAT], repeat_strs[repeat]);
+ fopt_set_str(&status_fopts[SF_SHUFFLE], shuffle_strs[shuffle]);
+ fopt_set_str(&status_fopts[SF_PLAYLISTMODE], aaa_mode_names[aaa_mode]);
+
+ player_info_lock();
+
+ if (player_info.ti)
+ duration = player_info.ti->duration;
+
+ vol_left = vol_right = vol = -1;
+ if (soft_vol) {
+ vol_left = soft_vol_l;
+ vol_right = soft_vol_r;
+ vol = (vol_left + vol_right + 1) / 2;
+ } else if (volume_max && volume_l >= 0 && volume_r >= 0) {
+ vol_left = scale_to_percentage(volume_l, volume_max);
+ vol_right = scale_to_percentage(volume_r, volume_max);
+ vol = (vol_left + vol_right + 1) / 2;
+ }
+ buffer_fill = scale_to_percentage(player_info.buffer_fill, player_info.buffer_size);
+
+ fopt_set_str(&status_fopts[SF_STATUS], status_strs[player_info.status]);
+
+ if (show_remaining_time && duration != -1) {
+ fopt_set_time(&status_fopts[SF_POSITION], player_info.pos - duration, 0);
+ } else {
+ fopt_set_time(&status_fopts[SF_POSITION], player_info.pos, 0);
+ }
+
+ fopt_set_time(&status_fopts[SF_DURATION], duration, 0);
+ fopt_set_int(&status_fopts[SF_VOLUME], vol, 0);
+ fopt_set_int(&status_fopts[SF_LVOLUME], vol_left, 0);
+ fopt_set_int(&status_fopts[SF_RVOLUME], vol_right, 0);
+ fopt_set_int(&status_fopts[SF_BUFFER], buffer_fill, 0);
+ fopt_set_str(&status_fopts[SF_CONTINUE], cont_strs[player_cont]);
+ fopt_set_int(&status_fopts[SF_BITRATE], player_info.current_bitrate / 1000. + 0.5, 0);
+
+ strcpy(format, " %s %p ");
+ if (duration != -1)
+ strcat(format, "/ %d ");
+ strcat(format, "- %t ");
+ if (vol >= 0) {
+ if (vol_left != vol_right) {
+ strcat(format, "vol: %l,%r ");
+ } else {
+ strcat(format, "vol: %v ");
+ }
+ }
+ if (player_info.ti) {
+ if (is_http_url(player_info.ti->filename))
+ strcat(format, "buf: %b ");
+ if (show_current_bitrate && player_info.current_bitrate >= 0)
+ strcat(format, " %B kbps ");
+ }
+ strcat(format, "%=");
+ if (player_repeat_current) {
+ strcat(format, "repeat current");
+ } else if (play_library) {
+ /* artist/album modes work only in lib */
+ if (shuffle) {
+ /* shuffle overrides sorted mode */
+ strcat(format, "%L from library");
+ } else if (play_sorted) {
+ strcat(format, "%L from sorted library");
+ } else {
+ strcat(format, "%L from library");
+ }
+ } else {
+ strcat(format, "playlist");
+ }
+ strcat(format, " | %1C%1R%1S ");
+ format_print(print_buffer, COLS, format, status_fopts);
+
+ msg = player_info.error_msg;
+ player_info.error_msg = NULL;
+
+ player_info_unlock();
+
+ bkgdset(pairs[CURSED_STATUSLINE]);
+ dump_print_buffer(LINES - 2, 0);
+
+ if (msg) {
+ error_msg("%s", msg);
+ free(msg);
+ }
+}
+
+static void dump_buffer(const char *buffer)
+{
+ if (using_utf8) {
+ addstr(buffer);
+ } else {
+ utf8_decode(buffer);
+ addstr(conv_buffer);
+ }
+}
+
+static void do_update_commandline(void)
+{
+ char *str;
+ int w, idx;
+ char ch;
+
+ move(LINES - 1, 0);
+ if (error_buf[0]) {
+ if (msg_is_error) {
+ bkgdset(pairs[CURSED_ERROR]);
+ } else {
+ bkgdset(pairs[CURSED_INFO]);
+ }
+ addstr(error_buf);
+ clrtoeol();
+ return;
+ }
+ bkgdset(pairs[CURSED_COMMANDLINE]);
+ if (input_mode == NORMAL_MODE) {
+ clrtoeol();
+ return;
+ }
+
+ str = cmdline.line;
+ if (!using_utf8) {
+ /* cmdline.line actually pretends to be UTF-8 but all non-ASCII
+ * characters are invalid UTF-8 so it really is in locale's
+ * encoding.
+ *
+ * This code should be safe because cmdline.bpos ==
+ * cmdline.cpos as every non-ASCII character is counted as one
+ * invalid UTF-8 byte.
+ *
+ * NOTE: This has nothing to do with widths of printed
+ * characters. I.e. even if there were control characters
+ * (displayed as <xx>) there would be no problem because bpos
+ * still equals to cpos, I think.
+ */
+ utf8_encode_to_buf(cmdline.line);
+ str = conv_buffer;
+ }
+
+ /* COMMAND_MODE or SEARCH_MODE */
+ w = u_str_width(str);
+ ch = ':';
+ if (input_mode == SEARCH_MODE)
+ ch = search_direction == SEARCH_FORWARD ? '/' : '?';
+
+ if (w <= COLS - 2) {
+ addch(ch);
+ idx = u_copy_chars(print_buffer, str, &w);
+ print_buffer[idx] = 0;
+ dump_buffer(print_buffer);
+ clrtoeol();
+ } else {
+ /* keep cursor as far right as possible */
+ int skip, width, cw;
+
+ /* cursor pos (width, not chars. doesn't count the ':') */
+ cw = u_str_nwidth(str, cmdline.cpos);
+
+ skip = cw + 2 - COLS;
+ if (skip > 0) {
+ /* skip the ':' */
+ skip--;
+
+ /* skip rest (if any) */
+ idx = u_skip_chars(str, &skip);
+
+ width = COLS;
+ idx = u_copy_chars(print_buffer, str + idx, &width);
+ while (width < COLS) {
+ /* cursor is at end of the buffer
+ * print 1, 2 or 3 spaces
+ *
+ * To clarify:
+ *
+ * If the last _skipped_ character was double-width we may need
+ * to print 2 spaces.
+ *
+ * If the last _skipped_ character was invalid UTF-8 we may need
+ * to print 3 spaces.
+ */
+ print_buffer[idx++] = ' ';
+ width++;
+ }
+ print_buffer[idx] = 0;
+ dump_buffer(print_buffer);
+ } else {
+ /* print ':' + COLS - 1 chars */
+ addch(ch);
+ width = COLS - 1;
+ idx = u_copy_chars(print_buffer, str, &width);
+ print_buffer[idx] = 0;
+ dump_buffer(print_buffer);
+ }
+ }
+}
+
+/* lock player_info! */
+static const char *get_stream_title(void)
+{
+ static char stream_title[255 * 16 + 1];
+ char *ptr, *title;
+
+ ptr = strstr(player_info.metadata, "StreamTitle='");
+ if (ptr == NULL)
+ return NULL;
+ ptr += 13;
+ title = ptr;
+ while (*ptr) {
+ if (*ptr == '\'' && *(ptr + 1) == ';') {
+ memcpy(stream_title, title, ptr - title);
+ stream_title[ptr - title] = 0;
+ return stream_title;
+ }
+ ptr++;
+ }
+ return NULL;
+}
+
+static void set_title(const char *title)
+{
+ if (!set_term_title)
+ return;
+
+ if (t_ts) {
+ printf("%s%s%s", tgoto(t_ts, 0, 0), title, t_fs);
+ fflush(stdout);
+ }
+}
+
+static void do_update_titleline(void)
+{
+ bkgdset(pairs[CURSED_TITLELINE]);
+ player_info_lock();
+ if (player_info.ti) {
+ int i, use_alt_format = 0;
+ char *wtitle;
+
+ fill_track_fopts_track_info(player_info.ti);
+
+ use_alt_format = !track_info_has_tag(player_info.ti);
+
+ if (is_http_url(player_info.ti->filename)) {
+ const char *title = get_stream_title();
+
+ if (title != NULL) {
+ free(title_buf);
+ title_buf = to_utf8(title, icecast_default_charset);
+ /*
+ * StreamTitle overrides radio station name
+ */
+ use_alt_format = 0;
+ fopt_set_str(&track_fopts[TF_TITLE], title_buf);
+ }
+ }
+
+ if (use_alt_format) {
+ format_print(print_buffer, COLS, current_alt_format, track_fopts);
+ } else {
+ format_print(print_buffer, COLS, current_format, track_fopts);
+ }
+ dump_print_buffer(LINES - 3, 0);
+
+ /* set window title */
+ if (use_alt_format) {
+ format_print(print_buffer, print_buffer_max_width,
+ window_title_alt_format, track_fopts);
+ } else {
+ format_print(print_buffer, print_buffer_max_width,
+ window_title_format, track_fopts);
+ }
+
+ /* remove whitespace */
+ i = strlen(print_buffer) - 1;
+ while (i > 0 && print_buffer[i] == ' ')
+ i--;
+ print_buffer[i + 1] = 0;
+
+ if (using_utf8) {
+ wtitle = print_buffer;
+ } else {
+ utf8_decode(print_buffer);
+ wtitle = conv_buffer;
+ }
+
+ set_title(wtitle);
+ } else {
+ move(LINES - 3, 0);
+ clrtoeol();
+
+ set_title("cmus " VERSION);
+ }
+ player_info_unlock();
+}
+
+static int cmdline_cursor_column(void)
+{
+ char *str;
+ int cw, skip, s;
+
+ str = cmdline.line;
+ if (!using_utf8) {
+ /* see do_update_commandline */
+ utf8_encode_to_buf(cmdline.line);
+ str = conv_buffer;
+ }
+
+ /* width of the text in the buffer before cursor */
+ cw = u_str_nwidth(str, cmdline.cpos);
+
+ if (1 + cw < COLS) {
+ /* whole line is visible */
+ return 1 + cw;
+ }
+
+ /* beginning of cmdline is not visible */
+
+ /* check if the first visible char in cmdline would be halved
+ * double-width character (or invalid byte <xx>) which is not possible.
+ * we need to skip the whole character and move cursor to COLS - 2
+ * column. */
+ skip = cw + 2 - COLS;
+
+ /* skip the ':' */
+ skip--;
+
+ /* skip rest */
+ s = skip;
+ u_skip_chars(str, &s);
+ if (s > skip) {
+ /* the last skipped char was double-width or <xx> */
+ return COLS - 1 - (s - skip);
+ }
+ return COLS - 1;
+}
+
+static void post_update(void)
+{
+ /* refresh makes cursor visible at least for urxvt */
+ if (input_mode == COMMAND_MODE || input_mode == SEARCH_MODE) {
+ move(LINES - 1, cmdline_cursor_column());
+ refresh();
+ curs_set(1);
+ } else {
+ if (cursor_x >= 0) {
+ move(cursor_y, cursor_x);
+ } else {
+ move(LINES - 1, 0);
+ }
+ refresh();
+
+ /* visible cursor is useful for screen readers */
+ if (show_cursor) {
+ curs_set(1);
+ } else {
+ curs_set(0);
+ }
+ }
+}
+
+void update_titleline(void)
+{
+ curs_set(0);
+ do_update_titleline();
+ post_update();
+}
+
+void update_full(void)
+{
+ if (!ui_initialized)
+ return;
+
+ curs_set(0);
+
+ do_update_view(1);
+ do_update_titleline();
+ do_update_statusline();
+ do_update_commandline();
+
+ post_update();
+}
+
+static void update_commandline(void)
+{
+ curs_set(0);
+ do_update_commandline();
+ post_update();
+}
+
+void update_statusline(void)
+{
+ if (!ui_initialized)
+ return;
+
+ curs_set(0);
+ do_update_statusline();
+ post_update();
+}
+
+void update_filterline(void)
+{
+ if (cur_view != TREE_VIEW && cur_view != SORTED_VIEW)
+ return;
+ if (lib_live_filter) {
+ char buf[512];
+ int w;
+ bkgdset(pairs[CURSED_STATUSLINE]);
+ snprintf(buf, sizeof(buf), "filtered: %s", lib_live_filter);
+ w = clamp(strlen(buf) + 2, COLS/4, COLS/2);
+ sprint(LINES-4, COLS-w, buf, w);
+ }
+}
+
+void info_msg(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vsnprintf(error_buf, sizeof(error_buf), format, ap);
+ va_end(ap);
+
+ if (client_fd != -1) {
+ write_all(client_fd, error_buf, strlen(error_buf));
+ write_all(client_fd, "\n", 1);
+ }
+
+ msg_is_error = 0;
+
+ update_commandline();
+}
+
+void error_msg(const char *format, ...)
+{
+ va_list ap;
+
+ strcpy(error_buf, "Error: ");
+ va_start(ap, format);
+ vsnprintf(error_buf + 7, sizeof(error_buf) - 7, format, ap);
+ va_end(ap);
+
+ d_print("%s\n", error_buf);
+ if (client_fd != -1) {
+ write_all(client_fd, error_buf, strlen(error_buf));
+ write_all(client_fd, "\n", 1);
+ }
+
+ msg_is_error = 1;
+ error_count++;
+
+ if (ui_initialized) {
+ error_time = time(NULL);
+ update_commandline();
+ } else {
+ warn("%s\n", error_buf);
+ error_buf[0] = 0;
+ }
+}
+
+int yes_no_query(const char *format, ...)
+{
+ char buffer[512];
+ va_list ap;
+ int ret = 0;
+
+ va_start(ap, format);
+ vsnprintf(buffer, sizeof(buffer), format, ap);
+ va_end(ap);
+
+ move(LINES - 1, 0);
+ bkgdset(pairs[CURSED_INFO]);
+
+ /* no need to convert buffer.
+ * it is always encoded in the right charset (assuming filenames are
+ * encoded in same charset as LC_CTYPE).
+ */
+
+ addstr(buffer);
+ clrtoeol();
+ refresh();
+
+ while (1) {
+ int ch = getch();
+
+ if (ch == ERR || ch == 0)
+ continue;
+ if (ch == 'y')
+ ret = 1;
+ break;
+ }
+ update_commandline();
+ return ret;
+}
+
+void search_not_found(void)
+{
+ const char *what = "Track";
+
+ if (search_restricted) {
+ switch (cur_view) {
+ case TREE_VIEW:
+ what = "Artist/album";
+ break;
+ case SORTED_VIEW:
+ case PLAYLIST_VIEW:
+ case QUEUE_VIEW:
+ what = "Title";
+ break;
+ case BROWSER_VIEW:
+ what = "File/Directory";
+ break;
+ case FILTERS_VIEW:
+ what = "Filter";
+ break;
+ case HELP_VIEW:
+ what = "Binding/command/option";
+ break;
+ }
+ } else {
+ switch (cur_view) {
+ case TREE_VIEW:
+ case SORTED_VIEW:
+ case PLAYLIST_VIEW:
+ case QUEUE_VIEW:
+ what = "Track";
+ break;
+ case BROWSER_VIEW:
+ what = "File/Directory";
+ break;
+ case FILTERS_VIEW:
+ what = "Filter";
+ break;
+ case HELP_VIEW:
+ what = "Binding/command/option";
+ break;
+ }
+ }
+ info_msg("%s not found: %s", what, search_str ? search_str : "");
+}
+
+void set_client_fd(int fd)
+{
+ client_fd = fd;
+}
+
+int get_client_fd(void)
+{
+ return client_fd;
+}
+
+void set_view(int view)
+{
+ if (view == cur_view)
+ return;
+
+ cur_view = view;
+ switch (cur_view) {
+ case TREE_VIEW:
+ searchable = tree_searchable;
+ break;
+ case SORTED_VIEW:
+ searchable = lib_editable.searchable;
+ break;
+ case PLAYLIST_VIEW:
+ searchable = pl_editable.searchable;
+ break;
+ case QUEUE_VIEW:
+ searchable = pq_editable.searchable;
+ break;
+ case BROWSER_VIEW:
+ searchable = browser_searchable;
+ break;
+ case FILTERS_VIEW:
+ searchable = filters_searchable;
+ break;
+ case HELP_VIEW:
+ searchable = help_searchable;
+ update_help_window();
+ break;
+ }
+
+ curs_set(0);
+ do_update_view(1);
+ post_update();
+}
+
+void enter_command_mode(void)
+{
+ error_buf[0] = 0;
+ error_time = 0;
+ input_mode = COMMAND_MODE;
+ update_commandline();
+}
+
+void enter_search_mode(void)
+{
+ error_buf[0] = 0;
+ error_time = 0;
+ input_mode = SEARCH_MODE;
+ search_direction = SEARCH_FORWARD;
+ update_commandline();
+}
+
+void enter_search_backward_mode(void)
+{
+ error_buf[0] = 0;
+ error_time = 0;
+ input_mode = SEARCH_MODE;
+ search_direction = SEARCH_BACKWARD;
+ update_commandline();
+}
+
+void update_colors(void)
+{
+ int i;
+
+ if (!ui_initialized)
+ return;
+
+ for (i = 0; i < NR_CURSED; i++) {
+ int bg = colors[cursed_to_bg_idx[i]];
+ int fg = colors[cursed_to_fg_idx[i]];
+ int attr = attrs[cursed_to_attr_idx[i]];
+ int pair = i + 1;
+
+ if (fg >= 8 && fg <= 15) {
+ /* fg colors 8..15 are special (0..7 + bold) */
+ init_pair(pair, fg & 7, bg);
+ pairs[i] = COLOR_PAIR(pair) | (fg & BRIGHT ? A_BOLD : 0) | attr;
+ } else {
+ init_pair(pair, fg, bg);
+ pairs[i] = COLOR_PAIR(pair) | attr;
+ }
+ }
+}
+
+static void clear_error(void)
+{
+ time_t t = time(NULL);
+
+ /* prevent accidental clearing of error messages */
+ if (t - error_time < 2)
+ return;
+
+ if (error_buf[0]) {
+ error_time = 0;
+ error_buf[0] = 0;
+ update_commandline();
+ }
+}
+
+/* screen updates }}} */
+
+static void spawn_status_program(void)
+{
+ enum player_status status;
+ const char *stream_title = NULL;
+ char *argv[32];
+ int i;
+
+ if (status_display_program == NULL || status_display_program[0] == 0)
+ return;
+
+ player_info_lock();
+ status = player_info.status;
+ if (status == PLAYER_STATUS_PLAYING && player_info.ti && is_http_url(player_info.ti->filename))
+ stream_title = get_stream_title();
+
+ i = 0;
+ argv[i++] = xstrdup(status_display_program);
+
+ argv[i++] = xstrdup("status");
+ argv[i++] = xstrdup(player_status_names[status]);
+ if (player_info.ti) {
+ static const char *keys[] = {
+ "artist", "album", "discnumber", "tracknumber", "title", "date",
+ "musicbrainz_trackid", NULL
+ };
+ int j;
+
+ if (is_http_url(player_info.ti->filename)) {
+ argv[i++] = xstrdup("url");
+ } else {
+ argv[i++] = xstrdup("file");
+ }
+ argv[i++] = xstrdup(player_info.ti->filename);
+
+ if (track_info_has_tag(player_info.ti)) {
+ for (j = 0; keys[j]; j++) {
+ const char *key = keys[j];
+ const char *val;
+
+ if (strcmp(key, "title") == 0 && stream_title)
+ /*
+ * StreamTitle overrides radio station name
+ */
+ val = stream_title;
+ else
+ val = keyvals_get_val(player_info.ti->comments, key);
+
+ if (val) {
+ argv[i++] = xstrdup(key);
+ argv[i++] = xstrdup(val);
+ }
+ }
+ if (player_info.ti->duration > 0) {
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%d", player_info.ti->duration);
+ argv[i++] = xstrdup("duration");
+ argv[i++] = xstrdup(buf);
+ }
+ } else if (stream_title) {
+ argv[i++] = xstrdup("title");
+ argv[i++] = xstrdup(stream_title);
+ }
+ }
+ argv[i++] = NULL;
+ player_info_unlock();
+
+ if (spawn(argv, NULL, 0) == -1)
+ error_msg("couldn't run `%s': %s", status_display_program, strerror(errno));
+ for (i = 0; argv[i]; i++)
+ free(argv[i]);
+}
+
+static int ctrl_c_pressed = 0;
+
+static void sig_int(int sig)
+{
+ ctrl_c_pressed = 1;
+}
+
+static void sig_hup(int sig)
+{
+ cmus_running = 0;
+}
+
+static int needs_to_resize = 1;
+
+static void sig_winch(int sig)
+{
+ needs_to_resize = 1;
+}
+
+static int get_window_size(int *lines, int *columns)
+{
+ struct winsize ws;
+
+ if (ioctl(0, TIOCGWINSZ, &ws) == -1)
+ return -1;
+ *columns = ws.ws_col;
+ *lines = ws.ws_row;
+ return 0;
+}
+
+static void resize_tree_view(int w, int h)
+{
+ tree_win_w = w / 3;
+ track_win_w = w - tree_win_w - 1;
+ if (tree_win_w < 8)
+ tree_win_w = 8;
+ if (track_win_w < 8)
+ track_win_w = 8;
+ tree_win_x = 0;
+ tree_win_y = 0;
+ track_win_x = tree_win_w + 1;
+ track_win_y = 0;
+
+ h--;
+ window_set_nr_rows(lib_tree_win, h);
+ window_set_nr_rows(lib_track_win, h);
+}
+
+static void update(void)
+{
+ int needs_view_update = 0;
+ int needs_title_update = 0;
+ int needs_status_update = 0;
+ int needs_command_update = 0;
+ int needs_spawn = 0;
+
+ if (needs_to_resize) {
+ int w, h;
+ int columns, lines;
+
+ if (get_window_size(&lines, &columns) == 0) {
+ needs_to_resize = 0;
+#if HAVE_RESIZETERM
+ resizeterm(lines, columns);
+#endif
+ w = COLS;
+ h = LINES - 3;
+ if (w < 16)
+ w = 16;
+ if (h < 8)
+ h = 8;
+ editable_lock();
+ resize_tree_view(w, h);
+ window_set_nr_rows(lib_editable.win, h - 1);
+ window_set_nr_rows(pl_editable.win, h - 1);
+ window_set_nr_rows(pq_editable.win, h - 1);
+ window_set_nr_rows(filters_win, h - 1);
+ window_set_nr_rows(help_win, h - 1);
+ window_set_nr_rows(browser_win, h - 1);
+ editable_unlock();
+ needs_title_update = 1;
+ needs_status_update = 1;
+ needs_command_update = 1;
+ }
+ clearok(curscr, TRUE);
+ refresh();
+ }
+
+ player_info_lock();
+ editable_lock();
+
+ needs_spawn = player_info.status_changed || player_info.file_changed ||
+ player_info.metadata_changed;
+
+ if (player_info.file_changed) {
+ player_info.file_changed = 0;
+ needs_title_update = 1;
+ needs_status_update = 1;
+ }
+ if (player_info.metadata_changed) {
+ player_info.metadata_changed = 0;
+ needs_title_update = 1;
+ }
+ if (player_info.position_changed || player_info.status_changed) {
+ player_info.position_changed = 0;
+ player_info.status_changed = 0;
+
+ needs_status_update = 1;
+ }
+ switch (cur_view) {
+ case TREE_VIEW:
+ needs_view_update += lib_tree_win->changed || lib_track_win->changed;
+ break;
+ case SORTED_VIEW:
+ needs_view_update += lib_editable.win->changed;
+ break;
+ case PLAYLIST_VIEW:
+ needs_view_update += pl_editable.win->changed;
+ break;
+ case QUEUE_VIEW:
+ needs_view_update += pq_editable.win->changed;
+ break;
+ case BROWSER_VIEW:
+ needs_view_update += browser_win->changed;
+ break;
+ case FILTERS_VIEW:
+ needs_view_update += filters_win->changed;
+ break;
+ case HELP_VIEW:
+ needs_view_update += help_win->changed;
+ break;
+ }
+
+ /* total time changed? */
+ if (play_library) {
+ needs_status_update += lib_editable.win->changed;
+ lib_editable.win->changed = 0;
+ } else {
+ needs_status_update += pl_editable.win->changed;
+ lib_editable.win->changed = 0;
+ }
+
+ editable_unlock();
+ player_info_unlock();
+
+ if (needs_spawn)
+ spawn_status_program();
+
+ if (needs_view_update || needs_title_update || needs_status_update || needs_command_update) {
+ curs_set(0);
+
+ if (needs_view_update)
+ do_update_view(0);
+ if (needs_title_update)
+ do_update_titleline();
+ if (needs_status_update)
+ do_update_statusline();
+ if (needs_command_update)
+ do_update_commandline();
+ post_update();
+ }
+}
+
+static void handle_ch(uchar ch)
+{
+ clear_error();
+ if (input_mode == NORMAL_MODE) {
+ normal_mode_ch(ch);
+ } else if (input_mode == COMMAND_MODE) {
+ command_mode_ch(ch);
+ update_commandline();
+ } else if (input_mode == SEARCH_MODE) {
+ search_mode_ch(ch);
+ update_commandline();
+ }
+}
+
+static void handle_escape(int c)
+{
+ clear_error();
+ if (input_mode == NORMAL_MODE) {
+ normal_mode_ch(c + 128);
+ } else if (input_mode == COMMAND_MODE) {
+ command_mode_escape(c);
+ update_commandline();
+ } else if (input_mode == SEARCH_MODE) {
+ search_mode_escape(c);
+ update_commandline();
+ }
+}
+
+static void handle_key(int key)
+{
+ clear_error();
+ if (input_mode == NORMAL_MODE) {
+ normal_mode_key(key);
+ } else if (input_mode == COMMAND_MODE) {
+ command_mode_key(key);
+ update_commandline();
+ } else if (input_mode == SEARCH_MODE) {
+ search_mode_key(key);
+ update_commandline();
+ }
+}
+
+static void u_getch(void)
+{
+ int key;
+ int bit = 7;
+ int mask = (1 << 7);
+ uchar u, ch;
+
+ key = getch();
+ if (key == ERR || key == 0)
+ return;
+
+ if (key > 255) {
+ handle_key(key);
+ return;
+ }
+
+ /* escape sequence */
+ if (key == 0x1B) {
+ int e_key = getch();
+ if (e_key != ERR && e_key != 0) {
+ handle_escape(e_key);
+ return;
+ }
+ }
+
+ ch = (unsigned char)key;
+ while (bit > 0 && ch & mask) {
+ mask >>= 1;
+ bit--;
+ }
+ if (bit == 7) {
+ /* ascii */
+ u = ch;
+ } else if (using_utf8) {
+ int count;
+
+ u = ch & ((1 << bit) - 1);
+ count = 6 - bit;
+ while (count) {
+ key = getch();
+ if (key == ERR || key == 0)
+ return;
+
+ ch = (unsigned char)key;
+ u = (u << 6) | (ch & 63);
+ count--;
+ }
+ } else
+ u = ch | U_INVALID_MASK;
+ handle_ch(u);
+}
+
+static void main_loop(void)
+{
+ int rc, fd_high;
+
+ fd_high = server_socket;
+ while (cmus_running) {
+ fd_set set;
+ struct timeval tv;
+ int poll_mixer = 0;
+ int i, nr_fds = 0;
+ int fds[NR_MIXER_FDS];
+ struct list_head *item;
+ struct client *client;
+
+ update();
+
+ /* Timeout must be so small that screen updates seem instant.
+ * Only affects changes done in other threads (worker, player).
+ *
+ * Too small timeout makes window updates too fast (wastes CPU).
+ *
+ * Too large timeout makes status line (position) updates too slow.
+ * The timeout is accuracy of player position.
+ */
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ player_info_lock();
+ if (player_info.status == PLAYER_STATUS_PLAYING) {
+ // player position updates need to be fast
+ tv.tv_usec = 100e3;
+ }
+ player_info_unlock();
+
+ if (!tv.tv_usec && worker_has_job(JOB_TYPE_ANY)) {
+ // playlist is loading. screen needs to be updated
+ tv.tv_usec = 250e3;
+ }
+
+ FD_ZERO(&set);
+ FD_SET(0, &set);
+ FD_SET(server_socket, &set);
+ list_for_each_entry(client, &client_head, node) {
+ FD_SET(client->fd, &set);
+ if (client->fd > fd_high)
+ fd_high = client->fd;
+ }
+ if (!soft_vol) {
+ nr_fds = mixer_get_fds(fds);
+ if (nr_fds <= 0) {
+ poll_mixer = 1;
+ if (!tv.tv_usec)
+ tv.tv_usec = 500e3;
+ }
+ for (i = 0; i < nr_fds; i++) {
+ BUG_ON(fds[i] <= 0);
+ FD_SET(fds[i], &set);
+ if (fds[i] > fd_high)
+ fd_high = fds[i];
+ }
+ }
+
+ if (tv.tv_usec) {
+ rc = select(fd_high + 1, &set, NULL, NULL, &tv);
+ } else {
+ rc = select(fd_high + 1, &set, NULL, NULL, NULL);
+ }
+ if (poll_mixer) {
+ int ol = volume_l;
+ int or = volume_r;
+
+ mixer_read_volume();
+ if (ol != volume_l || or != volume_r)
+ update_statusline();
+
+ }
+ if (rc <= 0) {
+ if (ctrl_c_pressed) {
+ handle_ch(0x03);
+ ctrl_c_pressed = 0;
+ }
+
+ continue;
+ }
+
+ for (i = 0; i < nr_fds; i++) {
+ if (FD_ISSET(fds[i], &set)) {
+ d_print("vol changed\n");
+ mixer_read_volume();
+ update_statusline();
+ }
+ }
+ if (FD_ISSET(server_socket, &set))
+ server_accept();
+
+ // server_serve() can remove client from the list
+ item = client_head.next;
+ while (item != &client_head) {
+ struct list_head *next = item->next;
+ client = container_of(item, struct client, node);
+ if (FD_ISSET(client->fd, &set))
+ server_serve(client);
+ item = next;
+ }
+
+ if (FD_ISSET(0, &set))
+ u_getch();
+ }
+}
+
+static int get_next(struct track_info **ti)
+{
+ struct track_info *info;
+
+ editable_lock();
+ info = play_queue_remove();
+ if (info == NULL) {
+ if (play_library) {
+ info = lib_set_next();
+ } else {
+ info = pl_set_next();
+ }
+ }
+ editable_unlock();
+
+ if (info == NULL)
+ return -1;
+
+ *ti = info;
+ return 0;
+}
+
+static const struct player_callbacks player_callbacks = {
+ .get_next = get_next
+};
+
+static void init_curses(void)
+{
+ struct sigaction act;
+ char *ptr, *term;
+
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = sig_int;
+ sigaction(SIGINT, &act, NULL);
+
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = sig_hup;
+ sigaction(SIGHUP, &act, NULL);
+
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &act, NULL);
+
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = sig_winch;
+ sigaction(SIGWINCH, &act, NULL);
+
+ initscr();
+
+ /* turn off kb buffering */
+ cbreak();
+
+ keypad(stdscr, TRUE);
+
+ /* wait max 5 * 0.1 s if there are no keys available
+ * doesn't really matter because we use select()
+ */
+ halfdelay(5);
+
+ noecho();
+ if (has_colors()) {
+#if HAVE_USE_DEFAULT_COLORS
+ start_color();
+ use_default_colors();
+#endif
+ }
+ d_print("Number of supported colors: %d\n", COLORS);
+ ui_initialized = 1;
+
+ /* this was disabled while initializing because it needs to be
+ * called only once after all colors have been set
+ */
+ update_colors();
+
+ ptr = tcap_buffer;
+ t_ts = tgetstr("ts", &ptr);
+ t_fs = tgetstr("fs", &ptr);
+ d_print("ts: %d fs: %d\n", !!t_ts, !!t_fs);
+
+ if (!t_fs)
+ t_ts = NULL;
+
+ term = getenv("TERM");
+ if (!t_ts && term) {
+ /*
+ * Eterm: Eterm
+ * aterm: rxvt
+ * mlterm: xterm
+ * terminal (xfce): xterm
+ * urxvt: rxvt-unicode
+ * xterm: xterm, xterm-{,16,88,256}color
+ */
+ if (!strcmp(term, "screen")) {
+ t_ts = "\033_";
+ t_fs = "\033\\";
+ } else if (!strncmp(term, "xterm", 5) ||
+ !strncmp(term, "rxvt", 4) ||
+ !strcmp(term, "Eterm")) {
+ /* \033]1; change icon
+ * \033]2; change title
+ * \033]0; change both
+ */
+ t_ts = "\033]0;";
+ t_fs = "\007";
+ }
+ }
+}
+
+static void init_all(void)
+{
+ server_init(server_address);
+
+ /* does not select output plugin */
+ player_init(&player_callbacks);
+
+ /* plugins have been loaded so we know what plugin options are available */
+ options_add();
+
+ lib_init();
+ searchable = tree_searchable;
+ pl_init();
+ cmus_init();
+ browser_init();
+ filters_init();
+ help_init();
+ cmdline_init();
+ commands_init();
+ search_mode_init();
+
+ /* almost everything must be initialized now */
+ options_load();
+
+ /* finally we can set the output plugin */
+ player_set_op(output_plugin);
+ if (!soft_vol)
+ mixer_open();
+
+ lib_autosave_filename = xstrjoin(cmus_config_dir, "/lib.pl");
+ pl_autosave_filename = xstrjoin(cmus_config_dir, "/playlist.pl");
+ play_queue_autosave_filename = xstrjoin(cmus_config_dir, "/queue.pl");
+ pl_filename = xstrdup(pl_autosave_filename);
+ lib_filename = xstrdup(lib_autosave_filename);
+
+ if (error_count) {
+ char buf[16];
+ char *ret;
+
+ warn("Press <enter> to continue.");
+
+ ret = fgets(buf, sizeof(buf), stdin);
+ BUG_ON(ret == NULL);
+ }
+ help_add_all_unbound();
+
+ init_curses();
+
+ if (resume_cmus) {
+ resume_load();
+ cmus_add(play_queue_append, play_queue_autosave_filename, FILE_TYPE_PL, JOB_TYPE_QUEUE, 0);
+ }
+
+ cmus_add(pl_add_track, pl_autosave_filename, FILE_TYPE_PL, JOB_TYPE_PL, 0);
+ cmus_add(lib_add_track, lib_autosave_filename, FILE_TYPE_PL, JOB_TYPE_LIB, 0);
+}
+
+static void exit_all(void)
+{
+ endwin();
+
+ if (resume_cmus)
+ resume_exit();
+ options_exit();
+
+ server_exit();
+ cmus_exit();
+ if (resume_cmus)
+ cmus_save(play_queue_for_each, play_queue_autosave_filename);
+ cmus_save(lib_for_each, lib_autosave_filename);
+ cmus_save(pl_for_each, pl_autosave_filename);
+
+ player_exit();
+ op_exit_plugins();
+ commands_exit();
+ search_mode_exit();
+ filters_exit();
+ help_exit();
+ browser_exit();
+}
+
+enum {
+ FLAG_LISTEN,
+ FLAG_PLUGINS,
+ FLAG_SHOW_CURSOR,
+ FLAG_HELP,
+ FLAG_VERSION,
+ NR_FLAGS
+};
+
+static struct option options[NR_FLAGS + 1] = {
+ { 0, "listen", 1 },
+ { 0, "plugins", 0 },
+ { 0, "show-cursor", 0 },
+ { 0, "help", 0 },
+ { 0, "version", 0 },
+ { 0, NULL, 0 }
+};
+
+static const char *usage =
+"Usage: %s [OPTION]...\n"
+"Curses based music player.\n"
+"\n"
+" --listen ADDR listen on ADDR instead of ~/.cmus/socket\n"
+" ADDR is either a UNIX socket or host[:port]\n"
+" WARNING: using TCP/IP is insecure!\n"
+" --plugins list available plugins and exit\n"
+" --show-cursor always visible cursor\n"
+" --help display this help and exit\n"
+" --version " VERSION "\n"
+"\n"
+"Use cmus-remote to control cmus from command line.\n"
+"Report bugs to <cmus-devel@lists.sourceforge.net>.\n";
+
+int main(int argc, char *argv[])
+{
+ int list_plugins = 0;
+
+ program_name = argv[0];
+ argv++;
+ while (1) {
+ int idx;
+ char *arg;
+
+ idx = get_option(&argv, options, &arg);
+ if (idx < 0)
+ break;
+
+ switch (idx) {
+ case FLAG_HELP:
+ printf(usage, program_name);
+ return 0;
+ case FLAG_VERSION:
+ printf("cmus " VERSION
+ "\nCopyright 2004-2006 Timo Hirvonen"
+ "\nCopyright 2008-2011 Various Authors\n");
+ return 0;
+ case FLAG_PLUGINS:
+ list_plugins = 1;
+ break;
+ case FLAG_LISTEN:
+ server_address = xstrdup(arg);
+ break;
+ case FLAG_SHOW_CURSOR:
+ show_cursor = 1;
+ break;
+ }
+ }
+
+ setlocale(LC_CTYPE, "");
+ setlocale(LC_COLLATE, "");
+ charset = getenv("CMUS_CHARSET");
+ if (!charset || !charset[0]) {
+#ifdef CODESET
+ charset = nl_langinfo(CODESET);
+#else
+ charset = "ISO-8859-1";
+#endif
+ }
+ if (strcmp(charset, "UTF-8") == 0)
+ using_utf8 = 1;
+
+ misc_init();
+ if (server_address == NULL)
+ server_address = xstrjoin(cmus_config_dir, "/socket");
+ debug_init();
+ d_print("charset = '%s'\n", charset);
+
+ ip_load_plugins();
+ op_load_plugins();
+ if (list_plugins) {
+ ip_dump_plugins();
+ op_dump_plugins();
+ return 0;
+ }
+ init_all();
+ main_loop();
+ exit_all();
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2006 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _UI_CURSES_H
+#define _UI_CURSES_H
+
+#include "search.h"
+#include "compiler.h"
+
+enum ui_input_mode {
+ NORMAL_MODE,
+ COMMAND_MODE,
+ SEARCH_MODE
+};
+
+extern int cmus_running;
+extern int ui_initialized;
+extern enum ui_input_mode input_mode;
+extern int cur_view;
+extern struct searchable *searchable;
+
+extern char *lib_filename;
+extern char *lib_ext_filename;
+extern char *pl_filename;
+extern char *pl_ext_filename;
+extern char *play_queue_filename;
+extern char *play_queue_ext_filename;
+
+extern char *charset;
+extern int using_utf8;
+
+void update_titleline(void);
+void update_statusline(void);
+void update_filterline(void);
+void update_colors(void);
+void update_full(void);
+void info_msg(const char *format, ...) __FORMAT(1, 2);
+void error_msg(const char *format, ...) __FORMAT(1, 2);
+int yes_no_query(const char *format, ...) __FORMAT(1, 2);
+void search_not_found(void);
+void set_view(int view);
+void set_client_fd(int fd);
+int get_client_fd(void);
+void enter_command_mode(void);
+void enter_search_mode(void);
+void enter_search_backward_mode(void);
+
+int track_format_valid(const char *format);
+
+#endif
--- /dev/null
+/* This file is automatically generated. DO NOT EDIT!
+Instead, edit gen_decomp.py and re-run. */
+
+static struct {
+ uchar composed;
+ uchar base;
+} unidecomp_map[] = {
+ { 0xc0, 0x41 }, // À -> A, ̀ (300)
+ { 0xc1, 0x41 }, // Á -> A, ́ (301)
+ { 0xc2, 0x41 }, // Â -> A, ̂ (302)
+ { 0xc3, 0x41 }, // Ã -> A, ̃ (303)
+ { 0xc4, 0x41 }, // Ä -> A, ̈ (308)
+ { 0xc5, 0x41 }, // Å -> A, ̊ (30a)
+ { 0xc6, 0x41 }, // Æ -> A,
+ { 0xc7, 0x43 }, // Ç -> C, ̧ (327)
+ { 0xc8, 0x45 }, // È -> E, ̀ (300)
+ { 0xc9, 0x45 }, // É -> E, ́ (301)
+ { 0xca, 0x45 }, // Ê -> E, ̂ (302)
+ { 0xcb, 0x45 }, // Ë -> E, ̈ (308)
+ { 0xcc, 0x49 }, // Ì -> I, ̀ (300)
+ { 0xcd, 0x49 }, // Í -> I, ́ (301)
+ { 0xce, 0x49 }, // Î -> I, ̂ (302)
+ { 0xcf, 0x49 }, // Ï -> I, ̈ (308)
+ { 0xd0, 0x44 }, // Ð -> D,
+ { 0xd1, 0x4e }, // Ñ -> N, ̃ (303)
+ { 0xd2, 0x4f }, // Ò -> O, ̀ (300)
+ { 0xd3, 0x4f }, // Ó -> O, ́ (301)
+ { 0xd4, 0x4f }, // Ô -> O, ̂ (302)
+ { 0xd5, 0x4f }, // Õ -> O, ̃ (303)
+ { 0xd6, 0x4f }, // Ö -> O, ̈ (308)
+ { 0xd7, 0x78 }, // × -> x,
+ { 0xd8, 0x4f }, // Ø -> O,
+ { 0xd9, 0x55 }, // Ù -> U, ̀ (300)
+ { 0xda, 0x55 }, // Ú -> U, ́ (301)
+ { 0xdb, 0x55 }, // Û -> U, ̂ (302)
+ { 0xdc, 0x55 }, // Ü -> U, ̈ (308)
+ { 0xdd, 0x59 }, // Ý -> Y, ́ (301)
+ { 0xde, 0x50 }, // Þ -> P,
+ { 0xdf, 0x42 }, // ß -> B,
+ { 0xe0, 0x61 }, // à -> a, ̀ (300)
+ { 0xe1, 0x61 }, // á -> a, ́ (301)
+ { 0xe2, 0x61 }, // â -> a, ̂ (302)
+ { 0xe3, 0x61 }, // ã -> a, ̃ (303)
+ { 0xe4, 0x61 }, // ä -> a, ̈ (308)
+ { 0xe5, 0x61 }, // å -> a, ̊ (30a)
+ { 0xe6, 0x61 }, // æ -> a,
+ { 0xe7, 0x63 }, // ç -> c, ̧ (327)
+ { 0xe8, 0x65 }, // è -> e, ̀ (300)
+ { 0xe9, 0x65 }, // é -> e, ́ (301)
+ { 0xea, 0x65 }, // ê -> e, ̂ (302)
+ { 0xeb, 0x65 }, // ë -> e, ̈ (308)
+ { 0xec, 0x69 }, // ì -> i, ̀ (300)
+ { 0xed, 0x69 }, // í -> i, ́ (301)
+ { 0xee, 0x69 }, // î -> i, ̂ (302)
+ { 0xef, 0x69 }, // ï -> i, ̈ (308)
+ { 0xf0, 0x64 }, // ð -> d,
+ { 0xf1, 0x6e }, // ñ -> n, ̃ (303)
+ { 0xf2, 0x6f }, // ò -> o, ̀ (300)
+ { 0xf3, 0x6f }, // ó -> o, ́ (301)
+ { 0xf4, 0x6f }, // ô -> o, ̂ (302)
+ { 0xf5, 0x6f }, // õ -> o, ̃ (303)
+ { 0xf6, 0x6f }, // ö -> o, ̈ (308)
+ { 0xf8, 0x6f }, // ø -> o,
+ { 0xf9, 0x75 }, // ù -> u, ̀ (300)
+ { 0xfa, 0x75 }, // ú -> u, ́ (301)
+ { 0xfb, 0x75 }, // û -> u, ̂ (302)
+ { 0xfc, 0x75 }, // ü -> u, ̈ (308)
+ { 0xfd, 0x79 }, // ý -> y, ́ (301)
+ { 0xfe, 0x70 }, // þ -> p,
+ { 0xff, 0x79 }, // ÿ -> y, ̈ (308)
+ { 0x100, 0x41 }, // Ā -> A, ̄ (304)
+ { 0x101, 0x61 }, // ā -> a, ̄ (304)
+ { 0x102, 0x41 }, // Ă -> A, ̆ (306)
+ { 0x103, 0x61 }, // ă -> a, ̆ (306)
+ { 0x104, 0x41 }, // Ą -> A, ̨ (328)
+ { 0x105, 0x61 }, // ą -> a, ̨ (328)
+ { 0x106, 0x43 }, // Ć -> C, ́ (301)
+ { 0x107, 0x63 }, // ć -> c, ́ (301)
+ { 0x108, 0x43 }, // Ĉ -> C, ̂ (302)
+ { 0x109, 0x63 }, // ĉ -> c, ̂ (302)
+ { 0x10a, 0x43 }, // Ċ -> C, ̇ (307)
+ { 0x10b, 0x63 }, // ċ -> c, ̇ (307)
+ { 0x10c, 0x43 }, // Č -> C, ̌ (30c)
+ { 0x10d, 0x63 }, // č -> c, ̌ (30c)
+ { 0x10e, 0x44 }, // Ď -> D, ̌ (30c)
+ { 0x10f, 0x64 }, // ď -> d, ̌ (30c)
+ { 0x112, 0x45 }, // Ē -> E, ̄ (304)
+ { 0x113, 0x65 }, // ē -> e, ̄ (304)
+ { 0x114, 0x45 }, // Ĕ -> E, ̆ (306)
+ { 0x115, 0x65 }, // ĕ -> e, ̆ (306)
+ { 0x116, 0x45 }, // Ė -> E, ̇ (307)
+ { 0x117, 0x65 }, // ė -> e, ̇ (307)
+ { 0x118, 0x45 }, // Ę -> E, ̨ (328)
+ { 0x119, 0x65 }, // ę -> e, ̨ (328)
+ { 0x11a, 0x45 }, // Ě -> E, ̌ (30c)
+ { 0x11b, 0x65 }, // ě -> e, ̌ (30c)
+ { 0x11c, 0x47 }, // Ĝ -> G, ̂ (302)
+ { 0x11d, 0x67 }, // ĝ -> g, ̂ (302)
+ { 0x11e, 0x47 }, // Ğ -> G, ̆ (306)
+ { 0x11f, 0x67 }, // ğ -> g, ̆ (306)
+ { 0x120, 0x47 }, // Ġ -> G, ̇ (307)
+ { 0x121, 0x67 }, // ġ -> g, ̇ (307)
+ { 0x122, 0x47 }, // Ģ -> G, ̧ (327)
+ { 0x123, 0x67 }, // ģ -> g, ̧ (327)
+ { 0x124, 0x48 }, // Ĥ -> H, ̂ (302)
+ { 0x125, 0x68 }, // ĥ -> h, ̂ (302)
+ { 0x128, 0x49 }, // Ĩ -> I, ̃ (303)
+ { 0x129, 0x69 }, // ĩ -> i, ̃ (303)
+ { 0x12a, 0x49 }, // Ī -> I, ̄ (304)
+ { 0x12b, 0x69 }, // ī -> i, ̄ (304)
+ { 0x12c, 0x49 }, // Ĭ -> I, ̆ (306)
+ { 0x12d, 0x69 }, // ĭ -> i, ̆ (306)
+ { 0x12e, 0x49 }, // Į -> I, ̨ (328)
+ { 0x12f, 0x69 }, // į -> i, ̨ (328)
+ { 0x130, 0x49 }, // İ -> I, ̇ (307)
+ { 0x134, 0x4a }, // Ĵ -> J, ̂ (302)
+ { 0x135, 0x6a }, // ĵ -> j, ̂ (302)
+ { 0x136, 0x4b }, // Ķ -> K, ̧ (327)
+ { 0x137, 0x6b }, // ķ -> k, ̧ (327)
+ { 0x139, 0x4c }, // Ĺ -> L, ́ (301)
+ { 0x13a, 0x6c }, // ĺ -> l, ́ (301)
+ { 0x13b, 0x4c }, // Ļ -> L, ̧ (327)
+ { 0x13c, 0x6c }, // ļ -> l, ̧ (327)
+ { 0x13d, 0x4c }, // Ľ -> L, ̌ (30c)
+ { 0x13e, 0x6c }, // ľ -> l, ̌ (30c)
+ { 0x143, 0x4e }, // Ń -> N, ́ (301)
+ { 0x144, 0x6e }, // ń -> n, ́ (301)
+ { 0x145, 0x4e }, // Ņ -> N, ̧ (327)
+ { 0x146, 0x6e }, // ņ -> n, ̧ (327)
+ { 0x147, 0x4e }, // Ň -> N, ̌ (30c)
+ { 0x148, 0x6e }, // ň -> n, ̌ (30c)
+ { 0x14c, 0x4f }, // Ō -> O, ̄ (304)
+ { 0x14d, 0x6f }, // ō -> o, ̄ (304)
+ { 0x14e, 0x4f }, // Ŏ -> O, ̆ (306)
+ { 0x14f, 0x6f }, // ŏ -> o, ̆ (306)
+ { 0x150, 0x4f }, // Ő -> O, ̋ (30b)
+ { 0x151, 0x6f }, // ő -> o, ̋ (30b)
+ { 0x154, 0x52 }, // Ŕ -> R, ́ (301)
+ { 0x155, 0x72 }, // ŕ -> r, ́ (301)
+ { 0x156, 0x52 }, // Ŗ -> R, ̧ (327)
+ { 0x157, 0x72 }, // ŗ -> r, ̧ (327)
+ { 0x158, 0x52 }, // Ř -> R, ̌ (30c)
+ { 0x159, 0x72 }, // ř -> r, ̌ (30c)
+ { 0x15a, 0x53 }, // Ś -> S, ́ (301)
+ { 0x15b, 0x73 }, // ś -> s, ́ (301)
+ { 0x15c, 0x53 }, // Ŝ -> S, ̂ (302)
+ { 0x15d, 0x73 }, // ŝ -> s, ̂ (302)
+ { 0x15e, 0x53 }, // Ş -> S, ̧ (327)
+ { 0x15f, 0x73 }, // ş -> s, ̧ (327)
+ { 0x160, 0x53 }, // Š -> S, ̌ (30c)
+ { 0x161, 0x73 }, // š -> s, ̌ (30c)
+ { 0x162, 0x54 }, // Ţ -> T, ̧ (327)
+ { 0x163, 0x74 }, // ţ -> t, ̧ (327)
+ { 0x164, 0x54 }, // Ť -> T, ̌ (30c)
+ { 0x165, 0x74 }, // ť -> t, ̌ (30c)
+ { 0x168, 0x55 }, // Ũ -> U, ̃ (303)
+ { 0x169, 0x75 }, // ũ -> u, ̃ (303)
+ { 0x16a, 0x55 }, // Ū -> U, ̄ (304)
+ { 0x16b, 0x75 }, // ū -> u, ̄ (304)
+ { 0x16c, 0x55 }, // Ŭ -> U, ̆ (306)
+ { 0x16d, 0x75 }, // ŭ -> u, ̆ (306)
+ { 0x16e, 0x55 }, // Ů -> U, ̊ (30a)
+ { 0x16f, 0x75 }, // ů -> u, ̊ (30a)
+ { 0x170, 0x55 }, // Ű -> U, ̋ (30b)
+ { 0x171, 0x75 }, // ű -> u, ̋ (30b)
+ { 0x172, 0x55 }, // Ų -> U, ̨ (328)
+ { 0x173, 0x75 }, // ų -> u, ̨ (328)
+ { 0x174, 0x57 }, // Ŵ -> W, ̂ (302)
+ { 0x175, 0x77 }, // ŵ -> w, ̂ (302)
+ { 0x176, 0x59 }, // Ŷ -> Y, ̂ (302)
+ { 0x177, 0x79 }, // ŷ -> y, ̂ (302)
+ { 0x178, 0x59 }, // Ÿ -> Y, ̈ (308)
+ { 0x179, 0x5a }, // Ź -> Z, ́ (301)
+ { 0x17a, 0x7a }, // ź -> z, ́ (301)
+ { 0x17b, 0x5a }, // Ż -> Z, ̇ (307)
+ { 0x17c, 0x7a }, // ż -> z, ̇ (307)
+ { 0x17d, 0x5a }, // Ž -> Z, ̌ (30c)
+ { 0x17e, 0x7a }, // ž -> z, ̌ (30c)
+ { 0x1a0, 0x4f }, // Ơ -> O, ̛ (31b)
+ { 0x1a1, 0x6f }, // ơ -> o, ̛ (31b)
+ { 0x1af, 0x55 }, // Ư -> U, ̛ (31b)
+ { 0x1b0, 0x75 }, // ư -> u, ̛ (31b)
+ { 0x1c4, 0x44 }, // DŽ -> D, Z (5a), ̌ (30c)
+ { 0x1c5, 0x44 }, // Dž -> D, z (7a), ̌ (30c)
+ { 0x1c6, 0x64 }, // dž -> d, z (7a), ̌ (30c)
+ { 0x1cd, 0x41 }, // Ǎ -> A, ̌ (30c)
+ { 0x1ce, 0x61 }, // ǎ -> a, ̌ (30c)
+ { 0x1cf, 0x49 }, // Ǐ -> I, ̌ (30c)
+ { 0x1d0, 0x69 }, // ǐ -> i, ̌ (30c)
+ { 0x1d1, 0x4f }, // Ǒ -> O, ̌ (30c)
+ { 0x1d2, 0x6f }, // ǒ -> o, ̌ (30c)
+ { 0x1d3, 0x55 }, // Ǔ -> U, ̌ (30c)
+ { 0x1d4, 0x75 }, // ǔ -> u, ̌ (30c)
+ { 0x1d5, 0x55 }, // Ǖ -> U, ̈ (308), ̄ (304)
+ { 0x1d6, 0x75 }, // ǖ -> u, ̈ (308), ̄ (304)
+ { 0x1d7, 0x55 }, // Ǘ -> U, ̈ (308), ́ (301)
+ { 0x1d8, 0x75 }, // ǘ -> u, ̈ (308), ́ (301)
+ { 0x1d9, 0x55 }, // Ǚ -> U, ̈ (308), ̌ (30c)
+ { 0x1da, 0x75 }, // ǚ -> u, ̈ (308), ̌ (30c)
+ { 0x1db, 0x55 }, // Ǜ -> U, ̈ (308), ̀ (300)
+ { 0x1dc, 0x75 }, // ǜ -> u, ̈ (308), ̀ (300)
+ { 0x1de, 0x41 }, // Ǟ -> A, ̈ (308), ̄ (304)
+ { 0x1df, 0x61 }, // ǟ -> a, ̈ (308), ̄ (304)
+ { 0x1e0, 0x41 }, // Ǡ -> A, ̇ (307), ̄ (304)
+ { 0x1e1, 0x61 }, // ǡ -> a, ̇ (307), ̄ (304)
+ { 0x1e2, 0x41 }, // Ǣ -> A, ̄ (304)
+ { 0x1e3, 0x61 }, // ǣ -> a, ̄ (304)
+ { 0x1e6, 0x47 }, // Ǧ -> G, ̌ (30c)
+ { 0x1e7, 0x67 }, // ǧ -> g, ̌ (30c)
+ { 0x1e8, 0x4b }, // Ǩ -> K, ̌ (30c)
+ { 0x1e9, 0x6b }, // ǩ -> k, ̌ (30c)
+ { 0x1ea, 0x4f }, // Ǫ -> O, ̨ (328)
+ { 0x1eb, 0x6f }, // ǫ -> o, ̨ (328)
+ { 0x1ec, 0x4f }, // Ǭ -> O, ̨ (328), ̄ (304)
+ { 0x1ed, 0x6f }, // ǭ -> o, ̨ (328), ̄ (304)
+ { 0x1ee, 0x1b7 }, // Ǯ -> Ʒ, ̌ (30c)
+ { 0x1ef, 0x292 }, // ǯ -> ʒ, ̌ (30c)
+ { 0x1f0, 0x6a }, // ǰ -> j, ̌ (30c)
+ { 0x1f4, 0x47 }, // Ǵ -> G, ́ (301)
+ { 0x1f5, 0x67 }, // ǵ -> g, ́ (301)
+ { 0x1f8, 0x4e }, // Ǹ -> N, ̀ (300)
+ { 0x1f9, 0x6e }, // ǹ -> n, ̀ (300)
+ { 0x1fa, 0x41 }, // Ǻ -> A, ̊ (30a), ́ (301)
+ { 0x1fb, 0x61 }, // ǻ -> a, ̊ (30a), ́ (301)
+ { 0x1fc, 0x41 }, // Ǽ -> A, ́ (301)
+ { 0x1fd, 0x61 }, // ǽ -> a, ́ (301)
+ { 0x1fe, 0x4f }, // Ǿ -> O, ́ (301)
+ { 0x1ff, 0x6f }, // ǿ -> o, ́ (301)
+ { 0x200, 0x41 }, // Ȁ -> A, ̏ (30f)
+ { 0x201, 0x61 }, // ȁ -> a, ̏ (30f)
+ { 0x202, 0x41 }, // Ȃ -> A, ̑ (311)
+ { 0x203, 0x61 }, // ȃ -> a, ̑ (311)
+ { 0x204, 0x45 }, // Ȅ -> E, ̏ (30f)
+ { 0x205, 0x65 }, // ȅ -> e, ̏ (30f)
+ { 0x206, 0x45 }, // Ȇ -> E, ̑ (311)
+ { 0x207, 0x65 }, // ȇ -> e, ̑ (311)
+ { 0x208, 0x49 }, // Ȉ -> I, ̏ (30f)
+ { 0x209, 0x69 }, // ȉ -> i, ̏ (30f)
+ { 0x20a, 0x49 }, // Ȋ -> I, ̑ (311)
+ { 0x20b, 0x69 }, // ȋ -> i, ̑ (311)
+ { 0x20c, 0x4f }, // Ȍ -> O, ̏ (30f)
+ { 0x20d, 0x6f }, // ȍ -> o, ̏ (30f)
+ { 0x20e, 0x4f }, // Ȏ -> O, ̑ (311)
+ { 0x20f, 0x6f }, // ȏ -> o, ̑ (311)
+ { 0x210, 0x52 }, // Ȑ -> R, ̏ (30f)
+ { 0x211, 0x72 }, // ȑ -> r, ̏ (30f)
+ { 0x212, 0x52 }, // Ȓ -> R, ̑ (311)
+ { 0x213, 0x72 }, // ȓ -> r, ̑ (311)
+ { 0x214, 0x55 }, // Ȕ -> U, ̏ (30f)
+ { 0x215, 0x75 }, // ȕ -> u, ̏ (30f)
+ { 0x216, 0x55 }, // Ȗ -> U, ̑ (311)
+ { 0x217, 0x75 }, // ȗ -> u, ̑ (311)
+ { 0x218, 0x53 }, // Ș -> S, ̦ (326)
+ { 0x219, 0x73 }, // ș -> s, ̦ (326)
+ { 0x21a, 0x54 }, // Ț -> T, ̦ (326)
+ { 0x21b, 0x74 }, // ț -> t, ̦ (326)
+ { 0x21e, 0x48 }, // Ȟ -> H, ̌ (30c)
+ { 0x21f, 0x68 }, // ȟ -> h, ̌ (30c)
+ { 0x226, 0x41 }, // Ȧ -> A, ̇ (307)
+ { 0x227, 0x61 }, // ȧ -> a, ̇ (307)
+ { 0x228, 0x45 }, // Ȩ -> E, ̧ (327)
+ { 0x229, 0x65 }, // ȩ -> e, ̧ (327)
+ { 0x22a, 0x4f }, // Ȫ -> O, ̈ (308), ̄ (304)
+ { 0x22b, 0x6f }, // ȫ -> o, ̈ (308), ̄ (304)
+ { 0x22c, 0x4f }, // Ȭ -> O, ̃ (303), ̄ (304)
+ { 0x22d, 0x6f }, // ȭ -> o, ̃ (303), ̄ (304)
+ { 0x22e, 0x4f }, // Ȯ -> O, ̇ (307)
+ { 0x22f, 0x6f }, // ȯ -> o, ̇ (307)
+ { 0x230, 0x4f }, // Ȱ -> O, ̇ (307), ̄ (304)
+ { 0x231, 0x6f }, // ȱ -> o, ̇ (307), ̄ (304)
+ { 0x232, 0x59 }, // Ȳ -> Y, ̄ (304)
+ { 0x233, 0x79 }, // ȳ -> y, ̄ (304)
+ { 0x386, 0x391 }, // Ά -> Α, ́ (301)
+ { 0x388, 0x395 }, // Έ -> Ε, ́ (301)
+ { 0x389, 0x397 }, // Ή -> Η, ́ (301)
+ { 0x38a, 0x399 }, // Ί -> Ι, ́ (301)
+ { 0x38c, 0x39f }, // Ό -> Ο, ́ (301)
+ { 0x38e, 0x3a5 }, // Ύ -> Υ, ́ (301)
+ { 0x38f, 0x3a9 }, // Ώ -> Ω, ́ (301)
+ { 0x390, 0x3b9 }, // ΐ -> ι, ̈ (308), ́ (301)
+ { 0x3aa, 0x399 }, // Ϊ -> Ι, ̈ (308)
+ { 0x3ab, 0x3a5 }, // Ϋ -> Υ, ̈ (308)
+ { 0x3ac, 0x3b1 }, // ά -> α, ́ (301)
+ { 0x3ad, 0x3b5 }, // έ -> ε, ́ (301)
+ { 0x3ae, 0x3b7 }, // ή -> η, ́ (301)
+ { 0x3af, 0x3b9 }, // ί -> ι, ́ (301)
+ { 0x3b0, 0x3c5 }, // ΰ -> υ, ̈ (308), ́ (301)
+ { 0x3ca, 0x3b9 }, // ϊ -> ι, ̈ (308)
+ { 0x3cb, 0x3c5 }, // ϋ -> υ, ̈ (308)
+ { 0x3cc, 0x3bf }, // ό -> ο, ́ (301)
+ { 0x3cd, 0x3c5 }, // ύ -> υ, ́ (301)
+ { 0x3ce, 0x3c9 }, // ώ -> ω, ́ (301)
+ { 0x3d3, 0x3a5 }, // ϓ -> Υ, ́ (301)
+ { 0x3d4, 0x3a5 }, // ϔ -> Υ, ̈ (308)
+ { 0x400, 0x415 }, // Ѐ -> Е, ̀ (300)
+ { 0x401, 0x415 }, // Ё -> Е, ̈ (308)
+ { 0x403, 0x413 }, // Ѓ -> Г, ́ (301)
+ { 0x407, 0x406 }, // Ї -> І, ̈ (308)
+ { 0x40c, 0x41a }, // Ќ -> К, ́ (301)
+ { 0x40d, 0x418 }, // Ѝ -> И, ̀ (300)
+ { 0x40e, 0x423 }, // Ў -> У, ̆ (306)
+ { 0x419, 0x418 }, // Й -> И, ̆ (306)
+ { 0x439, 0x438 }, // й -> и, ̆ (306)
+ { 0x450, 0x435 }, // ѐ -> е, ̀ (300)
+ { 0x451, 0x435 }, // ё -> е, ̈ (308)
+ { 0x453, 0x433 }, // ѓ -> г, ́ (301)
+ { 0x457, 0x456 }, // ї -> і, ̈ (308)
+ { 0x45c, 0x43a }, // ќ -> к, ́ (301)
+ { 0x45d, 0x438 }, // ѝ -> и, ̀ (300)
+ { 0x45e, 0x443 }, // ў -> у, ̆ (306)
+ { 0x476, 0x474 }, // Ѷ -> Ѵ, ̏ (30f)
+ { 0x477, 0x475 }, // ѷ -> ѵ, ̏ (30f)
+ { 0x4c1, 0x416 }, // Ӂ -> Ж, ̆ (306)
+ { 0x4c2, 0x436 }, // ӂ -> ж, ̆ (306)
+ { 0x4d0, 0x410 }, // Ӑ -> А, ̆ (306)
+ { 0x4d1, 0x430 }, // ӑ -> а, ̆ (306)
+ { 0x4d2, 0x410 }, // Ӓ -> А, ̈ (308)
+ { 0x4d3, 0x430 }, // ӓ -> а, ̈ (308)
+ { 0x4d6, 0x415 }, // Ӗ -> Е, ̆ (306)
+ { 0x4d7, 0x435 }, // ӗ -> е, ̆ (306)
+ { 0x4da, 0x4d8 }, // Ӛ -> Ә, ̈ (308)
+ { 0x4db, 0x4d9 }, // ӛ -> ә, ̈ (308)
+ { 0x4dc, 0x416 }, // Ӝ -> Ж, ̈ (308)
+ { 0x4dd, 0x436 }, // ӝ -> ж, ̈ (308)
+ { 0x4de, 0x417 }, // Ӟ -> З, ̈ (308)
+ { 0x4df, 0x437 }, // ӟ -> з, ̈ (308)
+ { 0x4e2, 0x418 }, // Ӣ -> И, ̄ (304)
+ { 0x4e3, 0x438 }, // ӣ -> и, ̄ (304)
+ { 0x4e4, 0x418 }, // Ӥ -> И, ̈ (308)
+ { 0x4e5, 0x438 }, // ӥ -> и, ̈ (308)
+ { 0x4e6, 0x41e }, // Ӧ -> О, ̈ (308)
+ { 0x4e7, 0x43e }, // ӧ -> о, ̈ (308)
+ { 0x4ea, 0x4e8 }, // Ӫ -> Ө, ̈ (308)
+ { 0x4eb, 0x4e9 }, // ӫ -> ө, ̈ (308)
+ { 0x4ec, 0x42d }, // Ӭ -> Э, ̈ (308)
+ { 0x4ed, 0x44d }, // ӭ -> э, ̈ (308)
+ { 0x4ee, 0x423 }, // Ӯ -> У, ̄ (304)
+ { 0x4ef, 0x443 }, // ӯ -> у, ̄ (304)
+ { 0x4f0, 0x423 }, // Ӱ -> У, ̈ (308)
+ { 0x4f1, 0x443 }, // ӱ -> у, ̈ (308)
+ { 0x4f2, 0x423 }, // Ӳ -> У, ̋ (30b)
+ { 0x4f3, 0x443 }, // ӳ -> у, ̋ (30b)
+ { 0x4f4, 0x427 }, // Ӵ -> Ч, ̈ (308)
+ { 0x4f5, 0x447 }, // ӵ -> ч, ̈ (308)
+ { 0x4f8, 0x42b }, // Ӹ -> Ы, ̈ (308)
+ { 0x4f9, 0x44b }, // ӹ -> ы, ̈ (308)
+ { 0x1e00, 0x41 }, // Ḁ -> A, ̥ (325)
+ { 0x1e01, 0x61 }, // ḁ -> a, ̥ (325)
+ { 0x1e02, 0x42 }, // Ḃ -> B, ̇ (307)
+ { 0x1e03, 0x62 }, // ḃ -> b, ̇ (307)
+ { 0x1e04, 0x42 }, // Ḅ -> B, ̣ (323)
+ { 0x1e05, 0x62 }, // ḅ -> b, ̣ (323)
+ { 0x1e06, 0x42 }, // Ḇ -> B, ̱ (331)
+ { 0x1e07, 0x62 }, // ḇ -> b, ̱ (331)
+ { 0x1e08, 0x43 }, // Ḉ -> C, ̧ (327), ́ (301)
+ { 0x1e09, 0x63 }, // ḉ -> c, ̧ (327), ́ (301)
+ { 0x1e0a, 0x44 }, // Ḋ -> D, ̇ (307)
+ { 0x1e0b, 0x64 }, // ḋ -> d, ̇ (307)
+ { 0x1e0c, 0x44 }, // Ḍ -> D, ̣ (323)
+ { 0x1e0d, 0x64 }, // ḍ -> d, ̣ (323)
+ { 0x1e0e, 0x44 }, // Ḏ -> D, ̱ (331)
+ { 0x1e0f, 0x64 }, // ḏ -> d, ̱ (331)
+ { 0x1e10, 0x44 }, // Ḑ -> D, ̧ (327)
+ { 0x1e11, 0x64 }, // ḑ -> d, ̧ (327)
+ { 0x1e12, 0x44 }, // Ḓ -> D, ̭ (32d)
+ { 0x1e13, 0x64 }, // ḓ -> d, ̭ (32d)
+ { 0x1e14, 0x45 }, // Ḕ -> E, ̄ (304), ̀ (300)
+ { 0x1e15, 0x65 }, // ḕ -> e, ̄ (304), ̀ (300)
+ { 0x1e16, 0x45 }, // Ḗ -> E, ̄ (304), ́ (301)
+ { 0x1e17, 0x65 }, // ḗ -> e, ̄ (304), ́ (301)
+ { 0x1e18, 0x45 }, // Ḙ -> E, ̭ (32d)
+ { 0x1e19, 0x65 }, // ḙ -> e, ̭ (32d)
+ { 0x1e1a, 0x45 }, // Ḛ -> E, ̰ (330)
+ { 0x1e1b, 0x65 }, // ḛ -> e, ̰ (330)
+ { 0x1e1c, 0x45 }, // Ḝ -> E, ̧ (327), ̆ (306)
+ { 0x1e1d, 0x65 }, // ḝ -> e, ̧ (327), ̆ (306)
+ { 0x1e1e, 0x46 }, // Ḟ -> F, ̇ (307)
+ { 0x1e1f, 0x66 }, // ḟ -> f, ̇ (307)
+ { 0x1e20, 0x47 }, // Ḡ -> G, ̄ (304)
+ { 0x1e21, 0x67 }, // ḡ -> g, ̄ (304)
+ { 0x1e22, 0x48 }, // Ḣ -> H, ̇ (307)
+ { 0x1e23, 0x68 }, // ḣ -> h, ̇ (307)
+ { 0x1e24, 0x48 }, // Ḥ -> H, ̣ (323)
+ { 0x1e25, 0x68 }, // ḥ -> h, ̣ (323)
+ { 0x1e26, 0x48 }, // Ḧ -> H, ̈ (308)
+ { 0x1e27, 0x68 }, // ḧ -> h, ̈ (308)
+ { 0x1e28, 0x48 }, // Ḩ -> H, ̧ (327)
+ { 0x1e29, 0x68 }, // ḩ -> h, ̧ (327)
+ { 0x1e2a, 0x48 }, // Ḫ -> H, ̮ (32e)
+ { 0x1e2b, 0x68 }, // ḫ -> h, ̮ (32e)
+ { 0x1e2c, 0x49 }, // Ḭ -> I, ̰ (330)
+ { 0x1e2d, 0x69 }, // ḭ -> i, ̰ (330)
+ { 0x1e2e, 0x49 }, // Ḯ -> I, ̈ (308), ́ (301)
+ { 0x1e2f, 0x69 }, // ḯ -> i, ̈ (308), ́ (301)
+ { 0x1e30, 0x4b }, // Ḱ -> K, ́ (301)
+ { 0x1e31, 0x6b }, // ḱ -> k, ́ (301)
+ { 0x1e32, 0x4b }, // Ḳ -> K, ̣ (323)
+ { 0x1e33, 0x6b }, // ḳ -> k, ̣ (323)
+ { 0x1e34, 0x4b }, // Ḵ -> K, ̱ (331)
+ { 0x1e35, 0x6b }, // ḵ -> k, ̱ (331)
+ { 0x1e36, 0x4c }, // Ḷ -> L, ̣ (323)
+ { 0x1e37, 0x6c }, // ḷ -> l, ̣ (323)
+ { 0x1e38, 0x4c }, // Ḹ -> L, ̣ (323), ̄ (304)
+ { 0x1e39, 0x6c }, // ḹ -> l, ̣ (323), ̄ (304)
+ { 0x1e3a, 0x4c }, // Ḻ -> L, ̱ (331)
+ { 0x1e3b, 0x6c }, // ḻ -> l, ̱ (331)
+ { 0x1e3c, 0x4c }, // Ḽ -> L, ̭ (32d)
+ { 0x1e3d, 0x6c }, // ḽ -> l, ̭ (32d)
+ { 0x1e3e, 0x4d }, // Ḿ -> M, ́ (301)
+ { 0x1e3f, 0x6d }, // ḿ -> m, ́ (301)
+ { 0x1e40, 0x4d }, // Ṁ -> M, ̇ (307)
+ { 0x1e41, 0x6d }, // ṁ -> m, ̇ (307)
+ { 0x1e42, 0x4d }, // Ṃ -> M, ̣ (323)
+ { 0x1e43, 0x6d }, // ṃ -> m, ̣ (323)
+ { 0x1e44, 0x4e }, // Ṅ -> N, ̇ (307)
+ { 0x1e45, 0x6e }, // ṅ -> n, ̇ (307)
+ { 0x1e46, 0x4e }, // Ṇ -> N, ̣ (323)
+ { 0x1e47, 0x6e }, // ṇ -> n, ̣ (323)
+ { 0x1e48, 0x4e }, // Ṉ -> N, ̱ (331)
+ { 0x1e49, 0x6e }, // ṉ -> n, ̱ (331)
+ { 0x1e4a, 0x4e }, // Ṋ -> N, ̭ (32d)
+ { 0x1e4b, 0x6e }, // ṋ -> n, ̭ (32d)
+ { 0x1e4c, 0x4f }, // Ṍ -> O, ̃ (303), ́ (301)
+ { 0x1e4d, 0x6f }, // ṍ -> o, ̃ (303), ́ (301)
+ { 0x1e4e, 0x4f }, // Ṏ -> O, ̃ (303), ̈ (308)
+ { 0x1e4f, 0x6f }, // ṏ -> o, ̃ (303), ̈ (308)
+ { 0x1e50, 0x4f }, // Ṑ -> O, ̄ (304), ̀ (300)
+ { 0x1e51, 0x6f }, // ṑ -> o, ̄ (304), ̀ (300)
+ { 0x1e52, 0x4f }, // Ṓ -> O, ̄ (304), ́ (301)
+ { 0x1e53, 0x6f }, // ṓ -> o, ̄ (304), ́ (301)
+ { 0x1e54, 0x50 }, // Ṕ -> P, ́ (301)
+ { 0x1e55, 0x70 }, // ṕ -> p, ́ (301)
+ { 0x1e56, 0x50 }, // Ṗ -> P, ̇ (307)
+ { 0x1e57, 0x70 }, // ṗ -> p, ̇ (307)
+ { 0x1e58, 0x52 }, // Ṙ -> R, ̇ (307)
+ { 0x1e59, 0x72 }, // ṙ -> r, ̇ (307)
+ { 0x1e5a, 0x52 }, // Ṛ -> R, ̣ (323)
+ { 0x1e5b, 0x72 }, // ṛ -> r, ̣ (323)
+ { 0x1e5c, 0x52 }, // Ṝ -> R, ̣ (323), ̄ (304)
+ { 0x1e5d, 0x72 }, // ṝ -> r, ̣ (323), ̄ (304)
+ { 0x1e5e, 0x52 }, // Ṟ -> R, ̱ (331)
+ { 0x1e5f, 0x72 }, // ṟ -> r, ̱ (331)
+ { 0x1e60, 0x53 }, // Ṡ -> S, ̇ (307)
+ { 0x1e61, 0x73 }, // ṡ -> s, ̇ (307)
+ { 0x1e62, 0x53 }, // Ṣ -> S, ̣ (323)
+ { 0x1e63, 0x73 }, // ṣ -> s, ̣ (323)
+ { 0x1e64, 0x53 }, // Ṥ -> S, ́ (301), ̇ (307)
+ { 0x1e65, 0x73 }, // ṥ -> s, ́ (301), ̇ (307)
+ { 0x1e66, 0x53 }, // Ṧ -> S, ̌ (30c), ̇ (307)
+ { 0x1e67, 0x73 }, // ṧ -> s, ̌ (30c), ̇ (307)
+ { 0x1e68, 0x53 }, // Ṩ -> S, ̣ (323), ̇ (307)
+ { 0x1e69, 0x73 }, // ṩ -> s, ̣ (323), ̇ (307)
+ { 0x1e6a, 0x54 }, // Ṫ -> T, ̇ (307)
+ { 0x1e6b, 0x74 }, // ṫ -> t, ̇ (307)
+ { 0x1e6c, 0x54 }, // Ṭ -> T, ̣ (323)
+ { 0x1e6d, 0x74 }, // ṭ -> t, ̣ (323)
+ { 0x1e6e, 0x54 }, // Ṯ -> T, ̱ (331)
+ { 0x1e6f, 0x74 }, // ṯ -> t, ̱ (331)
+ { 0x1e70, 0x54 }, // Ṱ -> T, ̭ (32d)
+ { 0x1e71, 0x74 }, // ṱ -> t, ̭ (32d)
+ { 0x1e72, 0x55 }, // Ṳ -> U, ̤ (324)
+ { 0x1e73, 0x75 }, // ṳ -> u, ̤ (324)
+ { 0x1e74, 0x55 }, // Ṵ -> U, ̰ (330)
+ { 0x1e75, 0x75 }, // ṵ -> u, ̰ (330)
+ { 0x1e76, 0x55 }, // Ṷ -> U, ̭ (32d)
+ { 0x1e77, 0x75 }, // ṷ -> u, ̭ (32d)
+ { 0x1e78, 0x55 }, // Ṹ -> U, ̃ (303), ́ (301)
+ { 0x1e79, 0x75 }, // ṹ -> u, ̃ (303), ́ (301)
+ { 0x1e7a, 0x55 }, // Ṻ -> U, ̄ (304), ̈ (308)
+ { 0x1e7b, 0x75 }, // ṻ -> u, ̄ (304), ̈ (308)
+ { 0x1e7c, 0x56 }, // Ṽ -> V, ̃ (303)
+ { 0x1e7d, 0x76 }, // ṽ -> v, ̃ (303)
+ { 0x1e7e, 0x56 }, // Ṿ -> V, ̣ (323)
+ { 0x1e7f, 0x76 }, // ṿ -> v, ̣ (323)
+ { 0x1e80, 0x57 }, // Ẁ -> W, ̀ (300)
+ { 0x1e81, 0x77 }, // ẁ -> w, ̀ (300)
+ { 0x1e82, 0x57 }, // Ẃ -> W, ́ (301)
+ { 0x1e83, 0x77 }, // ẃ -> w, ́ (301)
+ { 0x1e84, 0x57 }, // Ẅ -> W, ̈ (308)
+ { 0x1e85, 0x77 }, // ẅ -> w, ̈ (308)
+ { 0x1e86, 0x57 }, // Ẇ -> W, ̇ (307)
+ { 0x1e87, 0x77 }, // ẇ -> w, ̇ (307)
+ { 0x1e88, 0x57 }, // Ẉ -> W, ̣ (323)
+ { 0x1e89, 0x77 }, // ẉ -> w, ̣ (323)
+ { 0x1e8a, 0x58 }, // Ẋ -> X, ̇ (307)
+ { 0x1e8b, 0x78 }, // ẋ -> x, ̇ (307)
+ { 0x1e8c, 0x58 }, // Ẍ -> X, ̈ (308)
+ { 0x1e8d, 0x78 }, // ẍ -> x, ̈ (308)
+ { 0x1e8e, 0x59 }, // Ẏ -> Y, ̇ (307)
+ { 0x1e8f, 0x79 }, // ẏ -> y, ̇ (307)
+ { 0x1e90, 0x5a }, // Ẑ -> Z, ̂ (302)
+ { 0x1e91, 0x7a }, // ẑ -> z, ̂ (302)
+ { 0x1e92, 0x5a }, // Ẓ -> Z, ̣ (323)
+ { 0x1e93, 0x7a }, // ẓ -> z, ̣ (323)
+ { 0x1e94, 0x5a }, // Ẕ -> Z, ̱ (331)
+ { 0x1e95, 0x7a }, // ẕ -> z, ̱ (331)
+ { 0x1e96, 0x68 }, // ẖ -> h, ̱ (331)
+ { 0x1e97, 0x74 }, // ẗ -> t, ̈ (308)
+ { 0x1e98, 0x77 }, // ẘ -> w, ̊ (30a)
+ { 0x1e99, 0x79 }, // ẙ -> y, ̊ (30a)
+ { 0x1e9b, 0x73 }, // ẛ -> s, ̇ (307)
+ { 0x1ea0, 0x41 }, // Ạ -> A, ̣ (323)
+ { 0x1ea1, 0x61 }, // ạ -> a, ̣ (323)
+ { 0x1ea2, 0x41 }, // Ả -> A, ̉ (309)
+ { 0x1ea3, 0x61 }, // ả -> a, ̉ (309)
+ { 0x1ea4, 0x41 }, // Ấ -> A, ̂ (302), ́ (301)
+ { 0x1ea5, 0x61 }, // ấ -> a, ̂ (302), ́ (301)
+ { 0x1ea6, 0x41 }, // Ầ -> A, ̂ (302), ̀ (300)
+ { 0x1ea7, 0x61 }, // ầ -> a, ̂ (302), ̀ (300)
+ { 0x1ea8, 0x41 }, // Ẩ -> A, ̂ (302), ̉ (309)
+ { 0x1ea9, 0x61 }, // ẩ -> a, ̂ (302), ̉ (309)
+ { 0x1eaa, 0x41 }, // Ẫ -> A, ̂ (302), ̃ (303)
+ { 0x1eab, 0x61 }, // ẫ -> a, ̂ (302), ̃ (303)
+ { 0x1eac, 0x41 }, // Ậ -> A, ̣ (323), ̂ (302)
+ { 0x1ead, 0x61 }, // ậ -> a, ̣ (323), ̂ (302)
+ { 0x1eae, 0x41 }, // Ắ -> A, ̆ (306), ́ (301)
+ { 0x1eaf, 0x61 }, // ắ -> a, ̆ (306), ́ (301)
+ { 0x1eb0, 0x41 }, // Ằ -> A, ̆ (306), ̀ (300)
+ { 0x1eb1, 0x61 }, // ằ -> a, ̆ (306), ̀ (300)
+ { 0x1eb2, 0x41 }, // Ẳ -> A, ̆ (306), ̉ (309)
+ { 0x1eb3, 0x61 }, // ẳ -> a, ̆ (306), ̉ (309)
+ { 0x1eb4, 0x41 }, // Ẵ -> A, ̆ (306), ̃ (303)
+ { 0x1eb5, 0x61 }, // ẵ -> a, ̆ (306), ̃ (303)
+ { 0x1eb6, 0x41 }, // Ặ -> A, ̣ (323), ̆ (306)
+ { 0x1eb7, 0x61 }, // ặ -> a, ̣ (323), ̆ (306)
+ { 0x1eb8, 0x45 }, // Ẹ -> E, ̣ (323)
+ { 0x1eb9, 0x65 }, // ẹ -> e, ̣ (323)
+ { 0x1eba, 0x45 }, // Ẻ -> E, ̉ (309)
+ { 0x1ebb, 0x65 }, // ẻ -> e, ̉ (309)
+ { 0x1ebc, 0x45 }, // Ẽ -> E, ̃ (303)
+ { 0x1ebd, 0x65 }, // ẽ -> e, ̃ (303)
+ { 0x1ebe, 0x45 }, // Ế -> E, ̂ (302), ́ (301)
+ { 0x1ebf, 0x65 }, // ế -> e, ̂ (302), ́ (301)
+ { 0x1ec0, 0x45 }, // Ề -> E, ̂ (302), ̀ (300)
+ { 0x1ec1, 0x65 }, // ề -> e, ̂ (302), ̀ (300)
+ { 0x1ec2, 0x45 }, // Ể -> E, ̂ (302), ̉ (309)
+ { 0x1ec3, 0x65 }, // ể -> e, ̂ (302), ̉ (309)
+ { 0x1ec4, 0x45 }, // Ễ -> E, ̂ (302), ̃ (303)
+ { 0x1ec5, 0x65 }, // ễ -> e, ̂ (302), ̃ (303)
+ { 0x1ec6, 0x45 }, // Ệ -> E, ̣ (323), ̂ (302)
+ { 0x1ec7, 0x65 }, // ệ -> e, ̣ (323), ̂ (302)
+ { 0x1ec8, 0x49 }, // Ỉ -> I, ̉ (309)
+ { 0x1ec9, 0x69 }, // ỉ -> i, ̉ (309)
+ { 0x1eca, 0x49 }, // Ị -> I, ̣ (323)
+ { 0x1ecb, 0x69 }, // ị -> i, ̣ (323)
+ { 0x1ecc, 0x4f }, // Ọ -> O, ̣ (323)
+ { 0x1ecd, 0x6f }, // ọ -> o, ̣ (323)
+ { 0x1ece, 0x4f }, // Ỏ -> O, ̉ (309)
+ { 0x1ecf, 0x6f }, // ỏ -> o, ̉ (309)
+ { 0x1ed0, 0x4f }, // Ố -> O, ̂ (302), ́ (301)
+ { 0x1ed1, 0x6f }, // ố -> o, ̂ (302), ́ (301)
+ { 0x1ed2, 0x4f }, // Ồ -> O, ̂ (302), ̀ (300)
+ { 0x1ed3, 0x6f }, // ồ -> o, ̂ (302), ̀ (300)
+ { 0x1ed4, 0x4f }, // Ổ -> O, ̂ (302), ̉ (309)
+ { 0x1ed5, 0x6f }, // ổ -> o, ̂ (302), ̉ (309)
+ { 0x1ed6, 0x4f }, // Ỗ -> O, ̂ (302), ̃ (303)
+ { 0x1ed7, 0x6f }, // ỗ -> o, ̂ (302), ̃ (303)
+ { 0x1ed8, 0x4f }, // Ộ -> O, ̣ (323), ̂ (302)
+ { 0x1ed9, 0x6f }, // ộ -> o, ̣ (323), ̂ (302)
+ { 0x1eda, 0x4f }, // Ớ -> O, ̛ (31b), ́ (301)
+ { 0x1edb, 0x6f }, // ớ -> o, ̛ (31b), ́ (301)
+ { 0x1edc, 0x4f }, // Ờ -> O, ̛ (31b), ̀ (300)
+ { 0x1edd, 0x6f }, // ờ -> o, ̛ (31b), ̀ (300)
+ { 0x1ede, 0x4f }, // Ở -> O, ̛ (31b), ̉ (309)
+ { 0x1edf, 0x6f }, // ở -> o, ̛ (31b), ̉ (309)
+ { 0x1ee0, 0x4f }, // Ỡ -> O, ̛ (31b), ̃ (303)
+ { 0x1ee1, 0x6f }, // ỡ -> o, ̛ (31b), ̃ (303)
+ { 0x1ee2, 0x4f }, // Ợ -> O, ̛ (31b), ̣ (323)
+ { 0x1ee3, 0x6f }, // ợ -> o, ̛ (31b), ̣ (323)
+ { 0x1ee4, 0x55 }, // Ụ -> U, ̣ (323)
+ { 0x1ee5, 0x75 }, // ụ -> u, ̣ (323)
+ { 0x1ee6, 0x55 }, // Ủ -> U, ̉ (309)
+ { 0x1ee7, 0x75 }, // ủ -> u, ̉ (309)
+ { 0x1ee8, 0x55 }, // Ứ -> U, ̛ (31b), ́ (301)
+ { 0x1ee9, 0x75 }, // ứ -> u, ̛ (31b), ́ (301)
+ { 0x1eea, 0x55 }, // Ừ -> U, ̛ (31b), ̀ (300)
+ { 0x1eeb, 0x75 }, // ừ -> u, ̛ (31b), ̀ (300)
+ { 0x1eec, 0x55 }, // Ử -> U, ̛ (31b), ̉ (309)
+ { 0x1eed, 0x75 }, // ử -> u, ̛ (31b), ̉ (309)
+ { 0x1eee, 0x55 }, // Ữ -> U, ̛ (31b), ̃ (303)
+ { 0x1eef, 0x75 }, // ữ -> u, ̛ (31b), ̃ (303)
+ { 0x1ef0, 0x55 }, // Ự -> U, ̛ (31b), ̣ (323)
+ { 0x1ef1, 0x75 }, // ự -> u, ̛ (31b), ̣ (323)
+ { 0x1ef2, 0x59 }, // Ỳ -> Y, ̀ (300)
+ { 0x1ef3, 0x79 }, // ỳ -> y, ̀ (300)
+ { 0x1ef4, 0x59 }, // Ỵ -> Y, ̣ (323)
+ { 0x1ef5, 0x79 }, // ỵ -> y, ̣ (323)
+ { 0x1ef6, 0x59 }, // Ỷ -> Y, ̉ (309)
+ { 0x1ef7, 0x79 }, // ỷ -> y, ̉ (309)
+ { 0x1ef8, 0x59 }, // Ỹ -> Y, ̃ (303)
+ { 0x1ef9, 0x79 }, // ỹ -> y, ̃ (303)
+ { 0x1f00, 0x3b1 }, // ἀ -> α, ̓ (313)
+ { 0x1f01, 0x3b1 }, // ἁ -> α, ̔ (314)
+ { 0x1f02, 0x3b1 }, // ἂ -> α, ̓ (313), ̀ (300)
+ { 0x1f03, 0x3b1 }, // ἃ -> α, ̔ (314), ̀ (300)
+ { 0x1f04, 0x3b1 }, // ἄ -> α, ̓ (313), ́ (301)
+ { 0x1f05, 0x3b1 }, // ἅ -> α, ̔ (314), ́ (301)
+ { 0x1f06, 0x3b1 }, // ἆ -> α, ̓ (313), ͂ (342)
+ { 0x1f07, 0x3b1 }, // ἇ -> α, ̔ (314), ͂ (342)
+ { 0x1f08, 0x391 }, // Ἀ -> Α, ̓ (313)
+ { 0x1f09, 0x391 }, // Ἁ -> Α, ̔ (314)
+ { 0x1f0a, 0x391 }, // Ἂ -> Α, ̓ (313), ̀ (300)
+ { 0x1f0b, 0x391 }, // Ἃ -> Α, ̔ (314), ̀ (300)
+ { 0x1f0c, 0x391 }, // Ἄ -> Α, ̓ (313), ́ (301)
+ { 0x1f0d, 0x391 }, // Ἅ -> Α, ̔ (314), ́ (301)
+ { 0x1f0e, 0x391 }, // Ἆ -> Α, ̓ (313), ͂ (342)
+ { 0x1f0f, 0x391 }, // Ἇ -> Α, ̔ (314), ͂ (342)
+ { 0x1f10, 0x3b5 }, // ἐ -> ε, ̓ (313)
+ { 0x1f11, 0x3b5 }, // ἑ -> ε, ̔ (314)
+ { 0x1f12, 0x3b5 }, // ἒ -> ε, ̓ (313), ̀ (300)
+ { 0x1f13, 0x3b5 }, // ἓ -> ε, ̔ (314), ̀ (300)
+ { 0x1f14, 0x3b5 }, // ἔ -> ε, ̓ (313), ́ (301)
+ { 0x1f15, 0x3b5 }, // ἕ -> ε, ̔ (314), ́ (301)
+ { 0x1f18, 0x395 }, // Ἐ -> Ε, ̓ (313)
+ { 0x1f19, 0x395 }, // Ἑ -> Ε, ̔ (314)
+ { 0x1f1a, 0x395 }, // Ἒ -> Ε, ̓ (313), ̀ (300)
+ { 0x1f1b, 0x395 }, // Ἓ -> Ε, ̔ (314), ̀ (300)
+ { 0x1f1c, 0x395 }, // Ἔ -> Ε, ̓ (313), ́ (301)
+ { 0x1f1d, 0x395 }, // Ἕ -> Ε, ̔ (314), ́ (301)
+ { 0x1f20, 0x3b7 }, // ἠ -> η, ̓ (313)
+ { 0x1f21, 0x3b7 }, // ἡ -> η, ̔ (314)
+ { 0x1f22, 0x3b7 }, // ἢ -> η, ̓ (313), ̀ (300)
+ { 0x1f23, 0x3b7 }, // ἣ -> η, ̔ (314), ̀ (300)
+ { 0x1f24, 0x3b7 }, // ἤ -> η, ̓ (313), ́ (301)
+ { 0x1f25, 0x3b7 }, // ἥ -> η, ̔ (314), ́ (301)
+ { 0x1f26, 0x3b7 }, // ἦ -> η, ̓ (313), ͂ (342)
+ { 0x1f27, 0x3b7 }, // ἧ -> η, ̔ (314), ͂ (342)
+ { 0x1f28, 0x397 }, // Ἠ -> Η, ̓ (313)
+ { 0x1f29, 0x397 }, // Ἡ -> Η, ̔ (314)
+ { 0x1f2a, 0x397 }, // Ἢ -> Η, ̓ (313), ̀ (300)
+ { 0x1f2b, 0x397 }, // Ἣ -> Η, ̔ (314), ̀ (300)
+ { 0x1f2c, 0x397 }, // Ἤ -> Η, ̓ (313), ́ (301)
+ { 0x1f2d, 0x397 }, // Ἥ -> Η, ̔ (314), ́ (301)
+ { 0x1f2e, 0x397 }, // Ἦ -> Η, ̓ (313), ͂ (342)
+ { 0x1f2f, 0x397 }, // Ἧ -> Η, ̔ (314), ͂ (342)
+ { 0x1f30, 0x3b9 }, // ἰ -> ι, ̓ (313)
+ { 0x1f31, 0x3b9 }, // ἱ -> ι, ̔ (314)
+ { 0x1f32, 0x3b9 }, // ἲ -> ι, ̓ (313), ̀ (300)
+ { 0x1f33, 0x3b9 }, // ἳ -> ι, ̔ (314), ̀ (300)
+ { 0x1f34, 0x3b9 }, // ἴ -> ι, ̓ (313), ́ (301)
+ { 0x1f35, 0x3b9 }, // ἵ -> ι, ̔ (314), ́ (301)
+ { 0x1f36, 0x3b9 }, // ἶ -> ι, ̓ (313), ͂ (342)
+ { 0x1f37, 0x3b9 }, // ἷ -> ι, ̔ (314), ͂ (342)
+ { 0x1f38, 0x399 }, // Ἰ -> Ι, ̓ (313)
+ { 0x1f39, 0x399 }, // Ἱ -> Ι, ̔ (314)
+ { 0x1f3a, 0x399 }, // Ἲ -> Ι, ̓ (313), ̀ (300)
+ { 0x1f3b, 0x399 }, // Ἳ -> Ι, ̔ (314), ̀ (300)
+ { 0x1f3c, 0x399 }, // Ἴ -> Ι, ̓ (313), ́ (301)
+ { 0x1f3d, 0x399 }, // Ἵ -> Ι, ̔ (314), ́ (301)
+ { 0x1f3e, 0x399 }, // Ἶ -> Ι, ̓ (313), ͂ (342)
+ { 0x1f3f, 0x399 }, // Ἷ -> Ι, ̔ (314), ͂ (342)
+ { 0x1f40, 0x3bf }, // ὀ -> ο, ̓ (313)
+ { 0x1f41, 0x3bf }, // ὁ -> ο, ̔ (314)
+ { 0x1f42, 0x3bf }, // ὂ -> ο, ̓ (313), ̀ (300)
+ { 0x1f43, 0x3bf }, // ὃ -> ο, ̔ (314), ̀ (300)
+ { 0x1f44, 0x3bf }, // ὄ -> ο, ̓ (313), ́ (301)
+ { 0x1f45, 0x3bf }, // ὅ -> ο, ̔ (314), ́ (301)
+ { 0x1f48, 0x39f }, // Ὀ -> Ο, ̓ (313)
+ { 0x1f49, 0x39f }, // Ὁ -> Ο, ̔ (314)
+ { 0x1f4a, 0x39f }, // Ὂ -> Ο, ̓ (313), ̀ (300)
+ { 0x1f4b, 0x39f }, // Ὃ -> Ο, ̔ (314), ̀ (300)
+ { 0x1f4c, 0x39f }, // Ὄ -> Ο, ̓ (313), ́ (301)
+ { 0x1f4d, 0x39f }, // Ὅ -> Ο, ̔ (314), ́ (301)
+ { 0x1f50, 0x3c5 }, // ὐ -> υ, ̓ (313)
+ { 0x1f51, 0x3c5 }, // ὑ -> υ, ̔ (314)
+ { 0x1f52, 0x3c5 }, // ὒ -> υ, ̓ (313), ̀ (300)
+ { 0x1f53, 0x3c5 }, // ὓ -> υ, ̔ (314), ̀ (300)
+ { 0x1f54, 0x3c5 }, // ὔ -> υ, ̓ (313), ́ (301)
+ { 0x1f55, 0x3c5 }, // ὕ -> υ, ̔ (314), ́ (301)
+ { 0x1f56, 0x3c5 }, // ὖ -> υ, ̓ (313), ͂ (342)
+ { 0x1f57, 0x3c5 }, // ὗ -> υ, ̔ (314), ͂ (342)
+ { 0x1f59, 0x3a5 }, // Ὑ -> Υ, ̔ (314)
+ { 0x1f5b, 0x3a5 }, // Ὓ -> Υ, ̔ (314), ̀ (300)
+ { 0x1f5d, 0x3a5 }, // Ὕ -> Υ, ̔ (314), ́ (301)
+ { 0x1f5f, 0x3a5 }, // Ὗ -> Υ, ̔ (314), ͂ (342)
+ { 0x1f60, 0x3c9 }, // ὠ -> ω, ̓ (313)
+ { 0x1f61, 0x3c9 }, // ὡ -> ω, ̔ (314)
+ { 0x1f62, 0x3c9 }, // ὢ -> ω, ̓ (313), ̀ (300)
+ { 0x1f63, 0x3c9 }, // ὣ -> ω, ̔ (314), ̀ (300)
+ { 0x1f64, 0x3c9 }, // ὤ -> ω, ̓ (313), ́ (301)
+ { 0x1f65, 0x3c9 }, // ὥ -> ω, ̔ (314), ́ (301)
+ { 0x1f66, 0x3c9 }, // ὦ -> ω, ̓ (313), ͂ (342)
+ { 0x1f67, 0x3c9 }, // ὧ -> ω, ̔ (314), ͂ (342)
+ { 0x1f68, 0x3a9 }, // Ὠ -> Ω, ̓ (313)
+ { 0x1f69, 0x3a9 }, // Ὡ -> Ω, ̔ (314)
+ { 0x1f6a, 0x3a9 }, // Ὢ -> Ω, ̓ (313), ̀ (300)
+ { 0x1f6b, 0x3a9 }, // Ὣ -> Ω, ̔ (314), ̀ (300)
+ { 0x1f6c, 0x3a9 }, // Ὤ -> Ω, ̓ (313), ́ (301)
+ { 0x1f6d, 0x3a9 }, // Ὥ -> Ω, ̔ (314), ́ (301)
+ { 0x1f6e, 0x3a9 }, // Ὦ -> Ω, ̓ (313), ͂ (342)
+ { 0x1f6f, 0x3a9 }, // Ὧ -> Ω, ̔ (314), ͂ (342)
+ { 0x1f70, 0x3b1 }, // ὰ -> α, ̀ (300)
+ { 0x1f71, 0x3b1 }, // ά -> α, ́ (301)
+ { 0x1f72, 0x3b5 }, // ὲ -> ε, ̀ (300)
+ { 0x1f73, 0x3b5 }, // έ -> ε, ́ (301)
+ { 0x1f74, 0x3b7 }, // ὴ -> η, ̀ (300)
+ { 0x1f75, 0x3b7 }, // ή -> η, ́ (301)
+ { 0x1f76, 0x3b9 }, // ὶ -> ι, ̀ (300)
+ { 0x1f77, 0x3b9 }, // ί -> ι, ́ (301)
+ { 0x1f78, 0x3bf }, // ὸ -> ο, ̀ (300)
+ { 0x1f79, 0x3bf }, // ό -> ο, ́ (301)
+ { 0x1f7a, 0x3c5 }, // ὺ -> υ, ̀ (300)
+ { 0x1f7b, 0x3c5 }, // ύ -> υ, ́ (301)
+ { 0x1f7c, 0x3c9 }, // ὼ -> ω, ̀ (300)
+ { 0x1f7d, 0x3c9 }, // ώ -> ω, ́ (301)
+ { 0x1f80, 0x3b1 }, // ᾀ -> α, ̓ (313), ͅ (345)
+ { 0x1f81, 0x3b1 }, // ᾁ -> α, ̔ (314), ͅ (345)
+ { 0x1f82, 0x3b1 }, // ᾂ -> α, ̓ (313), ̀ (300), ͅ (345)
+ { 0x1f83, 0x3b1 }, // ᾃ -> α, ̔ (314), ̀ (300), ͅ (345)
+ { 0x1f84, 0x3b1 }, // ᾄ -> α, ̓ (313), ́ (301), ͅ (345)
+ { 0x1f85, 0x3b1 }, // ᾅ -> α, ̔ (314), ́ (301), ͅ (345)
+ { 0x1f86, 0x3b1 }, // ᾆ -> α, ̓ (313), ͂ (342), ͅ (345)
+ { 0x1f87, 0x3b1 }, // ᾇ -> α, ̔ (314), ͂ (342), ͅ (345)
+ { 0x1f88, 0x391 }, // ᾈ -> Α, ̓ (313), ͅ (345)
+ { 0x1f89, 0x391 }, // ᾉ -> Α, ̔ (314), ͅ (345)
+ { 0x1f8a, 0x391 }, // ᾊ -> Α, ̓ (313), ̀ (300), ͅ (345)
+ { 0x1f8b, 0x391 }, // ᾋ -> Α, ̔ (314), ̀ (300), ͅ (345)
+ { 0x1f8c, 0x391 }, // ᾌ -> Α, ̓ (313), ́ (301), ͅ (345)
+ { 0x1f8d, 0x391 }, // ᾍ -> Α, ̔ (314), ́ (301), ͅ (345)
+ { 0x1f8e, 0x391 }, // ᾎ -> Α, ̓ (313), ͂ (342), ͅ (345)
+ { 0x1f8f, 0x391 }, // ᾏ -> Α, ̔ (314), ͂ (342), ͅ (345)
+ { 0x1f90, 0x3b7 }, // ᾐ -> η, ̓ (313), ͅ (345)
+ { 0x1f91, 0x3b7 }, // ᾑ -> η, ̔ (314), ͅ (345)
+ { 0x1f92, 0x3b7 }, // ᾒ -> η, ̓ (313), ̀ (300), ͅ (345)
+ { 0x1f93, 0x3b7 }, // ᾓ -> η, ̔ (314), ̀ (300), ͅ (345)
+ { 0x1f94, 0x3b7 }, // ᾔ -> η, ̓ (313), ́ (301), ͅ (345)
+ { 0x1f95, 0x3b7 }, // ᾕ -> η, ̔ (314), ́ (301), ͅ (345)
+ { 0x1f96, 0x3b7 }, // ᾖ -> η, ̓ (313), ͂ (342), ͅ (345)
+ { 0x1f97, 0x3b7 }, // ᾗ -> η, ̔ (314), ͂ (342), ͅ (345)
+ { 0x1f98, 0x397 }, // ᾘ -> Η, ̓ (313), ͅ (345)
+ { 0x1f99, 0x397 }, // ᾙ -> Η, ̔ (314), ͅ (345)
+ { 0x1f9a, 0x397 }, // ᾚ -> Η, ̓ (313), ̀ (300), ͅ (345)
+ { 0x1f9b, 0x397 }, // ᾛ -> Η, ̔ (314), ̀ (300), ͅ (345)
+ { 0x1f9c, 0x397 }, // ᾜ -> Η, ̓ (313), ́ (301), ͅ (345)
+ { 0x1f9d, 0x397 }, // ᾝ -> Η, ̔ (314), ́ (301), ͅ (345)
+ { 0x1f9e, 0x397 }, // ᾞ -> Η, ̓ (313), ͂ (342), ͅ (345)
+ { 0x1f9f, 0x397 }, // ᾟ -> Η, ̔ (314), ͂ (342), ͅ (345)
+ { 0x1fa0, 0x3c9 }, // ᾠ -> ω, ̓ (313), ͅ (345)
+ { 0x1fa1, 0x3c9 }, // ᾡ -> ω, ̔ (314), ͅ (345)
+ { 0x1fa2, 0x3c9 }, // ᾢ -> ω, ̓ (313), ̀ (300), ͅ (345)
+ { 0x1fa3, 0x3c9 }, // ᾣ -> ω, ̔ (314), ̀ (300), ͅ (345)
+ { 0x1fa4, 0x3c9 }, // ᾤ -> ω, ̓ (313), ́ (301), ͅ (345)
+ { 0x1fa5, 0x3c9 }, // ᾥ -> ω, ̔ (314), ́ (301), ͅ (345)
+ { 0x1fa6, 0x3c9 }, // ᾦ -> ω, ̓ (313), ͂ (342), ͅ (345)
+ { 0x1fa7, 0x3c9 }, // ᾧ -> ω, ̔ (314), ͂ (342), ͅ (345)
+ { 0x1fa8, 0x3a9 }, // ᾨ -> Ω, ̓ (313), ͅ (345)
+ { 0x1fa9, 0x3a9 }, // ᾩ -> Ω, ̔ (314), ͅ (345)
+ { 0x1faa, 0x3a9 }, // ᾪ -> Ω, ̓ (313), ̀ (300), ͅ (345)
+ { 0x1fab, 0x3a9 }, // ᾫ -> Ω, ̔ (314), ̀ (300), ͅ (345)
+ { 0x1fac, 0x3a9 }, // ᾬ -> Ω, ̓ (313), ́ (301), ͅ (345)
+ { 0x1fad, 0x3a9 }, // ᾭ -> Ω, ̔ (314), ́ (301), ͅ (345)
+ { 0x1fae, 0x3a9 }, // ᾮ -> Ω, ̓ (313), ͂ (342), ͅ (345)
+ { 0x1faf, 0x3a9 }, // ᾯ -> Ω, ̔ (314), ͂ (342), ͅ (345)
+ { 0x1fb0, 0x3b1 }, // ᾰ -> α, ̆ (306)
+ { 0x1fb1, 0x3b1 }, // ᾱ -> α, ̄ (304)
+ { 0x1fb2, 0x3b1 }, // ᾲ -> α, ̀ (300), ͅ (345)
+ { 0x1fb3, 0x3b1 }, // ᾳ -> α, ͅ (345)
+ { 0x1fb4, 0x3b1 }, // ᾴ -> α, ́ (301), ͅ (345)
+ { 0x1fb6, 0x3b1 }, // ᾶ -> α, ͂ (342)
+ { 0x1fb7, 0x3b1 }, // ᾷ -> α, ͂ (342), ͅ (345)
+ { 0x1fb8, 0x391 }, // Ᾰ -> Α, ̆ (306)
+ { 0x1fb9, 0x391 }, // Ᾱ -> Α, ̄ (304)
+ { 0x1fba, 0x391 }, // Ὰ -> Α, ̀ (300)
+ { 0x1fbb, 0x391 }, // Ά -> Α, ́ (301)
+ { 0x1fbc, 0x391 }, // ᾼ -> Α, ͅ (345)
+ { 0x1fc2, 0x3b7 }, // ῂ -> η, ̀ (300), ͅ (345)
+ { 0x1fc3, 0x3b7 }, // ῃ -> η, ͅ (345)
+ { 0x1fc4, 0x3b7 }, // ῄ -> η, ́ (301), ͅ (345)
+ { 0x1fc6, 0x3b7 }, // ῆ -> η, ͂ (342)
+ { 0x1fc7, 0x3b7 }, // ῇ -> η, ͂ (342), ͅ (345)
+ { 0x1fc8, 0x395 }, // Ὲ -> Ε, ̀ (300)
+ { 0x1fc9, 0x395 }, // Έ -> Ε, ́ (301)
+ { 0x1fca, 0x397 }, // Ὴ -> Η, ̀ (300)
+ { 0x1fcb, 0x397 }, // Ή -> Η, ́ (301)
+ { 0x1fcc, 0x397 }, // ῌ -> Η, ͅ (345)
+ { 0x1fd0, 0x3b9 }, // ῐ -> ι, ̆ (306)
+ { 0x1fd1, 0x3b9 }, // ῑ -> ι, ̄ (304)
+ { 0x1fd2, 0x3b9 }, // ῒ -> ι, ̈ (308), ̀ (300)
+ { 0x1fd3, 0x3b9 }, // ΐ -> ι, ̈ (308), ́ (301)
+ { 0x1fd6, 0x3b9 }, // ῖ -> ι, ͂ (342)
+ { 0x1fd7, 0x3b9 }, // ῗ -> ι, ̈ (308), ͂ (342)
+ { 0x1fd8, 0x399 }, // Ῐ -> Ι, ̆ (306)
+ { 0x1fd9, 0x399 }, // Ῑ -> Ι, ̄ (304)
+ { 0x1fda, 0x399 }, // Ὶ -> Ι, ̀ (300)
+ { 0x1fdb, 0x399 }, // Ί -> Ι, ́ (301)
+ { 0x1fe0, 0x3c5 }, // ῠ -> υ, ̆ (306)
+ { 0x1fe1, 0x3c5 }, // ῡ -> υ, ̄ (304)
+ { 0x1fe2, 0x3c5 }, // ῢ -> υ, ̈ (308), ̀ (300)
+ { 0x1fe3, 0x3c5 }, // ΰ -> υ, ̈ (308), ́ (301)
+ { 0x1fe4, 0x3c1 }, // ῤ -> ρ, ̓ (313)
+ { 0x1fe5, 0x3c1 }, // ῥ -> ρ, ̔ (314)
+ { 0x1fe6, 0x3c5 }, // ῦ -> υ, ͂ (342)
+ { 0x1fe7, 0x3c5 }, // ῧ -> υ, ̈ (308), ͂ (342)
+ { 0x1fe8, 0x3a5 }, // Ῠ -> Υ, ̆ (306)
+ { 0x1fe9, 0x3a5 }, // Ῡ -> Υ, ̄ (304)
+ { 0x1fea, 0x3a5 }, // Ὺ -> Υ, ̀ (300)
+ { 0x1feb, 0x3a5 }, // Ύ -> Υ, ́ (301)
+ { 0x1fec, 0x3a1 }, // Ῥ -> Ρ, ̔ (314)
+ { 0x1ff2, 0x3c9 }, // ῲ -> ω, ̀ (300), ͅ (345)
+ { 0x1ff3, 0x3c9 }, // ῳ -> ω, ͅ (345)
+ { 0x1ff4, 0x3c9 }, // ῴ -> ω, ́ (301), ͅ (345)
+ { 0x1ff6, 0x3c9 }, // ῶ -> ω, ͂ (342)
+ { 0x1ff7, 0x3c9 }, // ῷ -> ω, ͂ (342), ͅ (345)
+ { 0x1ff8, 0x39f }, // Ὸ -> Ο, ̀ (300)
+ { 0x1ff9, 0x39f }, // Ό -> Ο, ́ (301)
+ { 0x1ffa, 0x3a9 }, // Ὼ -> Ω, ̀ (300)
+ { 0x1ffb, 0x3a9 }, // Ώ -> Ω, ́ (301)
+ { 0x1ffc, 0x3a9 }, // ῼ -> Ω, ͅ (345)
+ { 0x2010, 0x2d }, // ‐ -> -,
+ { 0x2012, 0x2d }, // ‒ -> -,
+ { 0x2013, 0x2d }, // – -> -,
+ { 0x2014, 0x2d }, // — -> -,
+ { 0x2015, 0x2d }, // ― -> -,
+ { 0x2018, 0x27 }, // ‘ -> ',
+ { 0x2019, 0x27 }, // ’ -> ',
+ { 0x201c, 0x22 }, // “ -> ",
+ { 0x201d, 0x22 }, // ” -> ",
+ { 0x2026, 0x2e }, // … -> .,
+ { 0x2032, 0x27 }, // ′ -> ',
+ { 0x2033, 0x22 }, // ″ -> ",
+ { 0x212b, 0x41 }, // Å -> A, ̊ (30a)
+ { 0x219a, 0x2190 }, // ↚ -> ←, ̸ (338)
+ { 0x219b, 0x2192 }, // ↛ -> →, ̸ (338)
+ { 0x21ae, 0x2194 }, // ↮ -> ↔, ̸ (338)
+ { 0x21cd, 0x21d0 }, // ⇍ -> ⇐, ̸ (338)
+ { 0x21ce, 0x21d4 }, // ⇎ -> ⇔, ̸ (338)
+ { 0x21cf, 0x21d2 }, // ⇏ -> ⇒, ̸ (338)
+ { 0x2204, 0x2203 }, // ∄ -> ∃, ̸ (338)
+ { 0x2209, 0x2208 }, // ∉ -> ∈, ̸ (338)
+ { 0x220c, 0x220b }, // ∌ -> ∋, ̸ (338)
+ { 0x2212, 0x2d }, // − -> -,
+ { 0x2224, 0x2223 }, // ∤ -> ∣, ̸ (338)
+ { 0x2226, 0x2225 }, // ∦ -> ∥, ̸ (338)
+ { 0x2241, 0x223c }, // ≁ -> ∼, ̸ (338)
+ { 0x2244, 0x2243 }, // ≄ -> ≃, ̸ (338)
+ { 0x2247, 0x2245 }, // ≇ -> ≅, ̸ (338)
+ { 0x2249, 0x2248 }, // ≉ -> ≈, ̸ (338)
+ { 0x2260, 0x3d }, // ≠ -> =, ̸ (338)
+ { 0x2262, 0x2261 }, // ≢ -> ≡, ̸ (338)
+ { 0x226d, 0x224d }, // ≭ -> ≍, ̸ (338)
+ { 0x226e, 0x3c }, // ≮ -> <, ̸ (338)
+ { 0x226f, 0x3e }, // ≯ -> >, ̸ (338)
+ { 0x2270, 0x2264 }, // ≰ -> ≤, ̸ (338)
+ { 0x2271, 0x2265 }, // ≱ -> ≥, ̸ (338)
+ { 0x2274, 0x2272 }, // ≴ -> ≲, ̸ (338)
+ { 0x2275, 0x2273 }, // ≵ -> ≳, ̸ (338)
+ { 0x2278, 0x2276 }, // ≸ -> ≶, ̸ (338)
+ { 0x2279, 0x2277 }, // ≹ -> ≷, ̸ (338)
+ { 0x2280, 0x227a }, // ⊀ -> ≺, ̸ (338)
+ { 0x2281, 0x227b }, // ⊁ -> ≻, ̸ (338)
+ { 0x2284, 0x2282 }, // ⊄ -> ⊂, ̸ (338)
+ { 0x2285, 0x2283 }, // ⊅ -> ⊃, ̸ (338)
+ { 0x2288, 0x2286 }, // ⊈ -> ⊆, ̸ (338)
+ { 0x2289, 0x2287 }, // ⊉ -> ⊇, ̸ (338)
+ { 0x22ac, 0x22a2 }, // ⊬ -> ⊢, ̸ (338)
+ { 0x22ad, 0x22a8 }, // ⊭ -> ⊨, ̸ (338)
+ { 0x22ae, 0x22a9 }, // ⊮ -> ⊩, ̸ (338)
+ { 0x22af, 0x22ab }, // ⊯ -> ⊫, ̸ (338)
+ { 0x22e0, 0x227c }, // ⋠ -> ≼, ̸ (338)
+ { 0x22e1, 0x227d }, // ⋡ -> ≽, ̸ (338)
+ { 0x22e2, 0x2291 }, // ⋢ -> ⊑, ̸ (338)
+ { 0x22e3, 0x2292 }, // ⋣ -> ⊒, ̸ (338)
+ { 0x22ea, 0x22b2 }, // ⋪ -> ⊲, ̸ (338)
+ { 0x22eb, 0x22b3 }, // ⋫ -> ⊳, ̸ (338)
+ { 0x22ec, 0x22b4 }, // ⋬ -> ⊴, ̸ (338)
+ { 0x22ed, 0x22b5 }, // ⋭ -> ⊵, ̸ (338)
+ { 0x2adc, 0x2add }, // ⫝̸ -> ⫝, ̸ (338)
+ { 0x3003, 0x22 }, // 〃 -> ",
+};
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#ifdef HAVE_CONFIG
+#include "config/utils.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdint.h>
+#ifdef HAVE_BYTESWAP_H
+#include <byteswap.h>
+#endif
+
+#define N_ELEMENTS(array) (sizeof(array) / sizeof((array)[0]))
+
+#define STRINGIZE_HELPER(x) #x
+#define STRINGIZE(x) STRINGIZE_HELPER(x)
+
+#define getentry(ptr, offset, type) (*((type *) ((char *) (ptr) + (offset))))
+
+static inline int min(int a, int b)
+{
+ return a < b ? a : b;
+}
+
+static inline int max(int a, int b)
+{
+ return a > b ? a : b;
+}
+
+static inline int clamp(int val, int minval, int maxval)
+{
+ if (val < minval)
+ return minval;
+ if (val > maxval)
+ return maxval;
+ return val;
+}
+
+static inline int scale_from_percentage(int val, int max_val)
+{
+ if (val < 0)
+ return (val * max_val - 50) / 100;
+ return (val * max_val + 50) / 100;
+}
+
+static inline int scale_to_percentage(int val, int max_val)
+{
+ int half = max_val / 2;
+
+ if (max_val <= 0)
+ return 100;
+
+ if (val < 0)
+ return (val * 100 - half) / max_val;
+ return (val * 100 + half) / max_val;
+}
+
+static inline int str_to_int(const char *str, long int *val)
+{
+ char *end;
+
+ *val = strtol(str, &end, 10);
+ if (*str == 0 || *end != 0)
+ return -1;
+ return 0;
+}
+
+static inline int strcmp0(const char *str1, const char *str2)
+{
+ if (!str1)
+ return str2 ? -1 : 0;
+ if (!str2)
+ return 1;
+
+ return strcmp(str1, str2);
+}
+
+static inline int ends_with(const char *str, const char *suffix)
+{
+ return strstr(str, suffix) + strlen(suffix) == str + strlen(str);
+}
+
+static inline uint32_t hash_str(const char *s)
+{
+ const unsigned char *p = (const unsigned char *)s;
+ uint32_t h = 5381;
+
+ while (*p) {
+ h *= 33;
+ h ^= *p++;
+ }
+
+ return h ^ (h >> 16);
+}
+
+static inline time_t file_get_mtime(const char *filename)
+{
+ struct stat s;
+
+ /* stat follows symlinks, lstat does not */
+ if (stat(filename, &s) == -1)
+ return -1;
+ return s.st_mtime;
+}
+
+static inline void ns_sleep(int ns)
+{
+ struct timespec req;
+
+ req.tv_sec = 0;
+ req.tv_nsec = ns;
+ nanosleep(&req, NULL);
+}
+
+static inline void us_sleep(int us)
+{
+ ns_sleep(us * 1e3);
+}
+
+static inline void ms_sleep(int ms)
+{
+ ns_sleep(ms * 1e6);
+}
+
+static inline int is_http_url(const char *name)
+{
+ return strncmp(name, "http://", 7) == 0;
+}
+
+static inline int is_cdda_url(const char *name)
+{
+ return strncmp(name, "cdda://", 7) == 0;
+}
+
+static inline int is_cue_url(const char *name)
+{
+ return strncmp(name, "cue://", 6) == 0;
+}
+
+static inline int is_url(const char *name)
+{
+ return is_http_url(name) || is_cdda_url(name) || is_cue_url(name);
+}
+
+static inline int is_freeform_true(const char *c)
+{
+ return c[0] == '1' ||
+ c[0] == 'y' || c[0] == 'Y' ||
+ c[0] == 't' || c[0] == 'T';
+}
+
+/* e.g. NetBSD */
+#if defined(bswap16)
+/* GNU libc */
+#elif defined(bswap_16)
+# define bswap16 bswap_16
+/* e.g. OpenBSD */
+#elif defined(swap16)
+# define bswap16 swap16
+#else
+# define bswap16(x) \
+ ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))
+#endif
+
+static inline uint16_t swap_uint16(uint16_t x)
+{
+ return bswap16(x);
+}
+
+/* e.g. NetBSD */
+#if defined(bswap32)
+/* GNU libc */
+#elif defined(bswap_32)
+# define bswap32 bswap_32
+/* e.g. OpenBSD */
+#elif defined(swap32)
+# define bswap32 swap32
+#else
+# define bswap32(x) \
+ ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \
+ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24))
+#endif
+
+static inline uint32_t swap_uint32(uint32_t x)
+{
+ return bswap32(x);
+}
+
+static inline uint32_t read_le32(const char *buf)
+{
+ const unsigned char *b = (const unsigned char *)buf;
+
+ return b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24);
+}
+
+static inline uint16_t read_le16(const char *buf)
+{
+ const unsigned char *b = (const unsigned char *)buf;
+
+ return b[0] | (b[1] << 8);
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "xmalloc.h"
+#include "read_wrapper.h"
+#include "debug.h"
+#ifdef HAVE_CONFIG
+#include "config/tremor.h"
+#endif
+#include "comment.h"
+
+#ifdef CONFIG_TREMOR
+#include <tremor/ivorbisfile.h>
+#else
+#include <vorbis/vorbisfile.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <math.h>
+
+struct vorbis_private {
+ OggVorbis_File vf;
+ int current_section;
+};
+
+/* http://www.xiph.org/vorbis/doc/vorbisfile/callbacks.html */
+
+static size_t read_func(void *ptr, size_t size, size_t nmemb, void *datasource)
+{
+ struct input_plugin_data *ip_data = datasource;
+ int rc;
+
+ rc = read_wrapper(ip_data, ptr, size * nmemb);
+ if (rc == -1) {
+ d_print("error: %s\n", strerror(errno));
+ return 0;
+ }
+ if (rc == 0) {
+ errno = 0;
+ return 0;
+ }
+ return rc / size;
+}
+
+static int seek_func(void *datasource, ogg_int64_t offset, int whence)
+{
+ struct input_plugin_data *ip_data = datasource;
+
+ if (lseek(ip_data->fd, offset, whence) == -1)
+ return -1;
+ return 0;
+}
+
+static int close_func(void *datasource)
+{
+ struct input_plugin_data *ip_data = datasource;
+ int rc;
+
+ rc = close(ip_data->fd);
+ ip_data->fd = -1;
+ return rc;
+}
+
+static long tell_func(void *datasource)
+{
+ struct input_plugin_data *ip_data = datasource;
+ off_t off;
+
+ off = lseek(ip_data->fd, 0, SEEK_CUR);
+ return (off == -1) ? -1 : off;
+}
+
+/*
+ * typedef struct {
+ * size_t (*read_func) (void *ptr, size_t size, size_t nmemb, void *datasource);
+ * int (*seek_func) (void *datasource, ogg_int64_t offset, int whence);
+ * int (*close_func) (void *datasource);
+ * long (*tell_func) (void *datasource);
+ * } ov_callbacks;
+ */
+static ov_callbacks callbacks = {
+ .read_func = read_func,
+ .seek_func = seek_func,
+ .close_func = close_func,
+ .tell_func = tell_func
+};
+
+/* http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9 */
+static void channel_map_init_vorbis(int channels, channel_position_t *map)
+{
+ switch (channels) {
+ case 8:
+ channel_map_init_vorbis(7, map);
+ map[5] = CHANNEL_POSITION_REAR_LEFT;
+ map[6] = CHANNEL_POSITION_REAR_RIGHT;
+ map[7] = CHANNEL_POSITION_LFE;
+ break;
+ case 7:
+ channel_map_init_vorbis(3, map);
+ map[3] = CHANNEL_POSITION_SIDE_LEFT;
+ map[4] = CHANNEL_POSITION_SIDE_RIGHT;
+ map[5] = CHANNEL_POSITION_REAR_CENTER;
+ map[6] = CHANNEL_POSITION_LFE;
+ break;
+ case 6:
+ map[5] = CHANNEL_POSITION_LFE;
+ /* Fall through */
+ case 5:
+ map[3] = CHANNEL_POSITION_REAR_LEFT;
+ map[4] = CHANNEL_POSITION_REAR_RIGHT;
+ /* Fall through */
+ case 3:
+ map[0] = CHANNEL_POSITION_FRONT_LEFT;
+ map[1] = CHANNEL_POSITION_CENTER;
+ map[2] = CHANNEL_POSITION_FRONT_RIGHT;
+ break;
+ case 4:
+ map[2] = CHANNEL_POSITION_REAR_LEFT;
+ map[3] = CHANNEL_POSITION_REAR_RIGHT;
+ /* Fall through */
+ case 2:
+ map[0] = CHANNEL_POSITION_FRONT_LEFT;
+ map[1] = CHANNEL_POSITION_FRONT_RIGHT;
+ break;
+ case 1:
+ map[0] = CHANNEL_POSITION_MONO;
+ break;
+ default:
+ map[0] = CHANNEL_POSITION_INVALID;
+ break;
+ }
+}
+
+static int vorbis_open(struct input_plugin_data *ip_data)
+{
+ struct vorbis_private *priv;
+ vorbis_info *vi;
+ int rc;
+
+ priv = xnew(struct vorbis_private, 1);
+ priv->current_section = -1;
+ memset(&priv->vf, 0, sizeof(priv->vf));
+
+ rc = ov_open_callbacks(ip_data, &priv->vf, NULL, 0, callbacks);
+ if (rc != 0) {
+ d_print("ov_open failed: %d\n", rc);
+ free(priv);
+ /* ogg is a container format, so it is likely to contain
+ * something else if it isn't vorbis */
+ return -IP_ERROR_UNSUPPORTED_FILE_TYPE;
+ }
+ ip_data->private = priv;
+
+ vi = ov_info(&priv->vf, -1);
+ ip_data->sf = sf_rate(vi->rate) | sf_channels(vi->channels) | sf_bits(16) | sf_signed(1);
+#ifdef WORDS_BIGENDIAN
+ ip_data->sf |= sf_bigendian(1);
+#endif
+ channel_map_init_vorbis(vi->channels, ip_data->channel_map);
+ return 0;
+}
+
+static int vorbis_close(struct input_plugin_data *ip_data)
+{
+ struct vorbis_private *priv;
+ int rc;
+
+ priv = ip_data->private;
+ /* this closes ip_data->fd! */
+ rc = ov_clear(&priv->vf);
+ ip_data->fd = -1;
+ if (rc)
+ d_print("ov_clear returned %d\n", rc);
+ free(priv);
+ ip_data->private = NULL;
+ return 0;
+}
+
+static inline int vorbis_endian(void)
+{
+#ifdef WORDS_BIGENDIAN
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+/*
+ * OV_HOLE
+ * indicates there was an interruption in the data.
+ * (one of: garbage between pages, loss of sync followed by recapture,
+ * or a corrupt page)
+ * OV_EBADLINK
+ * indicates that an invalid stream section was supplied to libvorbisfile,
+ * or the requested link is corrupt.
+ * 0
+ * indicates EOF
+ * n
+ * indicates actual number of bytes read. ov_read() will decode at most
+ * one vorbis packet per invocation, so the value returned will generally
+ * be less than length.
+ */
+static int vorbis_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct vorbis_private *priv;
+ int rc;
+ int current_section;
+
+ priv = ip_data->private;
+#ifdef CONFIG_TREMOR
+ /* Tremor can only handle signed 16 bit data */
+ rc = ov_read(&priv->vf, buffer, count, ¤t_section);
+#else
+ rc = ov_read(&priv->vf, buffer, count, vorbis_endian(), 2, 1, ¤t_section);
+#endif
+
+ if (ip_data->remote && current_section != priv->current_section) {
+ ip_data->metadata_changed = 1;
+ priv->current_section = current_section;
+ }
+
+ switch (rc) {
+ case OV_HOLE:
+ errno = EAGAIN;
+ return -1;
+ case OV_EBADLINK:
+ errno = EINVAL;
+ return -1;
+ case OV_EINVAL:
+ errno = EINVAL;
+ return -1;
+ case 0:
+ if (errno) {
+ d_print("error: %s\n", strerror(errno));
+ return -1;
+/* return -IP_ERROR_INTERNAL; */
+ }
+ /* EOF */
+ return 0;
+ default:
+ if (rc < 0) {
+ d_print("error: %d\n", rc);
+ rc = -IP_ERROR_FILE_FORMAT;
+ }
+ return rc;
+ }
+}
+
+static int vorbis_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct vorbis_private *priv;
+ int rc;
+
+ priv = ip_data->private;
+
+#ifdef CONFIG_TREMOR
+ rc = ov_time_seek(&priv->vf, offset * 1000);
+#else
+ rc = ov_time_seek(&priv->vf, offset);
+#endif
+ switch (rc) {
+ case OV_ENOSEEK:
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+ case OV_EINVAL:
+ return -IP_ERROR_INTERNAL;
+ case OV_EREAD:
+ return -IP_ERROR_INTERNAL;
+ case OV_EFAULT:
+ return -IP_ERROR_INTERNAL;
+ case OV_EBADLINK:
+ return -IP_ERROR_INTERNAL;
+ }
+ return 0;
+}
+
+static int vorbis_read_comments(struct input_plugin_data *ip_data,
+ struct keyval **comments)
+{
+ GROWING_KEYVALS(c);
+ struct vorbis_private *priv;
+ vorbis_comment *vc;
+ int i;
+
+ priv = ip_data->private;
+ vc = ov_comment(&priv->vf, -1);
+ if (vc == NULL) {
+ d_print("vc == NULL\n");
+ *comments = keyvals_new(0);
+ return 0;
+ }
+ for (i = 0; i < vc->comments; i++) {
+ const char *str = vc->user_comments[i];
+ const char *eq = strchr(str, '=');
+ char *key;
+
+ if (!eq) {
+ d_print("invalid comment: '%s' ('=' expected)\n", str);
+ continue;
+ }
+
+ key = xstrndup(str, eq - str);
+ comments_add_const(&c, key, eq + 1);
+ free(key);
+ }
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+ return 0;
+}
+
+static int vorbis_duration(struct input_plugin_data *ip_data)
+{
+ struct vorbis_private *priv;
+ int duration;
+
+ priv = ip_data->private;
+ duration = ov_time_total(&priv->vf, -1);
+ if (duration == OV_EINVAL)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+#ifdef CONFIG_TREMOR
+ duration = (duration + 500) / 1000;
+#endif
+ return duration;
+}
+
+static long vorbis_bitrate(struct input_plugin_data *ip_data)
+{
+ struct vorbis_private *priv = ip_data->private;
+ long bitrate = ov_bitrate(&priv->vf, -1);
+ if (bitrate == OV_EINVAL || bitrate == OV_FALSE)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+ return bitrate;
+}
+
+static long vorbis_current_bitrate(struct input_plugin_data *ip_data)
+{
+ struct vorbis_private *priv = ip_data->private;
+ return ov_bitrate_instant(&priv->vf);
+}
+
+static char *vorbis_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("vorbis");
+}
+
+static const long rate_mapping_44[2][12] = {
+ { 32000, 48000, 60000, 70000, 80000, 86000, 96000, 110000, 120000, 140000, 160000, 239920 },
+ { 45000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 499821 }
+};
+
+static char *vorbis_codec_profile(struct input_plugin_data *ip_data)
+{
+ struct vorbis_private *priv = ip_data->private;
+ vorbis_info *vi = ov_info(&priv->vf, -1);
+ long b = vi->bitrate_nominal;
+ char buf[64];
+
+ if (b <= 0)
+ return NULL;
+
+ if (vi->channels > 2 || vi->rate < 44100) {
+ sprintf(buf, "%ldkbps", b / 1000);
+ } else {
+ const long *map = rate_mapping_44[vi->channels - 1];
+ float q;
+ int i;
+
+ for (i = 0; i < 12-1; i++) {
+ if (b >= map[i] && b < map[i+1])
+ break;
+ }
+ /* This is used even if upper / lower bitrate are set
+ * because it gives a good approximation. */
+ q = (i - 1) + (float) (b - map[i]) / (map[i+1] - map[i]);
+ sprintf(buf, "q%g", roundf(q * 100.f) / 100.f);
+ }
+
+ return xstrdup(buf);
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = vorbis_open,
+ .close = vorbis_close,
+ .read = vorbis_read,
+ .seek = vorbis_seek,
+ .read_comments = vorbis_read_comments,
+ .duration = vorbis_duration,
+ .bitrate = vorbis_bitrate,
+ .bitrate_current = vorbis_current_bitrate,
+ .codec = vorbis_codec,
+ .codec_profile = vorbis_codec_profile
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = { "ogg", NULL };
+const char * const ip_mime_types[] = { "application/ogg", "audio/x-ogg", NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "file.h"
+#include "xmalloc.h"
+#include "debug.h"
+#include "utils.h"
+#include "comment.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <unistd.h>
+
+#define WAVE_FORMAT_PCM 0x0001U
+#define WAVE_FORMAT_EXTENSIBLE 0xfffeU
+
+struct wav_private {
+ unsigned int pcm_start;
+ unsigned int pcm_size;
+ unsigned int pos;
+
+ /* size of one second of data */
+ unsigned int sec_size;
+
+ unsigned int frame_size;
+};
+
+static int read_chunk_header(int fd, char *name, unsigned int *size)
+{
+ int rc;
+ char buf[8];
+
+ rc = read_all(fd, buf, 8);
+ if (rc == -1)
+ return -IP_ERROR_ERRNO;
+ if (rc != 8)
+ return -IP_ERROR_FILE_FORMAT;
+ *size = read_le32(buf + 4);
+ memmove(name, buf, 4);
+ return 0;
+}
+
+static int read_named_chunk_header(int fd, const char *name, unsigned int *size)
+{
+ int rc;
+ char buf[4];
+
+ rc = read_chunk_header(fd, buf, size);
+ if (rc)
+ return rc;
+ if (memcmp(buf, name, 4))
+ return -IP_ERROR_FILE_FORMAT;
+ return 0;
+}
+
+static int find_chunk(int fd, const char *name, unsigned int *size)
+{
+ int rc;
+
+ do {
+ rc = read_named_chunk_header(fd, name, size);
+ if (rc == 0)
+ return 0;
+ if (rc != -IP_ERROR_FILE_FORMAT)
+ return rc;
+ d_print("seeking %d\n", *size);
+ if (lseek(fd, *size, SEEK_CUR) == -1) {
+ d_print("seek failed\n");
+ return -IP_ERROR_ERRNO;
+ }
+ } while (1);
+}
+
+static int wav_open(struct input_plugin_data *ip_data)
+{
+ struct wav_private *priv;
+ char buf[4];
+ char *fmt;
+ int rc;
+ unsigned int riff_size, fmt_size;
+ int save;
+
+ d_print("file: %s\n", ip_data->filename);
+ priv = xnew(struct wav_private, 1);
+ ip_data->private = priv;
+ rc = read_named_chunk_header(ip_data->fd, "RIFF", &riff_size);
+ if (rc)
+ goto error_exit;
+ rc = read_all(ip_data->fd, buf, 4);
+ if (rc == -1) {
+ rc = -IP_ERROR_ERRNO;
+ goto error_exit;
+ }
+ if (rc != 4 || memcmp(buf, "WAVE", 4) != 0) {
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto error_exit;
+ }
+
+ rc = find_chunk(ip_data->fd, "fmt ", &fmt_size);
+ if (rc)
+ goto error_exit;
+ if (fmt_size < 16) {
+ d_print("size of \"fmt \" chunk is invalid (%u)\n", fmt_size);
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto error_exit;
+ }
+ fmt = xnew(char, fmt_size);
+ rc = read_all(ip_data->fd, fmt, fmt_size);
+ if (rc == -1) {
+ save = errno;
+ free(fmt);
+ errno = save;
+ rc = -IP_ERROR_ERRNO;
+ goto error_exit;
+ }
+ if (rc != fmt_size) {
+ save = errno;
+ free(fmt);
+ errno = save;
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto error_exit;
+ }
+ {
+ unsigned int format_tag, channels, rate, bits, channel_mask = 0;
+
+ format_tag = read_le16(fmt + 0);
+ channels = read_le16(fmt + 2);
+ rate = read_le32(fmt + 4);
+ /* 4 bytes, bytes per second */
+ /* 2 bytes, bytes per sample */
+ bits = read_le16(fmt + 14);
+ if (format_tag == WAVE_FORMAT_EXTENSIBLE) {
+ unsigned int ext_size, valid_bits;
+ if (fmt_size < 18) {
+ free(fmt);
+ d_print("size of \"fmt \" chunk is invalid (%u)\n", fmt_size);
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto error_exit;
+ }
+ ext_size = read_le16(fmt + 16);
+ if (ext_size < 22) {
+ free(fmt);
+ d_print("size of \"fmt \" chunk extension is invalid (%u)\n", ext_size);
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto error_exit;
+ }
+ valid_bits = read_le16(fmt + 18);
+ if (valid_bits != bits) {
+ free(fmt);
+ d_print("padded samples are not supported (%u != %u)\n", bits, valid_bits);
+ rc = -IP_ERROR_FILE_FORMAT;
+ goto error_exit;
+ }
+ channel_mask = read_le32(fmt + 20);
+ format_tag = read_le16(fmt + 24);
+ /* ignore rest of extension tag */
+ }
+ free(fmt);
+
+ if (format_tag != WAVE_FORMAT_PCM) {
+ d_print("unsupported format tag %u, should be 1\n", format_tag);
+ rc = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
+ goto error_exit;
+ }
+ if ((bits != 8 && bits != 16 && bits != 24 && bits != 32) || channels < 1) {
+ rc = -IP_ERROR_SAMPLE_FORMAT;
+ goto error_exit;
+ }
+ ip_data->sf = sf_channels(channels) | sf_rate(rate) | sf_bits(bits) |
+ sf_signed(bits > 8);
+ channel_map_init_waveex(channels, channel_mask, ip_data->channel_map);
+ }
+
+ rc = find_chunk(ip_data->fd, "data", &priv->pcm_size);
+ if (rc)
+ goto error_exit;
+ if (lseek(ip_data->fd, 0, SEEK_CUR) == -1) {
+ rc = -IP_ERROR_ERRNO;
+ goto error_exit;
+ }
+ priv->pcm_start = rc;
+
+ priv->sec_size = sf_get_second_size(ip_data->sf);
+ priv->frame_size = sf_get_frame_size(ip_data->sf);
+ priv->pos = 0;
+
+ d_print("pcm start: %u\n", priv->pcm_start);
+ d_print("pcm size: %u\n", priv->pcm_size);
+ d_print("\n");
+ d_print("sr: %d, ch: %d, bits: %d, signed: %d\n", sf_get_rate(ip_data->sf),
+ sf_get_channels(ip_data->sf), sf_get_bits(ip_data->sf),
+ sf_get_signed(ip_data->sf));
+
+ /* clamp pcm_size to full frames (file might be corrupt or truncated) */
+ priv->pcm_size = priv->pcm_size & ~((unsigned int)sf_get_frame_size(ip_data->sf) - 1U);
+ return 0;
+error_exit:
+ save = errno;
+ free(priv);
+ errno = save;
+ return rc;
+}
+
+static int wav_close(struct input_plugin_data *ip_data)
+{
+ struct wav_private *priv;
+
+ priv = ip_data->private;
+ free(priv);
+ ip_data->private = NULL;
+ return 0;
+}
+
+static int wav_read(struct input_plugin_data *ip_data, char *buffer, int _count)
+{
+ struct wav_private *priv = ip_data->private;
+ unsigned int count = _count;
+ int rc;
+
+ if (priv->pos == priv->pcm_size) {
+ /* eof */
+ return 0;
+ }
+ if (count > priv->pcm_size - priv->pos)
+ count = priv->pcm_size - priv->pos;
+ rc = read(ip_data->fd, buffer, count);
+ if (rc == -1) {
+ d_print("read error\n");
+ return -IP_ERROR_ERRNO;
+ }
+ if (rc == 0) {
+ d_print("eof\n");
+ return 0;
+ }
+ priv->pos += rc;
+ return rc;
+}
+
+static int wav_seek(struct input_plugin_data *ip_data, double _offset)
+{
+ struct wav_private *priv = ip_data->private;
+ unsigned int offset;
+
+ offset = (unsigned int)(_offset * (double)priv->sec_size + 0.5);
+ /* align to frame size */
+ offset -= offset % priv->frame_size;
+ priv->pos = offset;
+ if (lseek(ip_data->fd, priv->pcm_start + offset, SEEK_SET) == -1)
+ return -1;
+ return 0;
+}
+
+static struct {
+ const char *old;
+ const char *new;
+} key_map[] = {
+ { "IART", "artist" },
+ { "ICMT", "comment" },
+ { "ICOP", "copyright" },
+ { "ICRD", "date" },
+ { "IGNR", "genre" },
+ { "INAM", "title" },
+ { "IPRD", "album" },
+ { "IPRT", "tracknumber" },
+ { "ISFT", "software" },
+ { NULL, NULL }
+};
+
+static const char *lookup_key(const char *key)
+{
+ int i;
+ for (i = 0; key_map[i].old; i++) {
+ if (!strcasecmp(key, key_map[i].old))
+ return key_map[i].new;
+ }
+ return NULL;
+}
+
+static int wav_read_comments(struct input_plugin_data *ip_data,
+ struct keyval **comments)
+{
+ GROWING_KEYVALS(c);
+ struct wav_private *priv;
+ unsigned int size;
+ char id[4+1];
+ int rc = 0;
+
+ priv = ip_data->private;
+ id[4] = '\0';
+
+ if (lseek(ip_data->fd, 12, SEEK_SET) == -1) {
+ rc = -1;
+ goto out;
+ }
+
+ while (1) {
+ rc = read_chunk_header(ip_data->fd, id, &size);
+ if (rc)
+ break;
+ if (strcmp(id, "data") == 0) {
+ rc = 0;
+ break;
+ } else if (strcmp(id, "LIST") == 0) {
+ char buf[4];
+ rc = read_all(ip_data->fd, buf, 4);
+ if (rc == -1)
+ break;
+ if (memcmp(buf, "INFO", 4) == 0)
+ continue;
+ size -= 4;
+ } else {
+ const char *key = lookup_key(id);
+ if (key) {
+ char *val = xnew(char, size + 1);
+ rc = read_all(ip_data->fd, val, size);
+ if (rc == -1) {
+ free(val);
+ break;
+ }
+ val[rc] = '\0';
+ comments_add(&c, key, val);
+ continue;
+ }
+ }
+
+ if (lseek(ip_data->fd, size, SEEK_CUR) == -1) {
+ rc = -1;
+ break;
+ }
+ }
+
+out:
+ lseek(ip_data->fd, priv->pcm_start, SEEK_SET);
+
+ keyvals_terminate(&c);
+
+ if (rc && c.count == 0) {
+ keyvals_free(c.keyvals);
+ return -1;
+ }
+
+ *comments = c.keyvals;
+ return 0;
+}
+
+static int wav_duration(struct input_plugin_data *ip_data)
+{
+ struct wav_private *priv;
+ int duration;
+
+ priv = ip_data->private;
+ duration = priv->pcm_size / priv->sec_size;
+ return duration;
+}
+
+static long wav_bitrate(struct input_plugin_data *ip_data)
+{
+ sample_format_t sf = ip_data->sf;
+ return sf_get_bits(sf) * sf_get_rate(sf) * sf_get_channels(sf);
+}
+
+static char *wav_codec(struct input_plugin_data *ip_data)
+{
+ char buf[16];
+ snprintf(buf, 16, "pcm_%c%u%s",
+ sf_get_signed(ip_data->sf) ? 's' : 'u',
+ sf_get_bits(ip_data->sf),
+ sf_get_bigendian(ip_data->sf) ? "be" : "le");
+
+ return xstrdup(buf);
+}
+
+static char *wav_codec_profile(struct input_plugin_data *ip_data)
+{
+ return NULL;
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = wav_open,
+ .close = wav_close,
+ .read = wav_read,
+ .seek = wav_seek,
+ .read_comments = wav_read_comments,
+ .duration = wav_duration,
+ .bitrate = wav_bitrate,
+ .bitrate_current = wav_bitrate,
+ .codec = wav_codec,
+ .codec_profile = wav_codec_profile
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = { "wav", NULL };
+const char * const ip_mime_types[] = { NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2007 dnk <dnk@bjum.net>
+ *
+ * Based on oss.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "op.h"
+#include "sf.h"
+#include "utils.h"
+#include "xmalloc.h"
+#include "debug.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <mmsystem.h>
+#include <stdio.h>
+
+static HWAVEOUT wave_out;
+static sample_format_t waveout_sf;
+static int buffer_size = 4096;
+static int buffer_count = 12;
+static WAVEHDR *buffers;
+static int buffer_idx;
+static int buffers_free;
+
+#define FRAME_SIZE_ALIGN(x) \
+ (((x) / sf_get_frame_size(waveout_sf)) * sf_get_frame_size(waveout_sf))
+
+static void waveout_error(const char *name, int rc)
+{
+ const char *errstr = "UNKNOWN";
+
+ switch (rc) {
+ case MMSYSERR_ALLOCATED: errstr = "MMSYSERR_ALLOCATED"; break;
+ case MMSYSERR_INVALHANDLE: errstr = "MMSYSERR_INVALHANDLE"; break;
+ case MMSYSERR_NODRIVER: errstr = "MMSYSERR_NODRIVER"; break;
+ case MMSYSERR_BADDEVICEID: errstr = "MMSYSERR_BADDEVICEID"; break;
+ case MMSYSERR_NOMEM: errstr = "MMSYSERR_NOMEM"; break;
+ case MMSYSERR_NOTSUPPORTED: errstr = "MMSYSERR_NOTSUPPORTED"; break;
+ case WAVERR_STILLPLAYING: errstr = "WAVERR_STILLPLAYING"; break;
+ case WAVERR_UNPREPARED: errstr = "WAVERR_UNPREPARED"; break;
+ case WAVERR_BADFORMAT: errstr = "WAVERR_BADFORMAT"; break;
+ case WAVERR_SYNC: errstr = "WAVERR_SYNC"; break;
+ }
+
+ d_print("%s returned error %s (%d)\n", name, errstr, rc);
+}
+
+static void clean_buffers(void)
+{
+ int i;
+
+ /* mark used buffers clean */
+ for (i = 0; i < buffer_count; i++) {
+ WAVEHDR *hdr = &buffers[(buffer_idx + i) % buffer_count];
+
+ if (!(hdr->dwFlags & WHDR_DONE))
+ break;
+ buffers_free++;
+ waveOutUnprepareHeader(wave_out, hdr, sizeof(WAVEHDR));
+ hdr->dwFlags = 0;
+ }
+}
+
+static int waveout_open(sample_format_t sf, const channel_position_t *channel_map)
+{
+ WAVEFORMATEX format = {
+ .cbSize = sizeof(format),
+ .wFormatTag = WAVE_FORMAT_PCM,
+ .nChannels = sf_get_channels(sf),
+ .nSamplesPerSec = sf_get_rate(sf),
+ .wBitsPerSample = sf_get_bits(sf),
+ .nAvgBytesPerSec = sf_get_second_size(sf),
+ .nBlockAlign = sf_get_frame_size(sf)
+ };
+ int rc, i;
+
+ /* WAVEFORMATEX does not support channels > 2, waveOutWrite() wants little endian signed PCM */
+ if (sf_get_bigendian(sf) || !sf_get_signed(sf) || sf_get_channels(sf) > 2) {
+ return -OP_ERROR_SAMPLE_FORMAT;
+ }
+
+ rc = waveOutOpen(&wave_out, WAVE_MAPPER, &format, 0, 0, CALLBACK_NULL);
+ if (rc != MMSYSERR_NOERROR) {
+ switch (rc) {
+ case MMSYSERR_ALLOCATED:
+ errno = EBUSY;
+ return -OP_ERROR_ERRNO;
+ case MMSYSERR_BADDEVICEID:
+ case MMSYSERR_NODRIVER:
+ errno = ENODEV;
+ return -OP_ERROR_ERRNO;
+ case MMSYSERR_NOMEM:
+ errno = ENOMEM;
+ return -OP_ERROR_ERRNO;
+ case WAVERR_BADFORMAT:
+ return -OP_ERROR_SAMPLE_FORMAT;
+ }
+ return -OP_ERROR_INTERNAL;
+ }
+
+ /* reset buffers */
+ for (i = 0; i < buffer_count; i++) {
+ buffers[i].dwFlags = 0;
+ }
+ buffer_idx = 0;
+ buffers_free = buffer_count;
+
+ waveout_sf = sf;
+
+ return 0;
+}
+
+static int waveout_close(void)
+{
+ int rc;
+
+ waveOutReset(wave_out);
+
+ clean_buffers();
+
+ rc = waveOutClose(wave_out);
+ if (rc != MMSYSERR_NOERROR) {
+ waveout_error("waveOutClose", rc);
+ return -1;
+ }
+ wave_out = NULL;
+
+ return 0;
+}
+
+static int waveout_init(void)
+{
+ WAVEHDR *hdr;
+ int i;
+
+ /* create buffers */
+ buffers = xnew(WAVEHDR, buffer_count);
+ for (i = 0; i < buffer_count; i++) {
+ hdr = &buffers[i];
+
+ memset(hdr, 0, sizeof(WAVEHDR));
+ hdr->lpData = xmalloc(buffer_size);
+ }
+ return 0;
+}
+
+static int waveout_exit(void)
+{
+ int i;
+
+ for (i = 0; i < buffer_count; i++) {
+ free(buffers[i].lpData);
+ }
+ free(buffers);
+ buffers = NULL;
+
+ return 0;
+}
+
+static int waveout_write(const char *buffer, int count)
+{
+ int written = 0;
+ int len, rc;
+
+ count = FRAME_SIZE_ALIGN(count);
+
+ clean_buffers();
+
+ while (count > 0) {
+ WAVEHDR *hdr = &buffers[buffer_idx];
+
+ if (hdr->dwFlags != 0) {
+ /* no free buffers */
+ break;
+ }
+
+ len = FRAME_SIZE_ALIGN(min(count, buffer_size));
+ hdr->dwBufferLength = len;
+ memcpy(hdr->lpData, buffer + written, len);
+
+ rc = waveOutPrepareHeader(wave_out, hdr, sizeof(WAVEHDR));
+ if (rc != MMSYSERR_NOERROR) {
+ waveout_error("waveOutPrepareHeader", rc);
+ break;
+ }
+
+ rc = waveOutWrite(wave_out, hdr, sizeof(WAVEHDR));
+ if (rc != MMSYSERR_NOERROR) {
+ waveOutUnprepareHeader(wave_out, hdr, sizeof(WAVEHDR));
+ hdr->dwFlags = 0;
+ waveout_error("waveOutWrite", rc);
+ break;
+ }
+
+ written += len;
+ count -= len;
+ buffer_idx = (buffer_idx + 1) % buffer_count;
+ buffers_free--;
+ }
+
+ return written;
+}
+
+static int waveout_pause(void)
+{
+ if (waveOutPause(wave_out) != MMSYSERR_NOERROR)
+ return -1;
+ return 0;
+}
+
+static int waveout_unpause(void)
+{
+ if (waveOutRestart(wave_out) != MMSYSERR_NOERROR)
+ return -1;
+ return 0;
+}
+
+static int waveout_buffer_space(void)
+{
+ clean_buffers();
+
+ return buffers_free * FRAME_SIZE_ALIGN(buffer_size);
+}
+
+static int waveout_set_option(int key, const char *val)
+{
+ long int ival;
+ int reinit = 0;
+
+ switch (key) {
+ case 0:
+ if (str_to_int(val, &ival) || ival < 4096 || ival > 65536) {
+ errno = EINVAL;
+ return -OP_ERROR_ERRNO;
+ }
+ if (buffers) {
+ waveout_exit();
+ reinit = 1;
+ }
+ buffer_size = ival;
+ break;
+ case 1:
+ if (str_to_int(val, &ival) || ival < 2 || ival > 64) {
+ errno = EINVAL;
+ return -OP_ERROR_ERRNO;
+ }
+ if (buffers) {
+ waveout_exit();
+ reinit = 1;
+ }
+ buffer_count = ival;
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+
+ if (reinit) {
+ waveout_init();
+ }
+
+ return 0;
+}
+
+static int waveout_get_option(int key, char **val)
+{
+ switch (key) {
+ case 0:
+ *val = xnew(char, 22);
+ snprintf(*val, 22, "%d", buffer_size);
+ break;
+ case 1:
+ *val = xnew(char, 22);
+ snprintf(*val, 22, "%d", buffer_count);
+ break;
+ default:
+ return -OP_ERROR_NOT_OPTION;
+ }
+ return 0;
+}
+
+const struct output_plugin_ops op_pcm_ops = {
+ .init = waveout_init,
+ .exit = waveout_exit,
+ .open = waveout_open,
+ .close = waveout_close,
+ .write = waveout_write,
+ .pause = waveout_pause,
+ .unpause = waveout_unpause,
+ .buffer_space = waveout_buffer_space,
+ .set_option = waveout_set_option,
+ .get_option = waveout_get_option
+};
+
+const char * const op_pcm_options[] = {
+ "buffer_size",
+ "buffer_count",
+ NULL
+};
+
+const int op_priority = 0;
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2007 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ip.h"
+#include "ape.h"
+#include "id3.h"
+#include "xmalloc.h"
+#include "read_wrapper.h"
+#include "debug.h"
+#include "buffer.h"
+#include "comment.h"
+
+#include <wavpack/wavpack.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define WV_CHANNEL_MAX 2
+
+struct wavpack_file {
+ int fd;
+ off_t len;
+ int push_back_byte;
+};
+
+struct wavpack_private {
+ WavpackContext *wpc;
+ int32_t samples[CHUNK_SIZE * WV_CHANNEL_MAX];
+ struct wavpack_file wv_file;
+ struct wavpack_file wvc_file;
+ unsigned int has_wvc : 1;
+};
+
+/* http://www.wavpack.com/lib_use.txt */
+
+static int32_t read_bytes(void *data, void *ptr, int32_t count)
+{
+ struct wavpack_file *file = data;
+ int32_t rc, n = 0;
+
+ if (file->push_back_byte != EOF) {
+ char *p = ptr;
+ *p = (char) file->push_back_byte;
+ ptr = p + 1;
+ file->push_back_byte = EOF;
+ count--;
+ n++;
+ }
+
+ rc = read(file->fd, ptr, count);
+ if (rc == -1) {
+ d_print("error: %s\n", strerror(errno));
+ return 0;
+ }
+ if (rc == 0) {
+ errno = 0;
+ return 0;
+ }
+ return rc + n;
+}
+
+static uint32_t get_pos(void *data)
+{
+ struct wavpack_file *file = data;
+
+ return lseek(file->fd, 0, SEEK_CUR);
+}
+
+static int set_pos_rel(void *data, int32_t delta, int mode)
+{
+ struct wavpack_file *file = data;
+
+ if (lseek(file->fd, delta, mode) == -1)
+ return -1;
+
+ file->push_back_byte = EOF;
+ return 0;
+}
+
+static int set_pos_abs(void *data, uint32_t pos)
+{
+ return set_pos_rel(data, pos, SEEK_SET);
+}
+
+static int push_back_byte(void *data, int c)
+{
+ struct wavpack_file *file = data;
+
+ if (file->push_back_byte != EOF) {
+ d_print("error: only one byte push back possible!\n");
+ return EOF;
+ }
+ file->push_back_byte = c;
+ return c;
+}
+
+static uint32_t get_length(void *data)
+{
+ struct wavpack_file *file = data;
+ return file->len;
+}
+
+static int can_seek(void *data)
+{
+ struct wavpack_file *file = data;
+ return file->len != -1;
+}
+
+static int32_t write_bytes(void *data, void *ptr, int32_t count)
+{
+ /* we shall not write any bytes */
+ return 0;
+}
+
+
+/*
+ * typedef struct {
+ * int32_t (*read_bytes)(void *id, void *data, int32_t bcount);
+ * uint32_t (*get_pos)(void *id);
+ * int (*set_pos_abs)(void *id, uint32_t pos);
+ * int (*set_pos_rel)(void *id, int32_t delta, int mode);
+ * int (*push_back_byte)(void *id, int c);
+ * uint32_t (*get_length)(void *id);
+ * int (*can_seek)(void *id);
+ *
+ * // this callback is for writing edited tags only
+ * int32_t (*write_bytes)(void *id, void *data, int32_t bcount);
+ * } WavpackStreamReader;
+ */
+static WavpackStreamReader callbacks = {
+ .read_bytes = read_bytes,
+ .get_pos = get_pos,
+ .set_pos_abs = set_pos_abs,
+ .set_pos_rel = set_pos_rel,
+ .push_back_byte = push_back_byte,
+ .get_length = get_length,
+ .can_seek = can_seek,
+ .write_bytes = write_bytes
+};
+
+static int wavpack_open(struct input_plugin_data *ip_data)
+{
+ struct wavpack_private *priv;
+ struct stat st;
+ char msg[80];
+ int channel_mask = 0;
+
+ const struct wavpack_private priv_init = {
+ .wv_file = {
+ .fd = ip_data->fd,
+ .push_back_byte = EOF
+ }
+ };
+
+ priv = xnew(struct wavpack_private, 1);
+ *priv = priv_init;
+ if (!ip_data->remote && fstat(ip_data->fd, &st) == 0) {
+ char *filename_wvc;
+
+ priv->wv_file.len = st.st_size;
+
+ filename_wvc = xnew(char, strlen(ip_data->filename) + 2);
+ sprintf(filename_wvc, "%sc", ip_data->filename);
+ if (stat(filename_wvc, &st) == 0) {
+ priv->wvc_file.fd = open(filename_wvc, O_RDONLY);
+ if (priv->wvc_file.fd != -1) {
+ priv->wvc_file.len = st.st_size;
+ priv->wvc_file.push_back_byte = EOF;
+ priv->has_wvc = 1;
+ d_print("use correction file: %s\n", filename_wvc);
+ }
+ }
+ free(filename_wvc);
+ } else
+ priv->wv_file.len = -1;
+ ip_data->private = priv;
+
+ *msg = '\0';
+
+ priv->wpc = WavpackOpenFileInputEx(&callbacks, &priv->wv_file,
+ priv->has_wvc ? &priv->wvc_file : NULL, msg,
+ OPEN_NORMALIZE, 0);
+
+ if (!priv->wpc) {
+ d_print("WavpackOpenFileInputEx failed: %s\n", msg);
+ free(priv);
+ return -IP_ERROR_FILE_FORMAT;
+ }
+
+ ip_data->sf = sf_rate(WavpackGetSampleRate(priv->wpc))
+ | sf_channels(WavpackGetReducedChannels(priv->wpc))
+ | sf_bits(WavpackGetBitsPerSample(priv->wpc))
+ | sf_signed(1);
+#if CUR_STREAM_VERS > 0x404
+ channel_mask = WavpackGetChannelMask(priv->wpc);
+#endif
+ channel_map_init_waveex(sf_get_channels(ip_data->sf), channel_mask, ip_data->channel_map);
+ return 0;
+}
+
+static int wavpack_close(struct input_plugin_data *ip_data)
+{
+ struct wavpack_private *priv;
+
+ priv = ip_data->private;
+ priv->wpc = WavpackCloseFile(priv->wpc);
+ if (priv->has_wvc)
+ close(priv->wvc_file.fd);
+ free(priv);
+ ip_data->private = NULL;
+ return 0;
+}
+
+/* from wv_engine.cpp (C) 2006 by Peter Lemenkov <lemenkov@newmail.ru> */
+static char *format_samples(int bps, char *dst, int32_t *src, uint32_t count)
+{
+ int32_t temp;
+
+ switch (bps) {
+ case 1:
+ while (count--)
+ *dst++ = *src++ + 128;
+ break;
+ case 2:
+ while (count--) {
+ *dst++ = (char) (temp = *src++);
+ *dst++ = (char) (temp >> 8);
+ }
+ break;
+ case 3:
+ while (count--) {
+ *dst++ = (char) (temp = *src++);
+ *dst++ = (char) (temp >> 8);
+ *dst++ = (char) (temp >> 16);
+ }
+ break;
+ case 4:
+ while (count--) {
+ *dst++ = (char) (temp = *src++);
+ *dst++ = (char) (temp >> 8);
+ *dst++ = (char) (temp >> 16);
+ *dst++ = (char) (temp >> 24);
+ }
+ break;
+ }
+
+ return dst;
+}
+
+static int wavpack_read(struct input_plugin_data *ip_data, char *buffer, int count)
+{
+ struct wavpack_private *priv;
+ int rc, bps, sample_count, channels;
+
+ priv = ip_data->private;
+ channels = sf_get_channels(ip_data->sf);
+ bps = WavpackGetBytesPerSample(priv->wpc);
+
+ sample_count = count / bps;
+
+ rc = WavpackUnpackSamples(priv->wpc, priv->samples, sample_count / channels);
+ format_samples(bps, buffer, priv->samples, rc * channels);
+ return rc * channels * bps;
+}
+
+static int wavpack_seek(struct input_plugin_data *ip_data, double offset)
+{
+ struct wavpack_private *priv = ip_data->private;
+
+ if (!WavpackSeekSample(priv->wpc, WavpackGetSampleRate(priv->wpc) * offset))
+ return -IP_ERROR_INTERNAL;
+ return 0;
+}
+
+static int wavpack_read_comments(struct input_plugin_data *ip_data,
+ struct keyval **comments)
+{
+ struct id3tag id3;
+ APETAG(ape);
+ GROWING_KEYVALS(c);
+ int fd, rc, save, i;
+
+ fd = open(ip_data->filename, O_RDONLY);
+ if (fd == -1)
+ return -1;
+ d_print("filename: %s\n", ip_data->filename);
+
+ id3_init(&id3);
+ rc = id3_read_tags(&id3, fd, ID3_V1);
+ save = errno;
+ close(fd);
+ errno = save;
+ if (rc) {
+ if (rc == -1) {
+ d_print("error: %s\n", strerror(errno));
+ return -1;
+ }
+ d_print("corrupted tag?\n");
+ goto next;
+ }
+
+ for (i = 0; i < NUM_ID3_KEYS; i++) {
+ char *val = id3_get_comment(&id3, i);
+ if (val)
+ comments_add(&c, id3_key_names[i], val);
+ }
+
+next:
+ id3_free(&id3);
+
+ rc = ape_read_tags(&ape, ip_data->fd, 1);
+ if (rc < 0)
+ goto out;
+
+ for (i = 0; i < rc; i++) {
+ char *k, *v;
+ k = ape_get_comment(&ape, &v);
+ if (!k)
+ break;
+ comments_add(&c, k, v);
+ free(k);
+ }
+
+out:
+ ape_free(&ape);
+
+ keyvals_terminate(&c);
+ *comments = c.keyvals;
+ return 0;
+}
+
+static int wavpack_duration(struct input_plugin_data *ip_data)
+{
+ struct wavpack_private *priv;
+ int duration;
+
+ priv = ip_data->private;
+ duration = WavpackGetNumSamples(priv->wpc) /
+ WavpackGetSampleRate(priv->wpc);
+
+ return duration;
+}
+
+static long wavpack_bitrate(struct input_plugin_data *ip_data)
+{
+ struct wavpack_private *priv = ip_data->private;
+ double bitrate = WavpackGetAverageBitrate(priv->wpc, 1);
+ if (!bitrate)
+ return -IP_ERROR_FUNCTION_NOT_SUPPORTED;
+ return (long) (bitrate + 0.5);
+}
+
+static long wavpack_current_bitrate(struct input_plugin_data *ip_data)
+{
+ struct wavpack_private *priv = ip_data->private;
+ return WavpackGetInstantBitrate(priv->wpc);
+}
+
+static char *wavpack_codec(struct input_plugin_data *ip_data)
+{
+ return xstrdup("wavpack");
+}
+
+static char *wavpack_codec_profile(struct input_plugin_data *ip_data)
+{
+ struct wavpack_private *priv = ip_data->private;
+ int m = WavpackGetMode(priv->wpc);
+ char buf[32];
+
+ buf[0] = '\0';
+
+ if (m & MODE_FAST)
+ strcat(buf, "fast");
+#ifdef MODE_VERY_HIGH
+ else if (m & MODE_VERY_HIGH)
+ strcat(buf, "very high");
+#endif
+ else if (m & MODE_HIGH)
+ strcat(buf, "high");
+ else
+ strcat(buf, "normal");
+
+ if (m & MODE_HYBRID)
+ strcat(buf, " hybrid");
+
+#ifdef MODE_XMODE
+ if ((m & MODE_EXTRA) && (m & MODE_XMODE)) {
+ char xmode[] = " x0";
+ xmode[2] = ((m & MODE_XMODE) >> 12) + '0';
+ strcat(buf, xmode);
+ }
+#endif
+
+ return xstrdup(buf);
+}
+
+const struct input_plugin_ops ip_ops = {
+ .open = wavpack_open,
+ .close = wavpack_close,
+ .read = wavpack_read,
+ .seek = wavpack_seek,
+ .read_comments = wavpack_read_comments,
+ .duration = wavpack_duration,
+ .bitrate = wavpack_bitrate,
+ .bitrate_current = wavpack_current_bitrate,
+ .codec = wavpack_codec,
+ .codec_profile = wavpack_codec_profile
+};
+
+const int ip_priority = 50;
+const char * const ip_extensions[] = { "wv", NULL };
+const char * const ip_mime_types[] = { "audio/x-wavpack", NULL };
+const char * const ip_options[] = { NULL };
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "window.h"
+#include "options.h"
+#include "xmalloc.h"
+#include "debug.h"
+
+#include <stdlib.h>
+
+static void sel_changed(struct window *win)
+{
+ if (win->sel_changed)
+ win->sel_changed();
+ win->changed = 1;
+}
+
+struct window *window_new(int (*get_prev)(struct iter *), int (*get_next)(struct iter *))
+{
+ struct window *win;
+
+ win = xnew(struct window, 1);
+ win->get_next = get_next;
+ win->get_prev = get_prev;
+ win->sel_changed = NULL;
+ win->nr_rows = 1;
+ win->changed = 1;
+ iter_init(&win->head);
+ iter_init(&win->top);
+ iter_init(&win->sel);
+ return win;
+}
+
+void window_free(struct window *win)
+{
+ free(win);
+}
+
+void window_set_empty(struct window *win)
+{
+ iter_init(&win->head);
+ iter_init(&win->top);
+ iter_init(&win->sel);
+ sel_changed(win);
+}
+
+void window_set_contents(struct window *win, void *head)
+{
+ struct iter first;
+
+ win->head.data0 = head;
+ win->head.data1 = NULL;
+ win->head.data2 = NULL;
+ first = win->head;
+ win->get_next(&first);
+ win->top = first;
+ win->sel = first;
+ sel_changed(win);
+}
+
+void window_set_nr_rows(struct window *win, int nr_rows)
+{
+ if (nr_rows < 1)
+ return;
+ win->nr_rows = nr_rows;
+ window_changed(win);
+ win->changed = 1;
+}
+
+void window_up(struct window *win, int rows)
+{
+ struct iter iter;
+ int delta, sel_up, top_up, actual_offset = -1;
+
+ /* distance between top and sel */
+ delta = 0;
+ iter = win->top;
+ while (!iters_equal(&iter, &win->sel)) {
+ win->get_next(&iter);
+ delta++;
+ }
+
+ for (sel_up = 0; sel_up < rows; sel_up++) {
+ iter = win->sel;
+ if (!win->get_prev(&iter)) {
+ actual_offset = 0;
+ break;
+ }
+ win->sel = iter;
+ }
+
+ if (actual_offset == -1) {
+ int upper_bound = win->nr_rows / 2;
+ if (scroll_offset < upper_bound)
+ upper_bound = scroll_offset;
+ for (actual_offset = 0; actual_offset < upper_bound; actual_offset++) {
+ if (!win->get_prev(&iter)) {
+ break;
+ }
+ }
+ }
+
+ top_up = actual_offset + sel_up - delta;
+ while (top_up > 0) {
+ win->get_prev(&win->top);
+ top_up--;
+ }
+
+ if (sel_up)
+ sel_changed(win);
+}
+
+void window_down(struct window *win, int rows)
+{
+ struct iter iter;
+ int delta, sel_down, top_down, actual_offset = -1;
+
+ /* distance between top and sel */
+ delta = 0;
+ iter = win->top;
+ while (!iters_equal(&iter, &win->sel)) {
+ win->get_next(&iter);
+ delta++;
+ }
+
+ for (sel_down = 0; sel_down < rows; sel_down++) {
+ iter = win->sel;
+ if (!win->get_next(&iter)) {
+ actual_offset = 0;
+ break;
+ }
+ win->sel = iter;
+ }
+
+ if (actual_offset == -1) {
+ int upper_bound = (win->nr_rows - 1) / 2;
+ if (scroll_offset < upper_bound)
+ upper_bound = scroll_offset;
+ for (actual_offset = 0; actual_offset < upper_bound; actual_offset++) {
+ if (!win->get_next(&iter)) {
+ break;
+ }
+ }
+ }
+
+ top_down = actual_offset + sel_down - (win->nr_rows - delta - 1);
+ while (top_down > 0) {
+ win->get_next(&win->top);
+ top_down--;
+ }
+
+ if (sel_down)
+ sel_changed(win);
+}
+
+/*
+ * minimize number of empty lines visible
+ * make sure selection is visible
+ */
+void window_changed(struct window *win)
+{
+ struct iter iter;
+ int delta, rows;
+
+ if (iter_is_null(&win->head)) {
+ BUG_ON(!iter_is_null(&win->top));
+ BUG_ON(!iter_is_null(&win->sel));
+ return;
+ }
+ BUG_ON(iter_is_null(&win->top));
+ BUG_ON(iter_is_null(&win->sel));
+
+ /* make sure top and sel point to real row if possible */
+ if (iter_is_head(&win->top)) {
+ win->get_next(&win->top);
+ win->sel = win->top;
+ sel_changed(win);
+ return;
+ }
+
+ /* make sure the selected row is visible */
+
+ /* get distance between top and sel */
+ delta = 0;
+ iter = win->top;
+ while (!iters_equal(&iter, &win->sel)) {
+ if (!win->get_next(&iter)) {
+ /* sel < top, scroll up until top == sel */
+ while (!iters_equal(&win->top, &win->sel))
+ win->get_prev(&win->top);
+ goto minimize;
+ }
+ delta++;
+ }
+
+ /* scroll down until sel is visible */
+ while (delta > win->nr_rows - 1) {
+ win->get_next(&win->top);
+ delta--;
+ }
+minimize:
+ /* minimize number of empty lines shown */
+ iter = win->top;
+ rows = 1;
+ while (rows < win->nr_rows) {
+ if (!win->get_next(&iter))
+ break;
+ rows++;
+ }
+ while (rows < win->nr_rows) {
+ iter = win->top;
+ if (!win->get_prev(&iter))
+ break;
+ win->top = iter;
+ rows++;
+ }
+ win->changed = 1;
+}
+
+void window_row_vanishes(struct window *win, struct iter *iter)
+{
+ struct iter new = *iter;
+
+ BUG_ON(iter->data0 != win->head.data0);
+ if (!win->get_next(&new)) {
+ new = *iter;
+ win->get_prev(&new);
+ }
+ if (iters_equal(&win->top, iter))
+ win->top = new;
+ if (iters_equal(&win->sel, iter)) {
+ win->sel = new;
+ sel_changed(win);
+ }
+ win->changed = 1;
+}
+
+int window_get_top(struct window *win, struct iter *iter)
+{
+ *iter = win->top;
+ return !iter_is_empty(iter);
+}
+
+int window_get_sel(struct window *win, struct iter *iter)
+{
+ *iter = win->sel;
+ return !iter_is_empty(iter);
+}
+
+int window_get_prev(struct window *win, struct iter *iter)
+{
+ return win->get_prev(iter);
+}
+
+int window_get_next(struct window *win, struct iter *iter)
+{
+ return win->get_next(iter);
+}
+
+void window_set_sel(struct window *win, struct iter *iter)
+{
+ int sel_nr, top_nr, bottom_nr;
+ int upper_bound;
+ struct iter tmp;
+
+ BUG_ON(iter_is_empty(&win->top));
+ BUG_ON(iter_is_empty(iter));
+ BUG_ON(iter->data0 != win->head.data0);
+
+ if (iters_equal(&win->sel, iter))
+ return;
+ win->sel = *iter;
+
+ tmp = win->head;
+ win->get_next(&tmp);
+ top_nr = 0;
+ while (!iters_equal(&tmp, &win->top)) {
+ win->get_next(&tmp);
+ top_nr++;
+ }
+
+ tmp = win->head;
+ win->get_next(&tmp);
+ sel_nr = 0;
+ while (!iters_equal(&tmp, &win->sel)) {
+ BUG_ON(!win->get_next(&tmp));
+ sel_nr++;
+ }
+
+ upper_bound = win->nr_rows / 2;
+ if (scroll_offset < upper_bound)
+ upper_bound = scroll_offset;
+
+ if (sel_nr < top_nr + upper_bound) { // scroll up
+ tmp = win->head;
+ win->get_next(&tmp);
+ if (sel_nr < upper_bound) { // no space above
+ win->top = tmp;
+ } else {
+ win->top = win->sel;
+ while (upper_bound > 0) {
+ win->get_prev(&win->top);
+ upper_bound--;
+ }
+ }
+ } else { //scroll down
+ upper_bound = (win->nr_rows - 1) / 2;
+ if (scroll_offset < upper_bound)
+ upper_bound = scroll_offset;
+
+ tmp = win->sel;
+ bottom_nr = sel_nr;
+ if (sel_nr >= top_nr + win->nr_rows) { // seleced element not visible
+ while (sel_nr >= top_nr + win->nr_rows) {
+ win->get_next(&win->top);
+ top_nr++;
+ }
+ } else { // selected element visible
+ while (bottom_nr + 1 < top_nr + win->nr_rows) {
+ if (!win->get_next(&tmp)) { //no space below
+ bottom_nr = sel_nr + upper_bound;
+ break;
+ }
+ bottom_nr++;
+ }
+ }
+
+ while (bottom_nr < sel_nr + upper_bound) {
+ if (!win->get_next(&tmp))
+ break;
+ bottom_nr++;
+ win->get_next(&win->top);
+ }
+ }
+ sel_changed(win);
+}
+
+void window_goto_top(struct window *win)
+{
+ struct iter old_sel;
+
+ old_sel = win->sel;
+ win->sel = win->head;
+ win->get_next(&win->sel);
+ win->top = win->sel;
+ if (!iters_equal(&old_sel, &win->sel))
+ sel_changed(win);
+}
+
+void window_goto_bottom(struct window *win)
+{
+ struct iter old_sel;
+ int count;
+
+ old_sel = win->sel;
+ win->sel = win->head;
+ win->get_prev(&win->sel);
+ win->top = win->sel;
+ count = win->nr_rows - 1;
+ while (count) {
+ struct iter iter = win->top;
+
+ if (!win->get_prev(&iter))
+ break;
+ win->top = iter;
+ count--;
+ }
+ if (!iters_equal(&old_sel, &win->sel))
+ sel_changed(win);
+}
+
+void window_page_up(struct window *win)
+{
+ window_up(win, win->nr_rows - 1);
+}
+
+void window_page_down(struct window *win)
+{
+ window_down(win, win->nr_rows - 1);
+}
+
+static void window_goto_pos(struct window *win, int pos)
+{
+ struct iter old_sel;
+ int i;
+
+ old_sel = win->sel;
+ win->sel = win->top;
+ for (i = 0; i < pos; i++)
+ win->get_next(&win->sel);
+ if (!iters_equal(&old_sel, &win->sel))
+ sel_changed(win);
+}
+
+void window_page_top(struct window *win)
+{
+ window_goto_pos(win, 0);
+}
+
+void window_page_bottom(struct window *win)
+{
+ window_goto_pos(win, win->nr_rows - 1);
+}
+
+void window_page_middle(struct window *win)
+{
+ window_goto_pos(win, win->nr_rows / 2);
+}
+
+int window_get_nr_rows(struct window *win)
+{
+ return win->nr_rows;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _WINDOW_H
+#define _WINDOW_H
+
+#include "iter.h"
+
+/*
+ * window contains list of rows
+ * - the list is double linked circular list
+ * - head contains no data. head is not a real row
+ * - head->prev gets the last row (or head if the list is empty) in the list
+ * - head->next gets the first row (or head if the list is empty) in the list
+ *
+ * get_prev(&iter) always stores prev row to the iter
+ * get_next(&iter) always stores next row to the iter
+ *
+ * these return 1 if the new row is real row (not head), 0 otherwise
+ *
+ * sel_changed callback is called if not NULL and selection has changed
+ */
+
+struct window {
+ /* head of the row list */
+ struct iter head;
+
+ /* top row */
+ struct iter top;
+
+ /* selected row */
+ struct iter sel;
+
+ /* window height */
+ int nr_rows;
+
+ unsigned changed : 1;
+
+ /* return 1 if got next/prev, otherwise 0 */
+ int (*get_prev)(struct iter *iter);
+ int (*get_next)(struct iter *iter);
+ void (*sel_changed)(void);
+};
+
+struct window *window_new(int (*get_prev)(struct iter *), int (*get_next)(struct iter *));
+void window_free(struct window *win);
+void window_set_empty(struct window *win);
+void window_set_contents(struct window *win, void *head);
+
+/* call this after rows were added to window or order of rows was changed.
+ * top and sel MUST point to valid rows (or window must be empty, but then
+ * why do you want to call this function :)).
+ *
+ * if you remove row from window then call window_row_vanishes BEFORE removing
+ * the row instead of this function.
+ */
+void window_changed(struct window *win);
+
+/* call this BEFORE row is removed from window */
+void window_row_vanishes(struct window *win, struct iter *iter);
+
+int window_get_top(struct window *win, struct iter *iter);
+int window_get_sel(struct window *win, struct iter *iter);
+int window_get_prev(struct window *win, struct iter *iter);
+int window_get_next(struct window *win, struct iter *iter);
+
+/* set selected row */
+void window_set_sel(struct window *win, struct iter *iter);
+
+void window_set_nr_rows(struct window *win, int nr_rows);
+void window_up(struct window *win, int rows);
+void window_down(struct window *win, int rows);
+void window_goto_top(struct window *win);
+void window_goto_bottom(struct window *win);
+void window_page_up(struct window *win);
+void window_page_down(struct window *win);
+void window_page_top(struct window *win);
+void window_page_bottom(struct window *win);
+void window_page_middle(struct window *win);
+
+int window_get_nr_rows(struct window *win);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "worker.h"
+#include "locking.h"
+#include "list.h"
+#include "xmalloc.h"
+#include "debug.h"
+
+#include <stdlib.h>
+#include <pthread.h>
+
+struct worker_job {
+ struct list_head node;
+ /* >0, 0 is 'any' */
+ int type;
+ void (*job_cb)(void *data);
+ void (*free_cb)(void *data);
+ void *data;
+};
+
+static LIST_HEAD(worker_job_head);
+static pthread_mutex_t worker_mutex = CMUS_MUTEX_INITIALIZER;
+static pthread_cond_t worker_cond = PTHREAD_COND_INITIALIZER;
+static pthread_t worker_thread;
+static int running = 1;
+static int cancel_type = JOB_TYPE_NONE;
+
+/*
+ * - only worker thread modifies this
+ * - cur_job->job_cb can read this without locking
+ * - anyone else must lock worker before reading this
+ */
+static struct worker_job *cur_job = NULL;
+
+#define worker_lock() cmus_mutex_lock(&worker_mutex)
+#define worker_unlock() cmus_mutex_unlock(&worker_mutex)
+
+static void *worker_loop(void *arg)
+{
+ worker_lock();
+ while (1) {
+ if (list_empty(&worker_job_head)) {
+ int rc;
+
+ if (!running)
+ break;
+
+ rc = pthread_cond_wait(&worker_cond, &worker_mutex);
+ if (rc)
+ d_print("pthread_cond_wait: %s\n", strerror(rc));
+ } else {
+ struct list_head *item = worker_job_head.next;
+ uint64_t t;
+
+ list_del(item);
+ cur_job = container_of(item, struct worker_job, node);
+ worker_unlock();
+
+ t = timer_get();
+ cur_job->job_cb(cur_job->data);
+ timer_print("worker job", timer_get() - t);
+
+ worker_lock();
+ cur_job->free_cb(cur_job->data);
+ free(cur_job);
+ cur_job = NULL;
+
+ // wakeup worker_remove_jobs() if needed
+ if (cancel_type != JOB_TYPE_NONE)
+ pthread_cond_signal(&worker_cond);
+ }
+ }
+ worker_unlock();
+ return NULL;
+}
+
+void worker_init(void)
+{
+ int rc = pthread_create(&worker_thread, NULL, worker_loop, NULL);
+
+ BUG_ON(rc);
+}
+
+void worker_exit(void)
+{
+ worker_lock();
+ running = 0;
+ pthread_cond_signal(&worker_cond);
+ worker_unlock();
+
+ pthread_join(worker_thread, NULL);
+}
+
+void worker_add_job(int type, void (*job_cb)(void *data),
+ void (*free_cb)(void *data), void *data)
+{
+ struct worker_job *job;
+
+ job = xnew(struct worker_job, 1);
+ job->type = type;
+ job->job_cb = job_cb;
+ job->free_cb = free_cb;
+ job->data = data;
+
+ worker_lock();
+ list_add_tail(&job->node, &worker_job_head);
+ pthread_cond_signal(&worker_cond);
+ worker_unlock();
+}
+
+void worker_remove_jobs(int type)
+{
+ struct list_head *item;
+
+ worker_lock();
+ cancel_type = type;
+
+ /* remove jobs of the specified type from the queue */
+ item = worker_job_head.next;
+ while (item != &worker_job_head) {
+ struct worker_job *job = container_of(item, struct worker_job, node);
+ struct list_head *next = item->next;
+
+ if (type == JOB_TYPE_ANY || type == job->type) {
+ list_del(&job->node);
+ job->free_cb(job->data);
+ free(job);
+ }
+ item = next;
+ }
+
+ /* wait current job to finish or cancel if it's of the specified type */
+ if (cur_job && (type == JOB_TYPE_ANY || type == cur_job->type))
+ pthread_cond_wait(&worker_cond, &worker_mutex);
+
+ cancel_type = JOB_TYPE_NONE;
+ worker_unlock();
+}
+
+int worker_has_job(int type)
+{
+ struct worker_job *job;
+ int has_job = 0;
+
+ worker_lock();
+ list_for_each_entry(job, &worker_job_head, node) {
+ if (type == JOB_TYPE_ANY || type == job->type) {
+ has_job = 1;
+ break;
+ }
+ }
+ if (cur_job && (type == JOB_TYPE_ANY || type == cur_job->type))
+ has_job = 1;
+ worker_unlock();
+ return has_job;
+}
+
+/*
+ * this is only called from the worker thread
+ * cur_job is guaranteed to be non-NULL
+ */
+int worker_cancelling(void)
+{
+ return cur_job->type == cancel_type || cancel_type == JOB_TYPE_ANY;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _WORKER_H
+#define _WORKER_H
+
+#define JOB_TYPE_NONE 0
+#define JOB_TYPE_ANY -1
+
+void worker_init(void);
+void worker_exit(void);
+
+/*
+ * @type: JOB_TYPE_* (>0)
+ * @job_cb: does the job
+ * @free_cb: frees @data
+ * @data: data for the callbacks
+ */
+void worker_add_job(int type, void (*job_cb)(void *data),
+ void (*free_cb)(void *data), void *data);
+
+/*
+ * @type: job type. >0, use JOB_TYPE_ANY to remove all
+ */
+void worker_remove_jobs(int type);
+
+int worker_has_job(int type);
+
+/*
+ * @type: type of this job
+ *
+ * returns: 0 or 1
+ *
+ * long jobs should call this to see whether it should cancel
+ * call from job function _only_
+ */
+int worker_cancelling(void);
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "xmalloc.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+extern char *program_name;
+
+void malloc_fail(void)
+{
+ fprintf(stderr, "%s: could not allocate memory: %s\n", program_name, strerror(errno));
+ exit(42);
+}
+
+#ifndef HAVE_STRNDUP
+char *xstrndup(const char *str, size_t n)
+{
+ size_t len;
+ char *s;
+
+ for (len = 0; len < n && str[len]; len++)
+ ;
+ s = xmalloc(len + 1);
+ memcpy(s, str, len);
+ s[len] = 0;
+ return s;
+}
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004-2005 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _XMALLOC_H
+#define _XMALLOC_H
+
+#include "compiler.h"
+#ifdef HAVE_CONFIG
+#include "config/xmalloc.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+void malloc_fail(void) __NORETURN;
+
+#define xnew(type, n) (type *)xmalloc(sizeof(type) * (n))
+#define xnew0(type, n) (type *)xmalloc0(sizeof(type) * (n))
+#define xrenew(type, mem, n) (type *)xrealloc(mem, sizeof(type) * (n))
+
+static inline void * __MALLOC xmalloc(size_t size)
+{
+ void *ptr = malloc(size);
+
+ if (unlikely(ptr == NULL))
+ malloc_fail();
+ return ptr;
+}
+
+static inline void * __MALLOC xmalloc0(size_t size)
+{
+ void *ptr = calloc(1, size);
+
+ if (unlikely(ptr == NULL))
+ malloc_fail();
+ return ptr;
+}
+
+static inline void * __MALLOC xrealloc(void *ptr, size_t size)
+{
+ ptr = realloc(ptr, size);
+ if (unlikely(ptr == NULL))
+ malloc_fail();
+ return ptr;
+}
+
+static inline char * __MALLOC xstrdup(const char *str)
+{
+#ifdef HAVE_STRDUP
+ char *s = strdup(str);
+ if (unlikely(s == NULL))
+ malloc_fail();
+ return s;
+#else
+ size_t size = strlen(str) + 1;
+ void *ptr = xmalloc(size);
+ return (char *) memcpy(ptr, str, size);
+#endif
+}
+
+#ifdef HAVE_STRNDUP
+static inline char * __MALLOC xstrndup(const char *str, size_t n)
+{
+ char *s = strndup(str, n);
+ if (unlikely(s == NULL))
+ malloc_fail();
+ return s;
+}
+#else
+char * __MALLOC xstrndup(const char *str, size_t n);
+#endif
+
+static inline void free_str_array(char **array)
+{
+ int i;
+
+ if (array == NULL)
+ return;
+ for (i = 0; array[i]; i++)
+ free(array[i]);
+ free(array);
+}
+
+#endif
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "xstrjoin.h"
+#include "xmalloc.h"
+#include "string.h"
+
+char *xstrjoin(const char *a, const char *b)
+{
+ int a_len, b_len;
+ char *joined;
+
+ a_len = strlen(a);
+ b_len = strlen(b);
+ joined = xnew(char, a_len + b_len + 1);
+ memcpy(joined, a, a_len);
+ memcpy(joined + a_len, b, b_len + 1);
+ return joined;
+}
--- /dev/null
+/*
+ * Copyright 2008-2011 Various Authors
+ * Copyright 2004 Timo Hirvonen
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _XSTRJOIN_H
+#define _XSTRJOIN_H
+
+char *xstrjoin(const char *a, const char *b);
+
+#endif