aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile6
-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
10 files changed, 693 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index 866e5a4..1440a9b 100644
--- a/Makefile
+++ b/Makefile
@@ -4,8 +4,10 @@ LDFLAGS = -lmpdclient -framework Cocoa
OUTPUT_OPTION=-MMD -MP -o $@
BINDIR = /usr/local/bin
-OBJ=mpc-bar.o ini.o
-DEP=$(OBJ:.o=.d)
+OBJ = mpc-bar.o ini.o
+MPC_SRC = $(wildcard mpc/*.c)
+OBJ += $(MPC_SRC:.c=.o)
+DEP = $(OBJ:.o=.d)
$(TARGET): $(OBJ)
$(CC) $^ $(LDFLAGS) -o $@
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