258 lines
5.6 KiB
C
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;
|
|
}
|