diff options
| -rw-r--r-- | Makefile | 14 | ||||
| -rw-r--r-- | README.md | 60 | ||||
| -rw-r--r-- | config.def.h | 21 | ||||
| -rw-r--r-- | mpd-notification.c | 163 | ||||
| -rw-r--r-- | mpd-notification.h | 8 | ||||
| -rw-r--r-- | systemd/mpd-notification.service | 2 | 
6 files changed, 178 insertions, 90 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,8 @@ 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 +DISTVER := 0.9.2 +VERSION ?= $(shell git describe --long 2>/dev/null || echo $(DISTVER))  all: mpd-notification README.html @@ -65,6 +63,6 @@ distclean:  	$(RM) -f *.o *~ README.html mpd-notification version.h config.h  release: -	git archive --format=tar.xz --prefix=mpd-notification-$(VERSION)/ $(VERSION) > mpd-notification-$(VERSION).tar.xz -	gpg --armor --detach-sign --comment mpd-notification-$(VERSION).tar.xz mpd-notification-$(VERSION).tar.xz -	git notes --ref=refs/notes/signatures/tar add -C $$(git archive --format=tar --prefix=mpd-notification-$(VERSION)/ $(VERSION) | gpg --armor --detach-sign --comment mpd-notification-$(VERSION).tar | git hash-object -w --stdin) $(VERSION) +	git archive --format=tar.xz --prefix=mpd-notification-$(DISTVER)/ $(DISTVER) > mpd-notification-$(DISTVER).tar.xz +	gpg --armor --detach-sign --comment mpd-notification-$(DISTVER).tar.xz mpd-notification-$(DISTVER).tar.xz +	git notes --ref=refs/notes/signatures/tar add -C $$(git archive --format=tar --prefix=mpd-notification-$(DISTVER)/ $(DISTVER) | gpg --armor --detach-sign --comment mpd-notification-$(DISTVER).tar | git hash-object -w --stdin) $(DISTVER) @@ -1,6 +1,10 @@  mpd-notification  ================ +[](https://github.com/eworm-de/mpd-notification/stargazers) +[](https://github.com/eworm-de/mpd-notification/network) +[](https://github.com/eworm-de/mpd-notification/watchers) +  **Notify about tracks played by mpd**  This runs in background and produces notifications whenever mpd produces @@ -15,23 +19,27 @@ This now even supports album artwork:  Read below for the details. +*Use at your own risk*, pay attention to +[license and warranty](#license-and-warranty), and +[disclaimer on external links](#disclaimer-on-external-links)! +  Requirements  ------------  To compile and run `mpd-notification` you need: -* [systemd](https://www.github.com/systemd/systemd) -* [file](https://www.darwinsys.com/file/) for `libmagic` -* [iniparser](https://github.com/ndevilla/iniparser) -* [libav](https://libav.org/) or [ffmpeg](https://www.ffmpeg.org/) -* [libnotify](https://developer.gnome.org/notification-spec/) -* [libmpdclient](https://www.musicpd.org/libs/libmpdclient/) -* [markdown](https://daringfireball.net/projects/markdown/) (HTML documentation) +* [systemd ↗️](https://www.github.com/systemd/systemd) +* [file ↗️](https://www.darwinsys.com/file/) for `libmagic` +* [iniparser ↗️](https://github.com/ndevilla/iniparser) +* [libav ↗️](https://libav.org/) or [ffmpeg ↗️](https://www.ffmpeg.org/) +* [libnotify ↗️](https://developer.gnome.org/notification-spec/) +* [libmpdclient ↗️](https://www.musicpd.org/libs/libmpdclient/) +* [markdown ↗️](https://daringfireball.net/projects/markdown/) (HTML documentation)  * `gnome-icon-theme` or `adwaita-icon-theme` (or anything else that includes    an icon named `audio-x-generic`)  To use `mpd-notification` you probably want `mpd`, the -[music player daemon](http://www.musicpd.org/) itself. ;) +[music player daemon ↗️](https://www.musicpd.org/) itself. ;)  Some systems may require additional development packages for the libraries.  Look for `libnotify-devel`, `libmpdclient-devel` or similar. @@ -41,11 +49,11 @@ Build and install  Building and installing is very easy. Just run: -> make +    make  followed by: -> make install +    make install  This will place an executable at `/usr/bin/mpd-notification`,  documentation can be found in `/usr/share/doc/mpd-notification/`. @@ -65,7 +73,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 +91,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  ------- @@ -115,6 +133,21 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  [GNU General Public License](COPYING.md) for more details. +Disclaimer on external links +---------------------------- + +Our website contains links to the websites of third parties ("external +links"). As the content of these websites is not under our control, we +cannot assume any liability for such external content. In all cases, the +provider of information of the linked websites is liable for the content +and accuracy of the information provided. At the point in time when the +links were placed, no infringements of the law were recognisable to us. +As soon as an infringement of the law becomes known to us, we will +immediately remove the link in question. + +> 💡️ **Hint**: All external links are marked with an arrow pointing +> diagonally in an up-right (or north-east) direction (↗️). +  ### Upstream  URL: @@ -123,3 +156,6 @@ URL:  Mirror:  [eworm.de](https://git.eworm.de/cgit.cgi/mpd-notification/)  [GitLab.com](https://gitlab.com/eworm-de/mpd-notification#mpd-notification) + +--- +[⬆️ Go back to top](#top) diff --git a/config.def.h b/config.def.h index bc64e88..e7a1264 100644 --- a/config.def.h +++ b/config.def.h @@ -1,5 +1,5 @@  /* - * (C) 2011-2024 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 @@ -26,19 +26,18 @@  #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 */ -#define REGEX_ARTWORK	"\\(folder\\|cover\\)\\.\\(jpg\\|png\\)" +#define REGEX_ARTWORK	"\\(folder\\|cover\\)\\.\\(avif\\|jpg\\|png\\|webp\\)"  /* how to connect to mpd host ?   * MPD_HOST is the server's host name, IP address or Unix socket path. If the diff --git a/mpd-notification.c b/mpd-notification.c index 2e48bb0..c48979e 100644 --- a/mpd-notification.c +++ b/mpd-notification.c @@ -1,5 +1,5 @@  /* - * (C) 2011-2024 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 @@ -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 @@ -87,7 +85,7 @@ GdkPixbuf * retrieve_artwork(const char * music_dir, const char * uri) {  	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) { @@ -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) { @@ -138,18 +134,31 @@ 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: +	if (loader) +		g_object_unref(loader);  #endif  	/* cut the file name from path for current directory */ @@ -208,26 +217,72 @@ done:  	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; + +				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; -	sprintf(offset, format, tmp); +				case 't': +					tmp = g_markup_escape_text(title, -1); +					break; -	free(tmp); +				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 string; +	return formatted;  }  /*** main ***/ @@ -238,10 +293,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 +324,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 +337,6 @@ int main(int argc, char ** argv) {  			case 'h':  				help++;  				break; -			case 'o': -				oneline++; -				break;  			case 'v':  				verbose++;  				break; @@ -306,7 +362,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 +443,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 +476,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 +484,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 +497,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 +528,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 +545,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..3e7254f 100644 --- a/mpd-notification.h +++ b/mpd-notification.h @@ -1,5 +1,5 @@  /* - * (C) 2011-2024 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 @@ -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); diff --git a/systemd/mpd-notification.service b/systemd/mpd-notification.service index 61b8f66..6a75da1 100644 --- a/systemd/mpd-notification.service +++ b/systemd/mpd-notification.service @@ -1,4 +1,4 @@ -# (C) 2011-2024 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 |