diff options
Diffstat (limited to 'mpc')
| -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 | 
9 files changed, 689 insertions, 0 deletions
| 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 | 
