diff options
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | config.def.h | 17 | ||||
-rw-r--r-- | mpd-notification.c | 157 | ||||
-rw-r--r-- | mpd-notification.h | 6 |
5 files changed, 126 insertions, 75 deletions
@@ -9,10 +9,7 @@ RM := rm # flags CFLAGS_EXTRA += -std=c11 -O2 -fPIC -Wall -Werror -ifneq ($(wildcard /usr/include/iniparser),) -CFLAGS_EXTRA += -I/usr/include/iniparser -endif -CFLAGS_EXTRA += -liniparser +CFLAGS_EXTRA += $(shell pkg-config --cflags --libs iniparser) CFLAGS_SYSTEMD := $(shell pkg-config --cflags --libs libsystemd 2>/dev/null) ifneq ($(CFLAGS_SYSTEMD),) CFLAGS_EXTRA += -DHAVE_SYSTEMD $(CFLAGS_SYSTEMD) @@ -28,7 +25,7 @@ LDFLAGS += -Wl,-z,now -Wl,-z,relro -pie # this is just a fallback in case you do not use git but downloaded # a release tarball... -VERSION := 0.8.7 +VERSION := 0.9.1 all: mpd-notification README.html @@ -65,7 +65,6 @@ or `systemctl --user enable mpd-notification`. * *-m MUSIC-DIR*: use *MUSIC-DIR* for artwork lookup * *--notification-file-workaround*: write artwork to file for notification daemons that do required it -* *-o*: Notification text is one line (no line breaks) * *-p PORT*: connect to *PORT* * *-s PIXELS*: scale image to a maximum size *PIXELS* x *PIXELS* pixels, keeping ratio @@ -84,12 +83,23 @@ look like this: host = localhost port = 6600 music-dir = /srv/media/music/ - oneline = true scale = 200 + text-topic = MPD Notification + text-play = Playing <b>%t</b>\nby <i>%a</i>\nfrom <i>%A</i> + text-pause = Paused <b>%t</b>\nby <i>%a</i>\nfrom <i>%A</i> + text-stop = Stopped playback timeout = 20 Unused options can be commented or removed completely. +The options `text-play` and `text-pause` support custom formatting with +these specifiers: + +* *%t*: title +* *%a*: artist +* *%A*: album +* *%d*: duration + Artwork ------- diff --git a/config.def.h b/config.def.h index bc64e88..b8ef018 100644 --- a/config.def.h +++ b/config.def.h @@ -26,15 +26,14 @@ #define ICON_AUDIO_X_GENERIC "audio-x-generic" /* strings used to display notification messages - * TEXT_PLAY_* need to include one string modifier '%s' each. */ -#define TEXT_TOPIC "MPD Notification" -#define TEXT_PLAY_PAUSE_STATE "%s " -#define TEXT_PLAY_PAUSE_TITLE "<b>%s</b>" -#define TEXT_PLAY_PAUSE_ARTIST "by <i>%s</i>" -#define TEXT_PLAY_PAUSE_ALBUM "from <i>%s</i>" -#define TEXT_STOP "Stopped playback" -#define TEXT_NONE "No action received yet." -#define TEXT_UNKNOWN "(unknown)" + * TEXT_PLAY & TEXT_PAUSE can include several specifiers: + * %t for title, %a for artist, %A for album and %d for duration */ +#define TEXT_TOPIC "MPD Notification" +#define TEXT_PLAY "Playing <b>%t</b>\nby <i>%a</i>\nfrom <i>%A</i>" +#define TEXT_PAUSE "Paused <b>%t</b>\nby <i>%a</i>\nfrom <i>%A</i>" +#define TEXT_STOP "Stopped playback" +#define TEXT_NONE "No action received yet" +#define TEXT_UNKNOWN "(unknown)" /* this is a regular expression that has to match image filename used * for artwork */ diff --git a/mpd-notification.c b/mpd-notification.c index 2e48bb0..af8985a 100644 --- a/mpd-notification.c +++ b/mpd-notification.c @@ -18,13 +18,12 @@ #include "mpd-notification.h" -const static char optstring[] = "hH:m:op:s:t:vV"; +const static char optstring[] = "hH:m:p:s:t:vV"; const static struct option options_long[] = { /* name has_arg flag val */ { "help", no_argument, NULL, 'h' }, { "host", required_argument, NULL, 'H' }, { "music-dir", required_argument, NULL, 'm' }, - { "oneline", no_argument, NULL, 'o' }, { "port", required_argument, NULL, 'p' }, { "scale", required_argument, NULL, 's' }, { "timeout", required_argument, NULL, 't' }, @@ -41,7 +40,6 @@ NotifyNotification * notification = NULL; struct mpd_connection * conn = NULL; uint8_t doexit = 0; uint8_t verbose = 0; -uint8_t oneline = 0; #ifdef HAVE_LIBAV magic_t magic = NULL; #endif @@ -105,7 +103,10 @@ GdkPixbuf * retrieve_artwork(const char * music_dir, const char * uri) { if (verbose > 0) printf("%s: MIME type for %s is: %s\n", program, uri_path, magic_mime); - if (strcmp(magic_mime, "audio/mpeg") != 0) + /* Are there more mime-types supporting embedded artwork? Tell me! */ + if (strcmp(magic_mime, "audio/mp4") != 0 && + strcmp(magic_mime, "audio/mpeg") != 0 && + strcmp(magic_mime, "audio/x-m4a") != 0) goto image; if ((pFormatCtx = avformat_alloc_context()) == NULL) { @@ -118,11 +119,6 @@ GdkPixbuf * retrieve_artwork(const char * music_dir, const char * uri) { goto image; } - if (pFormatCtx->iformat->read_header(pFormatCtx) < 0) { - fprintf(stderr, "%s: Could not read the format header.\n", program); - goto image; - } - /* find the first attached picture, if available */ for (i = 0; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) { @@ -149,6 +145,9 @@ GdkPixbuf * retrieve_artwork(const char * music_dir, const char * uri) { } } + if (pixbuf == NULL && verbose > 0) + printf("%s: No artwork in media file.\n", program); + image: #endif @@ -208,26 +207,72 @@ done: return pixbuf; } -/*** append_string ***/ -char * append_string(char * string, const char * format, const char delim, const char * s) { - char * tmp, * offset; - - tmp = g_markup_escape_text(s, -1); - - string = realloc(string, strlen(string) + strlen(format) + strlen(tmp) + 2 /* delim + line break */); - - offset = string + strlen(string); - - if (delim > 0) { - *offset = delim; - offset++; - } - - sprintf(offset, format, tmp); +/*** format_text ***/ +char * format_text(const char* format, const char* title, const char* artist, const char* album, unsigned int duration) { + char * formatted, * tmp = NULL; + size_t len; + + if (format == NULL || strlen(format) == 0) + return NULL; + + formatted = strdup(""); + len = 0; + + do { + if (*format == '%') { + format++; + + switch (*format) { + case 'a': + tmp = g_markup_escape_text(artist, -1); + break; + + case 'A': + tmp = g_markup_escape_text(album, -1); + break; + + case 'd': + size_t size; + size = snprintf(tmp, 0, "%d:%02d", duration / 60, duration % 60) + 1; + tmp = malloc(size); + snprintf(tmp, size, "%d:%02d", duration / 60, duration % 60); + break; + + case 't': + tmp = g_markup_escape_text(title, -1); + break; + + default: + formatted = realloc(formatted, len + 2); + sprintf(formatted + len, "%%"); + format--; + break; + } - free(tmp); + if (tmp != NULL) { + formatted = realloc(formatted, len + strlen(tmp) + 1); + sprintf(formatted + len, "%s", tmp); + free(tmp); + tmp = NULL; + } + } else if (*format == '\\') { + format++; + formatted = realloc(formatted, len + 2); + + if (*format == 'n') { + sprintf(formatted + len, "\n"); + } else { + sprintf(formatted + len, "\\"); + format--; + } + } else { + formatted = realloc(formatted, len + 2); + sprintf(formatted + len, "%c", *format); + } + len = strlen(formatted); + } while (*format++); - return string; + return formatted; } /*** main ***/ @@ -238,10 +283,11 @@ int main(int argc, char ** argv) { GdkPixbuf * pixbuf = NULL; GError * error = NULL; enum mpd_state state = MPD_STATE_UNKNOWN, last_state = MPD_STATE_UNKNOWN; - const char * mpd_host, * mpd_port_str, * music_dir, * uri = NULL; + const char * mpd_host, * mpd_port_str, * music_dir, * text_topic = TEXT_TOPIC, + * text_play = TEXT_PLAY, * text_pause = TEXT_PAUSE, * text_stop = TEXT_STOP, * uri = NULL; unsigned mpd_port = MPD_PORT, mpd_timeout = MPD_TIMEOUT, notification_timeout = NOTIFICATION_TIMEOUT; struct mpd_song * song = NULL; - unsigned int i, version = 0, help = 0, scale = 0, file_workaround = 0; + unsigned int i, version = 0, help = 0, scale = 0, file_workaround = 0, duration; int rc = EXIT_FAILURE; program = argv[0]; @@ -268,8 +314,11 @@ int main(int argc, char ** argv) { mpd_port = iniparser_getint(ini, ":port", mpd_port); music_dir = iniparser_getstring(ini, ":music-dir", music_dir); notification_timeout = iniparser_getint(ini, ":timeout", notification_timeout); - oneline = iniparser_getboolean(ini, ":oneline", oneline); scale = iniparser_getint(ini, ":scale", scale); + text_topic = iniparser_getstring(ini, ":text-topic", text_topic); + text_play = iniparser_getstring(ini, ":text-play", text_play); + text_pause = iniparser_getstring(ini, ":text-pause", text_pause); + text_stop = iniparser_getstring(ini, ":text-stop", text_stop); } /* get the verbose status */ @@ -278,9 +327,6 @@ int main(int argc, char ** argv) { case 'h': help++; break; - case 'o': - oneline++; - break; case 'v': verbose++; break; @@ -306,7 +352,7 @@ int main(int argc, char ** argv) { " (compiled: " __DATE__ ", " __TIME__ ")\n", program, PROGNAME, VERSION); if (help > 0) - fprintf(stderr, "usage: %s [-h] [-H HOST] [-m MUSIC-DIR] [-o] [-p PORT] [-s PIXELS] [-t TIMEOUT] [-v] [-V]\n", program); + fprintf(stderr, "usage: %s [-h] [-H HOST] [-m MUSIC-DIR] [-p PORT] [-s PIXELS] [-t TIMEOUT] [-v] [-V]\n", program); if (version > 0 || help > 0) return EXIT_SUCCESS; @@ -387,18 +433,21 @@ int main(int argc, char ** argv) { notification = # if NOTIFY_CHECK_VERSION(0, 7, 0) - notify_notification_new(TEXT_TOPIC, TEXT_NONE, ICON_AUDIO_X_GENERIC); + notify_notification_new(text_topic, TEXT_NONE, ICON_AUDIO_X_GENERIC); # else - notify_notification_new(TEXT_TOPIC, TEXT_NONE, ICON_AUDIO_X_GENERIC, NULL); + notify_notification_new(text_topic, TEXT_NONE, ICON_AUDIO_X_GENERIC, NULL); # endif notify_notification_set_category(notification, PROGNAME); notify_notification_set_urgency(notification, NOTIFY_URGENCY_NORMAL); notify_notification_set_timeout(notification, notification_timeout * 1000); - signal(SIGHUP, received_signal); - signal(SIGINT, received_signal); - signal(SIGTERM, received_signal); - signal(SIGUSR1, received_signal); + struct sigaction act = { 0 }; + act.sa_handler = received_signal; + + sigaction(SIGHUP, &act, NULL); + sigaction(SIGINT, &act, NULL); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGUSR1, &act, NULL); /* report ready to systemd */ #ifdef HAVE_SYSTEMD @@ -417,7 +466,7 @@ int main(int argc, char ** argv) { * too late, which results in issue with image date. Make sure to * show a notification without image data (just generic icon) first. */ if (last_state != MPD_STATE_PLAY && last_state != MPD_STATE_PAUSE) { - notify_notification_update(notification, TEXT_TOPIC, "Starting playback...", ICON_AUDIO_X_GENERIC); + notify_notification_update(notification, text_topic, "Starting playback...", ICON_AUDIO_X_GENERIC); notify_notification_show(notification, NULL); } @@ -425,7 +474,10 @@ int main(int argc, char ** argv) { song = mpd_recv_song(conn); - title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); + title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0); + artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0); + album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0); + duration = mpd_song_get_duration(song); /* ignore if we have no title */ if (title == NULL) @@ -435,16 +487,9 @@ int main(int argc, char ** argv) { sd_notifyf(0, "READY=1\nSTATUS=%s: %s", state == MPD_STATE_PLAY ? "Playing" : "Paused", title); #endif - /* initial allocation and string termination */ - notifystr = strdup(""); - notifystr = append_string(notifystr, TEXT_PLAY_PAUSE_STATE, 0, state == MPD_STATE_PLAY ? "Playing": "Paused"); - notifystr = append_string(notifystr, TEXT_PLAY_PAUSE_TITLE, 0, title); - - if ((artist = mpd_song_get_tag(song, MPD_TAG_ARTIST, 0)) != NULL) - notifystr = append_string(notifystr, TEXT_PLAY_PAUSE_ARTIST, oneline ? ' ' : '\n', artist); - - if ((album = mpd_song_get_tag(song, MPD_TAG_ALBUM, 0)) != NULL) - notifystr = append_string(notifystr, TEXT_PLAY_PAUSE_ALBUM, oneline ? ' ' : '\n', album); + /* get the formatted notification string */ + notifystr = format_text(state == MPD_STATE_PLAY ? text_play : text_pause, + title, artist ? artist : "unknown artist", album ? album : "unknown album", duration); uri = mpd_song_get_uri(song); @@ -473,9 +518,9 @@ int main(int argc, char ** argv) { mpd_song_free(song); } else if (state == MPD_STATE_STOP) { - notifystr = strdup(TEXT_STOP); + notifystr = strdup(text_stop); #ifdef HAVE_SYSTEMD - sd_notify(0, "READY=1\nSTATUS=" TEXT_STOP); + sd_notifyf(0, "READY=1\nSTATUS=%s", text_stop); #endif } else notifystr = strdup(TEXT_UNKNOWN); @@ -490,9 +535,9 @@ int main(int argc, char ** argv) { if (file_workaround > 0 && pixbuf != NULL) { gdk_pixbuf_save(pixbuf, "/tmp/.mpd-notification-artwork.png", "png", NULL, NULL); - notify_notification_update(notification, TEXT_TOPIC, notifystr, "/tmp/.mpd-notification-artwork.png"); + notify_notification_update(notification, text_topic, notifystr, "/tmp/.mpd-notification-artwork.png"); } else - notify_notification_update(notification, TEXT_TOPIC, notifystr, ICON_AUDIO_X_GENERIC); + notify_notification_update(notification, text_topic, notifystr, ICON_AUDIO_X_GENERIC); /* Call this unconditionally! When pixbuf is NULL this clears old image. */ notify_notification_set_image_from_pixbuf(notification, pixbuf); diff --git a/mpd-notification.h b/mpd-notification.h index cd8b386..d11e822 100644 --- a/mpd-notification.h +++ b/mpd-notification.h @@ -34,7 +34,7 @@ #include <systemd/sd-daemon.h> #endif -#include <iniparser.h> +#include <iniparser/iniparser.h> #include <libnotify/notify.h> #include <mpd/client.h> @@ -56,8 +56,8 @@ void received_signal(int signal); /*** retrieve_artwork ***/ GdkPixbuf * retrieve_artwork(const char * music_dir, const char * uri); -/*** append_string ***/ -char * append_string(char * string, const char * format, const char delim, const char * s); +/*** format_text ***/ +char * format_text(const char* format, const char* title, const char* artist, const char* album, unsigned int duration); /*** main ***/ int main(int argc, char ** argv); |