prezzyd/prezzyd.c
2024-12-28 10:36:36 +13:00

258 lines
5.6 KiB
C

/*
* prezzyd
* Copyright (c) 2024 Jeremy Baxter.
*/
#define _PREZZYD_C_
#define _DEFAULT_SOURCE
#ifndef BUILD_DATE
#define BUILD_DATE "1970-01-01"
#endif
#include <err.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "prezzyipc.h"
#include "discord_rpc.h"
void
connectHandler(const DiscordUser *user)
{
warnx("established connection to Discord (%s/%s)",
user->username, user->userId);
}
void
disconnectHandler(int code, const char *message)
{
warnx("disconnected from client (%d: %s)", code, message);
}
void
errorHandler(int code, const char *message)
{
errx(1, "rpc error %d: %s", code, message);
}
int
canReadFrom(int fd)
{
struct pollfd *p;
int ret;
p = malloc(sizeof(struct pollfd));
p->fd = fd;
p->events = POLLIN;
if (poll(p, 1, 5000) == -1)
err(1, "failed to poll client");
ret = p->revents & POLLIN;
free(p);
return ret;
}
int
ipcClose(int fd, byte code, const char *message)
{
write(fd, (char []){code}, 1);
write(fd, message, strlen(message));
if (close(fd) == -1)
err(1, "failed to close IPC connection");
return code;
}
void
ipcSucceed(int fd)
{
ipcClose(fd, 0, "OK");
}
int
makeIpcServer(const char *bindPath, int backlog)
{
struct sockaddr_un addr;
int fd;
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
err(1, "failed to create IPC socket");
if (remove(bindPath) == -1) {
if (errno != ENOENT)
err(1, "could not remove %s", bindPath);
}
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strbcpy(addr.sun_path, bindPath, sizeof(addr.sun_path) - 1);
if (bind(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1)
err(1, "failed to bind to %s", bindPath);
if (listen(fd, backlog) == -1)
err(1, "failed to listen on IPC socket %s", bindPath);
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == -1)
err(1, "failed to create non-blocking IPC socket");
return fd;
}
int
main(int argc, char **argv)
{
void *propertyMapping[PREZZY_PROPERTY_MAX];
DiscordRichPresence presence;
DiscordEventHandlers handlers;
const char *clientId;
int ch, sd;
while ((ch = getopt(argc, argv, "V")) != -1) {
switch (ch) {
case 'V':
printf("prezzyd version 0.1 built on " BUILD_DATE "\n");
return 0;
break;
}
}
if (argc - optind != 1) {
fputs("usage: prezzyd [-V] client-id\n", stderr);
return 1;
}
clientId = argv[optind];
memset(&presence, 0, sizeof(DiscordRichPresence));
memset(&handlers, 0, sizeof(DiscordEventHandlers));
handlers.ready = connectHandler;
handlers.disconnected = disconnectHandler;
handlers.errored = errorHandler;
Discord_Initialize(clientId, &handlers, 1, NULL);
propertyMapping[PREZZY_STATE] = &presence.state;
propertyMapping[PREZZY_DETAILS] = &presence.details;
propertyMapping[PREZZY_LARGE_IMAGE] = &presence.largeImageKey;
propertyMapping[PREZZY_LARGE_TOOLTIP] = &presence.largeImageText;
propertyMapping[PREZZY_ICON] = &presence.smallImageKey;
propertyMapping[PREZZY_ICON_TOOLTIP] = &presence.smallImageText;
propertyMapping[PREZZY_PARTY_SIZE] = &presence.partySize;
propertyMapping[PREZZY_PARTY_LIMIT] = &presence.partyMax;
propertyMapping[PREZZY_TIMESTAMP_START] = &presence.startTimestamp;
propertyMapping[PREZZY_TIMESTAMP_END] = &presence.endTimestamp;
/* start ipc server */
sd = makeIpcServer(PREZZY_SOCK, 8);
for (;;) {
PrezzyIpcPacket **packets;
char buffer[PREZZY_PACKET_MAX];
ssize_t sz;
int client, i, packetCount;
char ch;
packets = NULL;
packetCount = 0;
client = accept(sd, NULL, NULL);
if (client == -1) {
switch (errno) {
case EWOULDBLOCK:
goto update;
default:
err(1, "failed to accept incoming IPC connection");
break;
}
}
/*
* IPC error codes:
* 1: error during initial read
* 2: bad request format
*/
if (!canReadFrom(client))
ipcClose(client, 1, "Timed out");
for (i = 0; (sz = read(client, &ch, 1)) > 0; i++) {
if (i == PREZZY_PACKET_MAX) {
ipcClose(client, 0, "OK (truncated)");
buffer[i - 1] = '\0';
break;
}
switch (ch) {
case '\n':
case '\0':
/* end current packet */
if (i + 1 < 2 && /* are we before the payload? */
/* accept null bytes after newlines */
!(ch == '\0' && i == 0)) {
ipcClose(client, 2, "Request missing payload");
goto update;
}
if (buffer[0] < 0 || buffer[0] >= PREZZY_PROPERTY_MAX) {
ipcClose(client, 2, "Invalid property byte");
goto update;
}
buffer[i] = '\0';
packetCount++;
packets = realloc(packets, packetCount *
sizeof(PrezzyIpcPacket *));
packets[packetCount - 1] = makeIpcPacket(buffer);
if (ch == '\0') {
ipcSucceed(client);
goto process;
}
/* '\n'; more requests later */
i = -1;
continue;
default:
buffer[i] = ch;
break;
}
}
if (sz == -1)
err(1, "failed to read from IPC client");
process:
for (i = 0; i < packetCount; i++) {
size_t len;
if (packets[i]->type == PLT_STRING) {
len = strlen(packets[i]->pString) + 1;
*(char **)propertyMapping[packets[i]->property] =
malloc(len * sizeof(char));
strlcpy(*(char **)propertyMapping[packets[i]->property],
packets[i]->pString, len);
} else {
if (packets[i]->type == PLT_INT64) {
*(int64_t *)propertyMapping[packets[i]->property] =
packets[i]->pInteger;
} else {
*(int *)propertyMapping[packets[i]->property] =
squashLong(packets[i]->pInteger);
}
}
}
update:
Discord_UpdatePresence(&presence);
Discord_RunCallbacks();
usleep(64000);
}
Discord_Shutdown();
return 0;
}