aboutsummaryrefslogtreecommitdiffstats
path: root/mpd-notification.c
diff options
context:
space:
mode:
Diffstat (limited to 'mpd-notification.c')
-rw-r--r--mpd-notification.c264
1 files changed, 180 insertions, 84 deletions
diff --git a/mpd-notification.c b/mpd-notification.c
index 2ba710f..0d18fea 100644
--- a/mpd-notification.c
+++ b/mpd-notification.c
@@ -1,19 +1,29 @@
/*
- * (C) 2011-2019 by Christian Hesse <mail@eworm.de>
+ * (C) 2011-2025 by Christian Hesse <mail@eworm.de>
+ *
+ * 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 3 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/>.
*
- * This software may be used and distributed according to the terms
- * of the GNU General Public License, incorporated herein by reference.
*/
#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' },
@@ -30,10 +40,35 @@ NotifyNotification * notification = NULL;
struct mpd_connection * conn = NULL;
uint8_t doexit = 0;
uint8_t verbose = 0;
-uint8_t oneline = 0;
-#ifdef HAVE_LIBAV
+#ifdef HAVE_MAGIC
magic_t magic = NULL;
-#endif
+#endif /* HAVE_MAGIC */
+
+/* wrapper for sd_notify() to avoid #ifdef */
+static int mpdn_sd_notify(int unset_environment, const char *format, ...) {
+ int r = 0;
+
+#ifdef HAVE_SYSTEMD
+ va_list args;
+ char *string;
+ size_t str_len;
+
+ va_start(args, format);
+ str_len = vsnprintf(NULL, 0, format, args) + 1;
+ va_end(args);
+
+ string = malloc(str_len);
+
+ va_start(args, format);
+ vsnprintf(string, str_len, format, args);
+ va_end(args);
+
+ r = sd_notify(unset_environment, string);
+ free(string);
+#endif /* HAVE_SYSTEMD */
+
+ return r;
+}
/*** received_signal ***/
void received_signal(int signal) {
@@ -74,9 +109,8 @@ GdkPixbuf * retrieve_artwork(const char * music_dir, const char * uri) {
#ifdef HAVE_LIBAV
int i;
- const char *magic_mime;
AVFormatContext * pFormatCtx = NULL;
- GdkPixbufLoader * loader;
+ GdkPixbufLoader * loader = NULL;
/* try album artwork first */
if ((uri_path = malloc(strlen(music_dir) + strlen(uri) + 2)) == NULL) {
@@ -86,6 +120,9 @@ GdkPixbuf * retrieve_artwork(const char * music_dir, const char * uri) {
sprintf(uri_path, "%s/%s", music_dir, uri);
+#ifdef HAVE_MAGIC
+ const char *magic_mime;
+
if ((magic_mime = magic_file(magic, uri_path)) == NULL) {
fprintf(stderr, "%s: We did not get a MIME type...\n", program);
goto image;
@@ -94,8 +131,14 @@ 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/flac") != 0 &&
+ strcmp(magic_mime, "audio/mp4") != 0 &&
+ strcmp(magic_mime, "audio/mpeg") != 0 &&
+ strcmp(magic_mime, "audio/ogg") != 0 &&
+ strcmp(magic_mime, "audio/x-m4a") != 0)
goto image;
+#endif /* HAVE_MAGIC */
if ((pFormatCtx = avformat_alloc_context()) == NULL) {
fprintf(stderr, "%s: avformat_alloc_context() failed.\n", program);
@@ -107,11 +150,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) {
@@ -127,19 +165,32 @@ GdkPixbuf * retrieve_artwork(const char * music_dir, const char * uri) {
fprintf(stderr, "%s: gdk_pixbuf_loader_write() failed parsing buffer.\n", program);
goto image;
}
+
+ if (gdk_pixbuf_loader_close(loader, NULL) == FALSE) {
+ fprintf(stderr, "%s: gdk_pixbuf_loader_close() failed.\n", program);
+ goto image;
+ }
if ((pixbuf = gdk_pixbuf_loader_get_pixbuf(loader)) == NULL) {
fprintf(stderr, "%s: gdk_pixbuf_loader_get_pixbuf() failed creating pixbuf.\n", program);
goto image;
}
- gdk_pixbuf_loader_close(loader, NULL);
+ g_object_ref(pixbuf);
+ g_object_unref(loader);
+ loader = NULL;
+
goto done;
}
}
+ if (pixbuf == NULL && verbose > 0)
+ printf("%s: No artwork in media file.\n", program);
+
image:
-#endif
+ if (loader)
+ g_object_unref(loader);
+#endif /* HAVE_LIBAV */
/* cut the file name from path for current directory */
*strrchr(uri_path, '/') = 0;
@@ -190,33 +241,79 @@ done:
avformat_close_input(&pFormatCtx);
avformat_free_context(pFormatCtx);
}
-#endif
+#endif /* HAVE_LIBAV */
free(uri_path);
return pixbuf;
}
-/*** append_string ***/
-char * append_string(char * string, const char * format, const char delim, const char * s) {
- char * tmp, * offset;
+/*** 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;
- tmp = g_markup_escape_text(s, -1);
+ if (format == NULL || strlen(format) == 0)
+ return NULL;
- string = realloc(string, strlen(string) + strlen(format) + strlen(tmp) + 2 /* delim + line break */);
+ formatted = strdup("");
+ len = 0;
- offset = string + strlen(string);
+ do {
+ if (*format == '%') {
+ format++;
- if (delim > 0) {
- *offset = delim;
- offset++;
- }
+ switch (*format) {
+ case 'a':
+ tmp = g_markup_escape_text(artist, -1);
+ break;
+
+ case 'A':
+ tmp = g_markup_escape_text(album, -1);
+ break;
- sprintf(offset, format, tmp);
+ 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;
- free(tmp);
+ case 't':
+ tmp = g_markup_escape_text(title, -1);
+ break;
- return string;
+ default:
+ formatted = realloc(formatted, len + 2);
+ sprintf(formatted + len, "%%");
+ format--;
+ break;
+ }
+
+ 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 formatted;
}
/*** main ***/
@@ -227,10 +324,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];
@@ -246,15 +344,22 @@ int main(int argc, char ** argv) {
music_dir = getenv("XDG_MUSIC_DIR");
/* parse config file */
- if (chdir(getenv("HOME")) == 0 && access(".config/mpd-notification.conf", R_OK) == 0 &&
- (ini = iniparser_load(".config/mpd-notification.conf")) != NULL) {
+ if (chdir(getenv("XDG_CONFIG_HOME")) == 0 && access("mpd-notification.conf", R_OK) == 0) {
+ ini = iniparser_load("mpd-notification.conf");
+ } else if (chdir(getenv("HOME")) == 0 && access(".config/mpd-notification.conf", R_OK) == 0) {
+ ini = iniparser_load(".config/mpd-notification.conf");
+ }
+ if (ini != NULL) {
file_workaround = iniparser_getboolean(ini, ":notification-file-workaround", file_workaround);
mpd_host = iniparser_getstring(ini, ":host", mpd_host);
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 */
@@ -263,9 +368,6 @@ int main(int argc, char ** argv) {
case 'h':
help++;
break;
- case 'o':
- oneline++;
- break;
case 'v':
verbose++;
break;
@@ -284,14 +386,17 @@ int main(int argc, char ** argv) {
printf("%s: %s v%s"
#ifdef HAVE_SYSTEMD
" +systemd"
-#endif
+#endif /* HAVE_SYSTEMD */
#ifdef HAVE_LIBAV
" +libav"
-#endif
+#ifdef HAVE_MAGIC
+ " +libmagic"
+#endif /* HAVE_MAGIC */
+#endif /* HAVE_LIBAV */
" (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;
@@ -338,7 +443,7 @@ int main(int argc, char ** argv) {
#ifdef HAVE_LIBAV
/* libav */
-#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
+#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
av_register_all();
#endif
@@ -346,6 +451,7 @@ int main(int argc, char ** argv) {
if (verbose == 0)
av_log_set_level(AV_LOG_FATAL);
+#ifdef HAVE_MAGIC
if ((magic = magic_open(MAGIC_MIME_TYPE)) == NULL) {
fprintf(stderr, "%s: Could not initialize magic library.\n", program);
goto out40;
@@ -356,7 +462,8 @@ int main(int argc, char ** argv) {
magic_close(magic);
goto out30;
}
-#endif
+#endif /* HAVE_MAGIC */
+#endif /* HAVE_LIBAV */
conn = mpd_connection_new(mpd_host, mpd_port, mpd_timeout * 1000);
@@ -372,23 +479,24 @@ 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
- sd_notify(0, "READY=1\nSTATUS=Waiting for mpd event...");
-#endif
+ mpdn_sd_notify(0, "READY=1\nSTATUS=Waiting for mpd event...");
while (doexit == 0 && mpd_run_idle_mask(conn, MPD_IDLE_PLAYER)) {
mpd_command_list_begin(conn, true);
@@ -401,8 +509,8 @@ int main(int argc, char ** argv) {
/* There's a bug in libnotify where the server spec version is fetched
* 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) {
- notify_notification_update(notification, TEXT_TOPIC, "Starting playback...", ICON_AUDIO_X_GENERIC);
+ 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_show(notification, NULL);
}
@@ -410,26 +518,20 @@ 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)
goto nonotification;
-#ifdef HAVE_SYSTEMD
- 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);
+ mpdn_sd_notify(0, "READY=1\nSTATUS=%s: %s", state == MPD_STATE_PLAY ? "Playing" : "Paused", 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);
@@ -458,10 +560,8 @@ int main(int argc, char ** argv) {
mpd_song_free(song);
} else if (state == MPD_STATE_STOP) {
- notifystr = strdup(TEXT_STOP);
-#ifdef HAVE_SYSTEMD
- sd_notify(0, "READY=1\nSTATUS=" TEXT_STOP);
-#endif
+ notifystr = strdup(text_stop);
+ mpdn_sd_notify(0, "READY=1\nSTATUS=%s", text_stop);
} else
notifystr = strdup(TEXT_UNKNOWN);
@@ -475,9 +575,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);
@@ -505,9 +605,7 @@ nonotification:
printf("%s: Exiting...\n", program);
/* report stopping to systemd */
-#ifdef HAVE_SYSTEMD
- sd_notify(0, "STOPPING=1\nSTATUS=Stopping...");
-#endif
+ mpdn_sd_notify(0, "STOPPING=1\nSTATUS=Stopping...");
rc = EXIT_SUCCESS;
@@ -520,18 +618,16 @@ out20:
mpd_connection_free(conn);
out30:
-#ifdef HAVE_LIBAV
+#ifdef HAVE_MAGIC
if (magic != NULL)
magic_close(magic);
out40:
-#endif
+#endif /* HAVE_MAGIC */
if (ini != NULL)
iniparser_freedict(ini);
-#ifdef HAVE_SYSTEMD
- sd_notify(0, "STATUS=Stopped. Bye!");
-#endif
+ mpdn_sd_notify(0, "STATUS=Stopped. Bye!");
return rc;
}