diff options
Diffstat (limited to 'mpd-notification.c')
| -rw-r--r-- | mpd-notification.c | 264 |
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; } |