diff options
-rw-r--r-- | Makefile | 6 | ||||
-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 |
10 files changed, 693 insertions, 2 deletions
@@ -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, <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 |