diff options
-rw-r--r-- | LICENSE | 359 | ||||
-rw-r--r-- | Makefile | 14 | ||||
-rw-r--r-- | README.md | 13 | ||||
-rw-r--r-- | mpc-bar.m | 94 | ||||
-rw-r--r-- | mpc/Compiler.h | 174 | ||||
-rw-r--r-- | mpc/audio_format.c | 25 | ||||
-rw-r--r-- | mpc/audio_format.h | 12 | ||||
-rw-r--r-- | mpc/charset.h | 61 | ||||
-rw-r--r-- | mpc/config.h | 3 | ||||
-rw-r--r-- | mpc/format.c | 246 | ||||
-rw-r--r-- | mpc/format.h | 34 | ||||
-rw-r--r-- | mpc/song_format.c | 109 | ||||
-rw-r--r-- | mpc/song_format.h | 25 |
13 files changed, 1118 insertions, 51 deletions
@@ -1,21 +1,338 @@ -MIT License - -Copyright (c) 2023 Spencer Williams - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + <https://fsf.org/> + 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, see <https://www.gnu.org/licenses/>. + +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 Moe Ghoul>, 1 April 1989 + Moe Ghoul, 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. @@ -1,16 +1,22 @@ TARGET = mpc-bar CFLAGS = -O2 -fobjc-arc -Wall LDFLAGS = -lmpdclient -framework Cocoa +OUTPUT_OPTION=-MMD -MP -o $@ BINDIR = /usr/local/bin -$(TARGET): mpc-bar.o ini.o - $(CC) $^ $(LDFLAGS) -o $@ +OBJ = mpc-bar.o ini.o +MPC_SRC = $(wildcard mpc/*.c) +OBJ += $(MPC_SRC:.c=.o) +DEP = $(OBJ:.o=.d) -mpc-bar.o ini.o: ini.h +$(TARGET): $(OBJ) + $(CC) $^ $(LDFLAGS) -o $@ install: $(TARGET) install -d $(BINDIR) install -m755 $< $(BINDIR)/$< +-include $(DEP) + clean: - rm -f $(TARGET) mpc-bar.o ini.o + rm -f $(TARGET) $(OBJ) $(DEP) @@ -13,11 +13,20 @@ brew services start spnw/formulae/mpc-bar ``` ## Configuration -MPC Bar is configured with a `~/.mpcbar.ini` file. Currently the only -options are these: +MPC Bar is configured with a `~/.mpc-bar.ini` file. Below are the +default options. Note that `format` works just like `mpc -f`; see the +[mpc(1)](https://man.archlinux.org/man/mpc.1#f,) man page for more +information. ``` [connection] host = localhost port = 6600 +# password = qwerty123 + +[display] +format = [%name%: &[[%artist%|%performer%|%composer%|%albumartist%] - ]%title%]|%name%|[[%artist%|%performer%|%composer%|%albumartist%] - ]%title%|%file% +idle_message = No song playing +show_queue = true # Show queue/position info while playing? (true/false) +show_queue_idle = (value of show_queue) # Show queue/position info while idle? (true/false) ``` @@ -1,3 +1,21 @@ +// Copyright (C) 2023-2025 Spencer Williams + +// SPDX-License-Identifier: GPL-2.0-or-later + +// 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 +// <https://www.gnu.org/licenses/>. + #import <Cocoa/Cocoa.h> #include <MacTypes.h> #include <assert.h> @@ -9,8 +27,9 @@ #include <string.h> #include "ini.h" +#include "mpc/song_format.h" -#define VERSION "0.1.2" +#define VERSION "0.4" #define TITLE_MAX_LENGTH 96 #define SLEEP_INTERVAL 0.2 @@ -29,7 +48,8 @@ static NSString *formatTime(unsigned int t) { } struct config { - char *host; + const char *host, *password, *format, *idle_message; + int show_queue, show_queue_idle; unsigned port; }; @@ -41,6 +61,16 @@ static int handler(void *userdata, const char *section, const char *name, c->host = strdup(value); } else if (MATCH("connection", "port")) { c->port = atoi(value); + } else if (MATCH("connection", "password")) { + c->password = strdup(value); + } else if (MATCH("display", "format")) { + c->format = strdup(value); + } else if (MATCH("display", "idle_message")) { + c->idle_message = strdup(value); + } else if (MATCH("display", "show_queue")) { + c->show_queue = (strcmp(value, "false") != 0); + } else if (MATCH("display", "show_queue_idle")) { + c->show_queue_idle = (strcmp(value, "false") != 0); } else { return 0; } @@ -56,6 +86,7 @@ static int handler(void *userdata, const char *section, const char *name, struct mpd_connection *connection; BOOL songMenuNeedsUpdate; + NSString *errorMessage; NSMenu *controlMenu; NSMenuItem *timeItem, *timeSeparator, *playPauseItem, *stopItem, *nextItem, *previousItem, *singleItem, *clearItem, *updateDatabaseItem, @@ -68,15 +99,23 @@ static int handler(void *userdata, const char *section, const char *name, NSMapTable *songMap; } - (void)initConfig { - config.host = NULL; - config.port = 0; + config.host = "localhost"; + config.port = 6600; + config.format = "[%name%: &[[%artist%|%performer%|%composer%|%albumartist%] - ]%title%]|%name%|[[%artist%|%performer%|%composer%|%albumartist%] - ]%title%|%file%"; + config.idle_message = "No song playing"; + config.show_queue = 1; + config.show_queue_idle = -1; +} +- (BOOL)tryReadConfigFile:(NSString *)file { + return (0 == ini_parse([[NSHomeDirectory() stringByAppendingPathComponent:file] UTF8String], handler, &config)); } - (void)readConfigFile { - const char *path = [[NSHomeDirectory() - stringByAppendingPathComponent:@".mpcbar.ini"] UTF8String]; - if (ini_parse(path, handler, &config) < 0) { + if (!([self tryReadConfigFile: @".mpc-bar.ini"] || [self tryReadConfigFile: @".mpcbar"])) { NSLog(@"Failed to read config file"); } + if (config.show_queue_idle == -1) { + config.show_queue_idle = config.show_queue; + } } - (void)connect { assert(connection == NULL); @@ -87,6 +126,16 @@ static int handler(void *userdata, const char *section, const char *name, exit(1); } + errorMessage = @"Failed to get status (is MPD running?)"; + + if (mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS) { + if (config.password != NULL) { + if (!mpd_run_password(connection, config.password)) { + errorMessage = @"Auth failed (please fix password and restart service)"; + } + } + } + songMenuNeedsUpdate = YES; } - (void)disconnect { @@ -110,7 +159,7 @@ static int handler(void *userdata, const char *section, const char *name, [NSThread sleepForTimeInterval:SLEEP_INTERVAL]; if (!connection) { [self disableAllItems]; - [self showError:@"Failed to get status (is MPD running?)"]; + [self showError:errorMessage]; [self connect]; } if (!mpd_send_idle(connection)) { @@ -184,28 +233,25 @@ static int handler(void *userdata, const char *section, const char *name, else [menuButton setImage:nil]; - const char *artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); - const char *title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); - - if (artist) - [output appendString:utf8String(artist)]; - if (artist && title) - [output appendString:@" - "]; - if (title) - [output appendString:utf8String(title)]; - if (!(artist || title)) - [output appendString:utf8String(mpd_song_get_uri(song))]; + char *s = format_song(song, config.format); + [output appendString:utf8String(s)]; + free(s); } else { - [output setString:@"No song playing"]; + // FIXME: There's no point calling utf8String more than once, as + // idle_message never changes. + [output setString:utf8String(config.idle_message)]; [menuButton setImage:nil]; } int song_pos = mpd_status_get_song_pos(status); unsigned int queue_length = mpd_status_get_queue_length(status); - if (song_pos < 0) - [output appendFormat:@" (%u)", queue_length]; - else - [output appendFormat:@" (%u/%u)", song_pos + 1, queue_length]; + + if ((active && config.show_queue) || (!active && config.show_queue_idle)) { + if (song_pos < 0) + [output appendFormat:@" (%u)", queue_length]; + else + [output appendFormat:@" (%u/%u)", song_pos + 1, queue_length]; + } if ([output length] > TITLE_MAX_LENGTH) { int leftCount = (TITLE_MAX_LENGTH - 3) / 2; diff --git a/mpc/Compiler.h b/mpc/Compiler.h new file mode 100644 index 0000000..c51ad7b --- /dev/null +++ b/mpc/Compiler.h @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#ifndef COMPILER_H +#define COMPILER_H + +#define GCC_MAKE_VERSION(major, minor, patchlevel) ((major) * 10000 + (minor) * 100 + patchlevel) + +#ifdef __GNUC__ +#define GCC_VERSION GCC_MAKE_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) +#else +#define GCC_VERSION 0 +#endif + +#ifdef __clang__ +# define CLANG_VERSION GCC_MAKE_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) +#else +# define CLANG_VERSION 0 +#endif + +/** + * Are we building with the specified version of gcc (not clang or any + * other compiler) or newer? + */ +#define GCC_CHECK_VERSION(major, minor) \ + (!CLANG_VERSION && \ + GCC_VERSION >= GCC_MAKE_VERSION(major, minor, 0)) + +/** + * Are we building with clang (any version) or at least the specified + * gcc version? + */ +#define CLANG_OR_GCC_VERSION(major, minor) \ + (CLANG_VERSION || GCC_CHECK_VERSION(major, minor)) + +/** + * Are we building with gcc (not clang or any other compiler) and a + * version older than the specified one? + */ +#define GCC_OLDER_THAN(major, minor) \ + (GCC_VERSION && !CLANG_VERSION && \ + GCC_VERSION < GCC_MAKE_VERSION(major, minor, 0)) + +/** + * Are we building with the specified version of clang or newer? + */ +#define CLANG_CHECK_VERSION(major, minor) \ + (CLANG_VERSION >= GCC_MAKE_VERSION(major, minor, 0)) + +#if CLANG_OR_GCC_VERSION(4,0) + +/* GCC 4.x */ + +#define gcc_const __attribute__((const)) +#define gcc_deprecated __attribute__((deprecated)) +#define gcc_may_alias __attribute__((may_alias)) +#define gcc_malloc __attribute__((malloc)) +#define gcc_noreturn __attribute__((noreturn)) +#define gcc_packed __attribute__((packed)) +#define gcc_printf(a,b) __attribute__((format(printf, a, b))) +#define gcc_pure __attribute__((pure)) +#define gcc_sentinel __attribute__((sentinel)) +#define gcc_unused __attribute__((unused)) +#define gcc_warn_unused_result __attribute__((warn_unused_result)) + +#define gcc_nonnull(...) __attribute__((nonnull(__VA_ARGS__))) +#define gcc_nonnull_all __attribute__((nonnull)) + +#define gcc_likely(x) __builtin_expect (!!(x), 1) +#define gcc_unlikely(x) __builtin_expect (!!(x), 0) + +#define gcc_aligned(n) __attribute__((aligned(n))) + +#define gcc_visibility_hidden __attribute__((visibility("hidden"))) +#define gcc_visibility_default __attribute__((visibility("default"))) + +#define gcc_always_inline __attribute__((always_inline)) + +#else + +/* generic C compiler */ + +#define gcc_const +#define gcc_deprecated +#define gcc_may_alias +#define gcc_malloc +#define gcc_noreturn +#define gcc_packed +#define gcc_printf(a,b) +#define gcc_pure +#define gcc_sentinel +#define gcc_unused +#define gcc_warn_unused_result + +#define gcc_nonnull(...) +#define gcc_nonnull_all + +#define gcc_likely(x) (x) +#define gcc_unlikely(x) (x) + +#define gcc_aligned(n) + +#define gcc_visibility_hidden +#define gcc_visibility_default + +#define gcc_always_inline inline + +#endif + +#if CLANG_OR_GCC_VERSION(4,3) + +#define gcc_hot __attribute__((hot)) +#define gcc_cold __attribute__((cold)) + +#else /* ! GCC_UNUSED >= 40300 */ + +#define gcc_hot +#define gcc_cold + +#endif /* ! GCC_UNUSED >= 40300 */ + +#if GCC_CHECK_VERSION(4,6) +#define gcc_flatten __attribute__((flatten)) +#else +#define gcc_flatten +#endif + +#ifndef __cplusplus +/* plain C99 has "restrict" */ +#define gcc_restrict restrict +#elif CLANG_OR_GCC_VERSION(4,0) +/* "__restrict__" is a GCC extension for C++ */ +#define gcc_restrict __restrict__ +#else +/* disable it on other compilers */ +#define gcc_restrict +#endif + +/* C++11 features */ + +#if defined(__cplusplus) + +/* support for C++11 "override" was added in gcc 4.7 */ +#if GCC_OLDER_THAN(4,7) +#define override +#define final +#endif + +#if CLANG_OR_GCC_VERSION(4,8) +#define gcc_alignas(T, fallback) alignas(T) +#else +#define gcc_alignas(T, fallback) gcc_aligned(fallback) +#endif + +#endif + +#ifndef __has_feature + // define dummy macro for non-clang compilers + #define __has_feature(x) 0 +#endif + +#if __has_feature(attribute_unused_on_fields) +#define gcc_unused_field gcc_unused +#else +#define gcc_unused_field +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define gcc_unreachable() __builtin_unreachable() +#else +#define gcc_unreachable() +#endif + +#endif diff --git a/mpc/audio_format.c b/mpc/audio_format.c new file mode 100644 index 0000000..044a1d4 --- /dev/null +++ b/mpc/audio_format.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#include "audio_format.h" + +#include <mpd/client.h> + +#include <assert.h> +#include <stdio.h> + +void +format_audio_format(char *buffer, size_t buffer_size, + const struct mpd_audio_format *audio_format) +{ + assert(buffer != NULL); + assert(buffer_size > 0); + assert(audio_format != NULL); + + if (audio_format->bits == MPD_SAMPLE_FORMAT_FLOAT) + snprintf(buffer, buffer_size, "%u:f:%u", audio_format->sample_rate, audio_format->channels); + else if (audio_format->bits == MPD_SAMPLE_FORMAT_DSD) + snprintf(buffer, buffer_size, "%u:dsd:%u", audio_format->sample_rate, audio_format->channels); + else + snprintf(buffer, buffer_size, "%u:%u:%u", audio_format->sample_rate, audio_format->bits, audio_format->channels); +} diff --git a/mpc/audio_format.h b/mpc/audio_format.h new file mode 100644 index 0000000..963f653 --- /dev/null +++ b/mpc/audio_format.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#pragma once + +#include <stddef.h> + +struct mpd_audio_format; + +void +format_audio_format(char *buffer, size_t buffer_size, + const struct mpd_audio_format *audio_format); diff --git a/mpc/charset.h b/mpc/charset.h new file mode 100644 index 0000000..99ad405 --- /dev/null +++ b/mpc/charset.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#ifndef CHAR_CONV_H +#define CHAR_CONV_H + +#include "config.h" +#include "Compiler.h" + +#include <stdbool.h> + +#ifdef HAVE_ICONV + +/** + * Initializes the character set conversion library. + * + * @param enable_input allow conversion from locale to UTF-8 + * @param enable_output allow conversion from UTF-8 to locale + */ +void +charset_init(bool enable_input, bool enable_output); + +void charset_deinit(void); + +gcc_pure +const char * +charset_to_utf8(const char *from); + +gcc_pure +const char * +charset_from_utf8(const char *from); + +#else + +static inline void +charset_init(bool disable_input, bool disable_output) +{ + (void)disable_input; + (void)disable_output; +} + +static inline void +charset_deinit(void) +{ +} + +static inline const char * +charset_to_utf8(const char *from) +{ + return from; +} + +static inline const char * +charset_from_utf8(const char *from) +{ + return from; +} + +#endif + +#endif diff --git a/mpc/config.h b/mpc/config.h new file mode 100644 index 0000000..f6dd921 --- /dev/null +++ b/mpc/config.h @@ -0,0 +1,3 @@ +#pragma once +#undef HAVE_ICONV +#define HAVE_STRNDUP diff --git a/mpc/format.c b/mpc/format.c new file mode 100644 index 0000000..eeb5563 --- /dev/null +++ b/mpc/format.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#include "format.h" + +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +/** + * Reallocate the given string and append the source string. + */ +gcc_malloc +static char * +string_append(char *dest, const char *src, size_t len) +{ + size_t destlen = dest != NULL + ? strlen(dest) + : 0; + + dest = realloc(dest, destlen + len + 1); + memcpy(dest + destlen, src, len); + dest[destlen + len] = '\0'; + + return dest; +} + +/** + * Skip the format string until the current group is closed by either + * '&', '|' or ']' (supports nesting). + */ +gcc_pure +static const char * +skip_format(const char *p) +{ + unsigned stack = 0; + + while (*p != '\0') { + if (*p == '[') + stack++; + else if (*p == '#' && p[1] != '\0') + /* skip escaped stuff */ + ++p; + else if (stack > 0) { + if (*p == ']') + --stack; + } else if (*p == '&' || *p == '|' || *p == ']') + break; + + ++p; + } + + return p; +} + +static bool +is_name_char(char ch) +{ + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || ch == '_'; +} + +static char * +format_object2(const char *format, const char **last, const void *object, + const char *(*getter)(const void *object, const char *name)) +{ + char *ret = NULL; + const char *p; + bool found = false; + + for (p = format; *p != '\0';) { + switch (p[0]) { + case '|': + ++p; + if (!found) { + /* nothing found yet: try the next + section */ + free(ret); + ret = NULL; + } else + /* already found a value: skip the + next section */ + p = skip_format(p); + break; + + case '&': + ++p; + if (!found) + /* nothing found yet, so skip this + section */ + p = skip_format(p); + else + /* we found something yet, but it will + only be used if the next section + also found something, so reset the + flag */ + found = false; + break; + + case '[': { + char *t = format_object2(p + 1, &p, object, getter); + if (t != NULL) { + ret = string_append(ret, t, strlen(t)); + free(t); + found = true; + } + } + break; + + case ']': + if (last != NULL) + *last = p + 1; + if (!found) { + free(ret); + ret = NULL; + } + return ret; + + case '\\': { + /* take care of escape sequences */ + char ltemp; + switch (p[1]) { + case 'a': + ltemp = '\a'; + break; + + case 'b': + ltemp = '\b'; + break; + + case 'e': + ltemp = '\033'; + break; + + case 't': + ltemp = '\t'; + break; + + case 'n': + ltemp = '\n'; + break; + + case 'v': + ltemp = '\v'; + break; + + case 'f': + ltemp = '\f'; + break; + + case 'r': + ltemp = '\r'; + break; + + case '[': + case ']': + ltemp = p[1]; + break; + + default: + /* unknown escape: copy the + backslash */ + ltemp = p[0]; + --p; + break; + } + + ret = string_append(ret, <emp, 1); + p += 2; + } + break; + + case '%': { + /* find the extent of this format specifier + (stop at \0, ' ', or esc) */ + const char *end = p + 1; + while (is_name_char(*end)) + ++end; + + const size_t length = end - p + 1; + + if (*end != '%') { + ret = string_append(ret, p, length - 1); + p = end; + continue; + } + + char name[32]; + if (length > (int)sizeof(name)) { + ret = string_append(ret, p, length); + p = end + 1; + continue; + } + + memcpy(name, p + 1, length - 2); + name[length - 2] = 0; + + const char *value = getter(object, name); + size_t value_length; + if (value != NULL) { + if (*value != 0) + found = true; + value_length = strlen(value); + } else { + /* unknown variable: copy verbatim + from format string */ + value = p; + value_length = length; + } + + ret = string_append(ret, value, value_length); + + /* advance past the specifier */ + p = end + 1; + } + break; + + case '#': + /* let the escape character escape itself */ + if (p[1] != '\0') { + ret = string_append(ret, p + 1, 1); + p += 2; + break; + } + + /* fall through */ + + default: + /* pass-through non-escaped portions of the format string */ + ret = string_append(ret, p, 1); + ++p; + } + } + + if (last != NULL) + *last = p; + return ret; +} + +char * +format_object(const char *format, const void *object, + const char *(*getter)(const void *object, const char *name)) +{ + return format_object2(format, NULL, object, getter); +} diff --git a/mpc/format.h b/mpc/format.h new file mode 100644 index 0000000..e7aefeb --- /dev/null +++ b/mpc/format.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#ifndef MPC_FORMAT_H +#define MPC_FORMAT_H + +#include "Compiler.h" + +struct mpd_song; + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Pretty-print an object into a string using the given format + * specification. + * + * @param format the format string + * @param object the object + * @param getter a getter function that extracts a value from the object + * @return the resulting string to be freed by free(); NULL if + * no format string group produced any output + */ +gcc_malloc +char * +format_object(const char *format, const void *object, + const char *(*getter)(const void *object, const char *name)); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/mpc/song_format.c b/mpc/song_format.c new file mode 100644 index 0000000..d628459 --- /dev/null +++ b/mpc/song_format.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#include "song_format.h" +#include "audio_format.h" +#include "format.h" +#include "charset.h" + +#include <mpd/client.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +static const char * +format_mtime(char *buffer, size_t buffer_size, + const struct mpd_song *song, const char *format) +{ + time_t t = mpd_song_get_last_modified(song); + if (t == 0) + return NULL; + + struct tm tm; +#ifdef _WIN32 + tm = *localtime(&t); +#else + localtime_r(&t, &tm); +#endif + + strftime(buffer, buffer_size, format, &tm); + return buffer; +} + +/** + * Extract an attribute from a song object. + * + * @param song the song object + * @param name the attribute name + * @return the attribute value; NULL if the attribute name is invalid; + * an empty string if the attribute name is valid, but not present in + * the song + */ +gcc_pure +static const char * +song_value(const struct mpd_song *song, const char *name) +{ + static char buffer[40]; + const char *value; + + if (strcmp(name, "file") == 0) + value = mpd_song_get_uri(song); + else if (strcmp(name, "time") == 0) { + unsigned duration = mpd_song_get_duration(song); + + if (duration > 0) { + snprintf(buffer, sizeof(buffer), "%u:%02u", + duration / 60, duration % 60); + value = buffer; + } else + value = NULL; + } else if (strcmp(name, "position") == 0) { + unsigned pos = mpd_song_get_pos(song); + snprintf(buffer, sizeof(buffer), "%u", pos+1); + value = buffer; + } else if (strcmp(name, "id") == 0) { + snprintf(buffer, sizeof(buffer), "%u", mpd_song_get_id(song)); + value = buffer; + } else if (strcmp(name, "prio") == 0) { + snprintf(buffer, sizeof(buffer), "%u", + mpd_song_get_prio(song)); + value = buffer; + } else if (strcmp(name, "mtime") == 0) { + value = format_mtime(buffer, sizeof(buffer), song, "%c"); + } else if (strcmp(name, "mdate") == 0) { + value = format_mtime(buffer, sizeof(buffer), song, "%x"); + } else if (strcmp(name, "audioformat") == 0) { + const struct mpd_audio_format *audio_format = mpd_song_get_audio_format(song); + if (audio_format == NULL) + return NULL; + + format_audio_format(buffer, sizeof(buffer), audio_format); + value = buffer; + } else { + enum mpd_tag_type tag_type = mpd_tag_name_iparse(name); + if (tag_type == MPD_TAG_UNKNOWN) + return NULL; + + value = mpd_song_get_tag(song, tag_type, 0); + } + + if (value != NULL) + value = charset_from_utf8(value); + else + value = ""; + + return value; +} + +static const char * +song_getter(const void *object, const char *name) +{ + return song_value((const struct mpd_song *)object, name); +} + +char * +format_song(const struct mpd_song *song, const char *format) +{ + return format_object(format, song, song_getter); +} diff --git a/mpc/song_format.h b/mpc/song_format.h new file mode 100644 index 0000000..7825982 --- /dev/null +++ b/mpc/song_format.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright The Music Player Daemon Project + +#ifndef MPC_SONG_FORMAT_H +#define MPC_SONG_FORMAT_H + +#include "Compiler.h" + +struct mpd_song; + +/** + * Pretty-print song metadata into a string using the given format + * specification. + * + * @param song the song object + * @param format the format string + * @return the resulting string to be freed by free(); NULL if + * no format string group produced any output + */ +gcc_malloc +char * +format_song(const struct mpd_song *song, + const char *format); + +#endif |