Skip to content
Snippets Groups Projects
Verified Commit e28e940d authored by Gigadoc 2's avatar Gigadoc 2
Browse files

initial working version

parents
No related branches found
No related tags found
No related merge requests found
CFLAGS += -Wall -Wextra --pedantic
override CFLAGS += $(shell pkg-config --cflags --libs libsystemd)
override CFLAGS += $(shell pkg-config --cflags --libs libmpdclient)
mpdisplay: mpdisplay.c
#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;
}
[Unit]
Description=MPD-to-charlcd information display
Wants=dev-lcd.device
After=dev-lcd.device
After=network.target
After=mpd.service
[Service]
Type=simple
ExecStart=/usr/bin/mpdisplay
Restart=always
User=mpdisplay
Group=mpdisplay
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ProtectHostname=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
RemoveIPC=yes
[Install]
WantedBy=multi-user.target
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment