Skip to content
Snippets Groups Projects
mpdisplay.c 7.59 KiB
Newer Older
Gigadoc 2's avatar
Gigadoc 2 committed
#define _POSIX_C_SOURCE 200809L
#include <fcntl.h>
#include <linux/wireless.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>

#include <mpd/client.h>

#include <systemd/sd-daemon.h>
#include <systemd/sd-event.h>

#define LCD_DEV "/dev/lcd"
#define LCD_WIDTH 20
#define LCD_HEIGHT 4
#define WIFI_DEV "wlan0"
#define WIFI_POLL_SEC 2

struct display_data {
	int wifi_strength;
	int wifi_strength_last;
	char playing;
	const char *name;
	const char *title;
};

struct handler_data {
	int lcd_fd;
	int sock_fd;
	struct display_data *disp_data;
	struct mpd_connection *conn;
};

void display_song(int fd, const struct display_data *data)
{
	// rewrite from the top
	dprintf(fd, "\033[Lx000y000;");

	if (data->playing) {
		dprintf(fd, "%.*s\033[Lk\n", LCD_WIDTH, data->name);
		dprintf(fd, "%.*s\033[Lk\n", LCD_WIDTH, data->title);
		// If the title is longer than our display, contine in the second line;
		// if not then fill it with a blank line
		if (strnlen(data->title, 21) > LCD_WIDTH) {
			dprintf(fd, "%.*s\033[Lk\n", LCD_WIDTH, data->title + 20);
		} else {
			dprintf(fd, "\033[Lk\n");
		}
	} else {
		dprintf(fd, "\033[Lk\n  Keine Wiedergabe\033[Lk\n\033[Lk");
	}
}

void display_wifi(int fd, struct display_data *data)
{
	// don't redraw anything if nothing changed
	if (data->wifi_strength == data->wifi_strength_last) {
		return;
	}

	// rewrite from the bottom right
	dprintf(fd, "\033[Lx017y003;");

	if (data->wifi_strength < 1) {
		dprintf(fd, "   ");
	}
	else if (data->wifi_strength < 2) {
		dprintf(fd, "\x01  ");
	}
	else if (data->wifi_strength < 3) {
		dprintf(fd, "\x01\x02 ");
	}
	else {
		dprintf(fd, "\x01\x02\x03");
	}

	data->wifi_strength_last = data->wifi_strength;
}

void init_display(int fd)
{
	// clear everything
	dprintf(fd, "\f");
	// init the custom characters
	dprintf(fd, "\033[LG4000e11040a000400;\033[LG10000000000ffff00;"
	            "\033[LG2000000ffffffff00;\033[LG300ffffffffffff00;");
	// draw the wifi logo
	dprintf(fd, "\033[Lx016y003;\x04");
}

int wifi_bars_from_dbm(int dbm)
{
	if (dbm < -70) {
		return 1;
	} else if (dbm < -67) {
		return 2;
	} else {
		return 3;
	}
}

void update_wifi(int sock_fd, struct display_data *data)
{
	struct iwreq req;
	int r;

	strcpy(req.ifr_name, WIFI_DEV);
	req.u.data.pointer = (struct iw_statistics *) malloc(sizeof(struct iw_statistics));
	req.u.data.length = sizeof(struct iw_statistics);

	r = ioctl(sock_fd, SIOCGIWSTATS, &req);
	if (r == -1) {
		if (errno == ENOTSUP) { // returned when link is down -> disconnected
			data->wifi_strength = 0;
		} else {
			perror("ioctl() failed");
		}
		return;
	}

	if (((struct iw_statistics *) req.u.data.pointer)->qual.updated & IW_QUAL_DBM) {
		data->wifi_strength = wifi_bars_from_dbm(
				((struct iw_statistics *) req.u.data.pointer)->qual.level - 256);
	} else {
		fprintf(stderr, "Warning: Could not get valid signal strength data\n");
	}
}

void update_song(struct mpd_connection *conn, struct display_data *data)
{
	enum mpd_error err;
	struct mpd_status *status;
	enum mpd_state state;
	struct mpd_song *song;

	status = mpd_run_status(conn);
	if (status == NULL) {
		err = mpd_connection_get_error(conn);
		fprintf(stderr, "Warning: Could not get player status: %s\n",
				mpd_connection_get_error_message(conn));
		return;
	}

	state = mpd_status_get_state(status);
	switch (state) {
		case MPD_STATE_UNKNOWN:
			fprintf(stderr, "Warning: MPD has unknown state!\n");
			__attribute__((fallthrough));
		case MPD_STATE_STOP:
		case MPD_STATE_PAUSE:
			data->playing = 0;
			return;
		case MPD_STATE_PLAY:
			data->playing = 1;
			break;
	}

	song = mpd_run_current_song(conn);
	err = mpd_connection_get_error(conn);
	if (err != MPD_ERROR_SUCCESS) {
		fprintf(stderr, "Warning: Could not get current song data: %s\n",
				mpd_connection_get_error_message(conn));
	}

	if (song == NULL) {
		fprintf(stderr, "Warning: Current song is NULL!\n");
		return;
	}

	data->name = mpd_song_get_tag(song, MPD_TAG_NAME, 0);
	data->title = mpd_song_get_tag(song, MPD_TAG_TITLE, 0);

	if (data->name == NULL) {
		data->name = "";
	}
	if (data->title == NULL) {
		data->title = "";
	}
}

static int mpd_handler(sd_event_source *es, int fd, uint32_t revents, void *userdata)
{
	(void) es;
	(void) fd;
	(void) revents;
	struct handler_data *data = (struct handler_data *) userdata;
	struct display_data *disp_data = data->disp_data;
	struct mpd_connection *conn = data->conn;
	int lcd_fd = data->lcd_fd;
	enum mpd_idle event_types;

	event_types = mpd_recv_idle(conn, 1);
	fprintf(stderr, "Event: %x\n", event_types);

	update_song(conn, disp_data);
	display_song(lcd_fd, disp_data);

	// TODO: error checking
	mpd_send_idle_mask(conn, MPD_IDLE_PLAYER);

	return 0;
}

static int timer_handler(sd_event_source *es, uint64_t usec, void *userdata)
{
	struct handler_data *data = (struct handler_data *) userdata;
	struct display_data *disp_data = data->disp_data;
	int lcd_fd = data->lcd_fd;
	int sock_fd = data->sock_fd;
	int r = 0;

	update_wifi(sock_fd, disp_data);
	display_wifi(lcd_fd, disp_data);

	r = sd_event_source_set_time(es, usec + (WIFI_POLL_SEC * 1000000));
	if (r < 0) {
		goto finish;
	}
	r = sd_event_source_set_enabled(es, SD_EVENT_ONESHOT);
	if (r < 0) {
		goto finish;
	}

finish:
	return r;
}

int main(void)
{
	int r;

	struct mpd_connection *mpd_conn = NULL;

	int mpd_fd = -1;
	sd_event_source *mpd_es = NULL;
	sd_event_source *timer_es = NULL;
	sd_event *event = NULL;
	sigset_t ss;

	int lcd_fd = -1;
	struct display_data data;
	struct handler_data userdata;

	int sock_fd;

	r = sd_event_default(&event);
	if (r < 0) {
		goto finish;
	}

	if (sigemptyset(&ss) < 0 ||
			sigaddset(&ss, SIGTERM) < 0 ||
			sigaddset(&ss, SIGINT) < 0) {
		r = -errno;
		goto finish;
	}

	/* Block SIGTERM first, so that the event loop can handle it */
	if (sigprocmask(SIG_BLOCK, &ss, NULL) < 0) {
		r = -errno;
		goto finish;
	}

	/* Let's make use of the default handler and "floating" reference features of sd_event_add_signal() */
	r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL);
	if (r < 0) {
		goto finish;
	}
	r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
	if (r < 0) {
		goto finish;
	}

	mpd_conn = mpd_connection_new(NULL, 0, 0);
	if (mpd_conn == NULL) {
		r = -ENOMEM;
		goto finish;
	}
	if (mpd_connection_get_error(mpd_conn) != MPD_ERROR_SUCCESS) {
		fprintf(stderr, "Error getting MPD connection: %s\n",
				mpd_connection_get_error_message(mpd_conn));
		r = -EIO;
		goto finish;
	}

	lcd_fd = open(LCD_DEV, O_WRONLY);
	if (lcd_fd < 0) {
		r = -errno;
		goto finish;
	}

	update_song(mpd_conn, &data);
	init_display(lcd_fd);
	display_song(lcd_fd, &data);

	if (!mpd_send_idle_mask(mpd_conn, MPD_IDLE_PLAYER)) {
		r = -255;
		goto finish;
	}
	mpd_fd = mpd_connection_get_fd(mpd_conn);
	userdata.disp_data = &data;
	userdata.conn = mpd_conn;
	userdata.lcd_fd = lcd_fd;
	r = sd_event_add_io(event, &mpd_es, mpd_fd, EPOLLIN, mpd_handler,
			(void *) &userdata);
	if (r < 0) {
		goto finish;
	}

	sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock_fd <= 0) {
		perror("Could not open AF_INET socket for wifi stats");
	}
	userdata.sock_fd = sock_fd;
	r = sd_event_add_time(event, &timer_es, CLOCK_MONOTONIC, 0, 1000000,
			timer_handler, (void *) &userdata);
	if (r < 0) {
		goto finish;
	}

	r = sd_event_loop(event);

finish:
	mpd_es = sd_event_source_unref(mpd_es);
	timer_es = sd_event_source_unref(timer_es);
	event = sd_event_unref(event);
	mpd_connection_free(mpd_conn);

	if (lcd_fd >= 0) {
		(void) close(lcd_fd);
	}

	if (mpd_fd >= 0) {
		(void) close(mpd_fd);
	}

	if (r < 0) {
		fprintf(stderr, "Failure: %s\n", strerror(-r));
	}

	return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}