aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--LICENSE359
-rw-r--r--Makefile14
-rw-r--r--README.md13
-rw-r--r--mpc-bar.m94
-rw-r--r--mpc/Compiler.h174
-rw-r--r--mpc/audio_format.c25
-rw-r--r--mpc/audio_format.h12
-rw-r--r--mpc/charset.h61
-rw-r--r--mpc/config.h3
-rw-r--r--mpc/format.c246
-rw-r--r--mpc/format.h34
-rw-r--r--mpc/song_format.c109
-rw-r--r--mpc/song_format.h25
13 files changed, 1118 insertions, 51 deletions
diff --git a/LICENSE b/LICENSE
index b306fdd..9efa6fb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -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.
diff --git a/Makefile b/Makefile
index 2380dad..1440a9b 100644
--- a/Makefile
+++ b/Makefile
@@ -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)
diff --git a/README.md b/README.md
index 16c3cb1..d643c90 100644
--- a/README.md
+++ b/README.md
@@ -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)
```
diff --git a/mpc-bar.m b/mpc-bar.m
index c4a25b9..c9decd4 100644
--- a/mpc-bar.m
+++ b/mpc-bar.m
@@ -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, &ltemp, 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