initial commit

This commit is contained in:
Jeremy Baxter 2024-12-28 10:36:36 +13:00
commit 8c8158ad5b
7 changed files with 724 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
prezzyd
prezzyc
*.o

25
Makefile Normal file
View file

@ -0,0 +1,25 @@
CC = cc
CFLAGS = -std=c99 -O2 -pedantic -Wall -Wextra
LDFLAGS = -ldiscord-rpc
OBJS = prezzyipc.o
all: prezzyd prezzyc
prezzyd: prezzyd.o ${OBJS}
cc ${CFLAGS} -o $@ prezzyd.o ${OBJS} ${LDFLAGS}
prezzyc: prezzyc.o ${OBJS}
cc ${CFLAGS} -o $@ prezzyc.o ${OBJS} ${LDFLAGS}
.SUFFIXES: .c .o
.c.o:
${CC} ${CFLAGS} -DBUILD_DATE=\"$$(date -I)\" -c $<
clean:
rm -f prezzyd prezzyc prezzyd.o prezzyc.o ${OBJS}
.PHONY: all clean
prezzyd.o: prezzyd.c prezzyipc.h discord_rpc.h
prezzyc.o: prezzyc.c prezzyipc.h
prezzyipc.o: prezzyipc.c prezzyipc.h

87
discord_rpc.h Normal file
View file

@ -0,0 +1,87 @@
#pragma once
#include <stdint.h>
// clang-format off
#if defined(DISCORD_DYNAMIC_LIB)
# if defined(_WIN32)
# if defined(DISCORD_BUILDING_SDK)
# define DISCORD_EXPORT __declspec(dllexport)
# else
# define DISCORD_EXPORT __declspec(dllimport)
# endif
# else
# define DISCORD_EXPORT __attribute__((visibility("default")))
# endif
#else
# define DISCORD_EXPORT
#endif
// clang-format on
#ifdef __cplusplus
extern "C" {
#endif
typedef struct DiscordRichPresence {
const char* state; /* max 128 bytes */
const char* details; /* max 128 bytes */
int64_t startTimestamp;
int64_t endTimestamp;
const char* largeImageKey; /* max 32 bytes */
const char* largeImageText; /* max 128 bytes */
const char* smallImageKey; /* max 32 bytes */
const char* smallImageText; /* max 128 bytes */
const char* partyId; /* max 128 bytes */
int partySize;
int partyMax;
const char* matchSecret; /* max 128 bytes */
const char* joinSecret; /* max 128 bytes */
const char* spectateSecret; /* max 128 bytes */
int8_t instance;
} DiscordRichPresence;
typedef struct DiscordUser {
const char* userId;
const char* username;
const char* discriminator;
const char* avatar;
} DiscordUser;
typedef struct DiscordEventHandlers {
void (*ready)(const DiscordUser* request);
void (*disconnected)(int errorCode, const char* message);
void (*errored)(int errorCode, const char* message);
void (*joinGame)(const char* joinSecret);
void (*spectateGame)(const char* spectateSecret);
void (*joinRequest)(const DiscordUser* request);
} DiscordEventHandlers;
#define DISCORD_REPLY_NO 0
#define DISCORD_REPLY_YES 1
#define DISCORD_REPLY_IGNORE 2
DISCORD_EXPORT void Discord_Initialize(const char* applicationId,
DiscordEventHandlers* handlers,
int autoRegister,
const char* optionalSteamId);
DISCORD_EXPORT void Discord_Shutdown(void);
/* checks for incoming messages, dispatches callbacks */
DISCORD_EXPORT void Discord_RunCallbacks(void);
/* If you disable the lib starting its own io thread, you'll need to call this from your own */
#ifdef DISCORD_DISABLE_IO_THREAD
DISCORD_EXPORT void Discord_UpdateConnection(void);
#endif
DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence);
DISCORD_EXPORT void Discord_ClearPresence(void);
DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply);
DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
#ifdef __cplusplus
} /* extern "C" */
#endif

146
prezzyc.c Normal file
View file

@ -0,0 +1,146 @@
/*
* prezzyc
* Copyright (c) 2024 Jeremy Baxter.
*/
#define _PREZZYC_C_
#define _DEFAULT_SOURCE
#ifndef BUILD_DATE
#define BUILD_DATE "1970-01-01"
#endif
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "prezzyipc.h"
#include "discord_rpc.h"
static PrezzyIpcPacket **packets;
static int packetCount;
void
addPacket(PrezzyProperty property, PrezzyPayloadType type,
char *pString, int64_t pInteger)
{
PrezzyIpcPacket *p;
size_t len;
p = malloc(sizeof(PrezzyIpcPacket));
p->property = property;
p->type = type;
if (type == PLT_STRING) {
len = strlen(pString) + 1;
p->pString = malloc(len * sizeof(char));
strbcpy(p->pString, pString, len);
} else
p->pInteger = pInteger;
packetCount++;
packets = realloc(packets,
packetCount * sizeof(PrezzyIpcPacket *));
packets[packetCount - 1] = p;
}
int
getServer(const char *socketPath)
{
struct sockaddr_un addr;
int fd;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strbcpy(addr.sun_path, socketPath, sizeof(addr.sun_path) - 1);
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
err(1, "failed to create IPC client socket");
if (connect(fd, (struct sockaddr *)&addr,
sizeof(struct sockaddr_un)) == -1)
err(1, "failed to establish connection to IPC daemon");
return fd;
}
void
usage(void)
{
fputs(
"usage: prezzyc [-aV] [-d details] [-I icon] [-i tooltip]\n"
" [-L large-image] [-l tooltip] [-s state]\n"
" [-P limit] [-p size] [-T end] [-t start]\n",
stderr);
}
int
main(int argc, char **argv)
{
char *buffer;
ssize_t sz;
int ch, i;
int server;
if (argc < 2) { /* no options? */
usage();
return 1;
}
while ((ch = getopt(argc, argv, "ad:I:i:L:l:P:p:s:T:t:V")) != -1) {
switch (ch) {
case 'a': addPacket(PREZZY_TIMESTAMP_START, PLT_INT64, NULL, time(NULL)); break;
case 'd': addPacket(PREZZY_DETAILS, PLT_STRING, optarg, 0); break;
case 'I': addPacket(PREZZY_ICON, PLT_STRING, optarg, 0); break;
case 'i': addPacket(PREZZY_ICON_TOOLTIP, PLT_STRING, optarg, 0); break;
case 'L': addPacket(PREZZY_LARGE_IMAGE, PLT_STRING, optarg, 0); break;
case 'l': addPacket(PREZZY_LARGE_TOOLTIP, PLT_STRING, optarg, 0); break;
case 's': addPacket(PREZZY_STATE, PLT_STRING, optarg, 0); break;
case 'P': addPacket(PREZZY_PARTY_LIMIT, PLT_INT, NULL,
squashLong(strtol(optarg, NULL, 10))); break;
case 'p': addPacket(PREZZY_PARTY_SIZE, PLT_INT, NULL,
squashLong(strtol(optarg, NULL, 10))); break;
case 'T': addPacket(PREZZY_TIMESTAMP_END, PLT_INT64, NULL,
strtol(optarg, NULL, 10)); break;
case 't': addPacket(PREZZY_TIMESTAMP_START, PLT_INT64, NULL,
strtol(optarg, NULL, 10)); break;
case 'V':
printf("prezzyc version 0.1 built on " BUILD_DATE "\n");
return 0;
break;
}
}
if (argc - optind > 0) { /* extra arguments? */
usage();
return 1;
}
server = getServer(PREZZY_SOCK);
for (i = 0; i < packetCount; i++) {
buffer = fromIpcPacket(*packets[i]);
write(server, buffer, strlen(buffer));
free(buffer);
}
write(server, "\0", 1);
buffer = malloc(PREZZY_PACKET_MAX * sizeof(char));
memset(buffer, 0, PREZZY_PACKET_MAX);
while ((sz = read(server, buffer, PREZZY_PACKET_MAX - 1)) > 0) {
if (buffer[0] != 0)
errx(1, "IPC error %d: %s", buffer[0], buffer + 1);
}
if (sz == -1)
err(1, "failed to read from IPC server");
for (i = 0; i < packetCount; i++) {
if (packets[i]->type == PLT_STRING)
free(packets[i]->pString);
free(packets[i]);
}
free(packets);
return 0;
}

258
prezzyd.c Normal file
View file

@ -0,0 +1,258 @@
/*
* 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;
}

132
prezzyipc.c Normal file
View file

@ -0,0 +1,132 @@
/*
* prezzyipc.c
* Copyright (c) 2024 Jeremy Baxter.
*/
#define _PREZZYIPC_C_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "prezzyipc.h"
void
dumpPacket(PrezzyIpcPacket p)
{
fprintf(stderr,
"[packet]\n"
"property = %d\n"
"type = %d\n"
"payload = ",
p.property, p.type);
if (p.type == PLT_STRING)
fprintf(stderr, "%s\n", p.pString);
else
fprintf(stderr, "%ld\n", p.pInteger);
}
char *
fromIpcPacket(PrezzyIpcPacket p)
{
char *buffer;
buffer = malloc(PREZZY_PACKET_MAX * sizeof(char));
if (p.type == PLT_STRING) {
snprintf(buffer, PREZZY_PACKET_MAX, "%c%s\n",
p.property, p.pString);
} else {
snprintf(buffer, PREZZY_PACKET_MAX, "%c%ld\n",
p.property, p.pInteger);
}
return buffer;
}
PrezzyIpcPacket *
makeIpcPacket(const char *buffer)
{
PrezzyIpcPacket *m;
int i;
m = malloc(sizeof(PrezzyIpcPacket));
/* extract property */
m->property = buffer[0];
if (m->property < 0 || m->property >= PREZZY_PROPERTY_MAX)
return NULL;
/* determine payload type */
m->type = payloadTypeMapping[m->property];
switch (m->type) {
case PLT_STRING:
m->pString = NULL;
for (i = 1; buffer[i] != '\n' && buffer[i] != '\0'
&& i < PREZZY_PACKET_MAX; i++) {
m->pString = realloc(m->pString, (i + 1) * sizeof(char));
m->pString[i - 1] = buffer[i];
m->pString[i] = '\0';
}
break;
case PLT_INT64:
m->pInteger = strtol(buffer + 1, NULL, 10);
break;
case PLT_INT:
m->pInteger = squashLong(strtol(buffer + 1, NULL, 10));
break;
default: break;
}
return m;
}
int
squashLong(long l)
{
return (l >= INT_MIN && l <= INT_MAX) ? l : INT_MAX ;
}
/*
* Copy string src to buffer dst of size dsize. At most dsize-1
* chars will be copied. Always NUL terminates (unless dsize == 0).
* Returns strlen(src); if retval >= dsize, truncation occurred.
*/
size_t
strbcpy(char *dst, const char *src, size_t dsize)
{
const char *osrc = src;
size_t nleft = dsize;
/* Copy as many bytes as will fit. */
if (nleft != 0) {
while (--nleft != 0) {
if ((*dst++ = *src++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src. */
if (nleft == 0) {
if (dsize != 0)
*dst = '\0'; /* NUL-terminate dst */
while (*src++)
;
}
return (src - osrc - 1); /* count does not include NUL */
}
char *
stringFrom(const char *reference)
{
char *buffer;
size_t len;
len = strlen(reference) + 1;
buffer = malloc(len * sizeof(char));
strbcpy(buffer, reference, len);
return buffer;
}

73
prezzyipc.h Normal file
View file

@ -0,0 +1,73 @@
/*
* prezzyipc.h
* Copyright (c) 2024 Jeremy Baxter.
*/
#ifndef _PREZZYIPC_H_
#define _PREZZYIPC_H_
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#define PREZZY_PACKET_MAX 1024
#define PREZZY_SOCK "/tmp/prezzyd.sock"
typedef char byte;
typedef enum {
PREZZY_STATE = 1,
PREZZY_DETAILS = 2,
PREZZY_LARGE_IMAGE = 3,
PREZZY_LARGE_TOOLTIP = 4,
PREZZY_ICON = 5,
PREZZY_ICON_TOOLTIP = 6,
PREZZY_PARTY_SIZE = 7,
PREZZY_PARTY_LIMIT = 8,
PREZZY_TIMESTAMP_START = 9,
PREZZY_TIMESTAMP_END = 10,
PREZZY_PROPERTY_MAX = 11
} PrezzyProperty;
typedef enum {
PLT_STRING = 1,
PLT_INT64 = 2,
PLT_INT = 3,
PLT_MAX = 4
} PrezzyPayloadType;
#ifdef _PREZZYIPC_C_
PrezzyPayloadType payloadTypeMapping[PREZZY_PROPERTY_MAX] = {
[PREZZY_STATE] = PLT_STRING,
[PREZZY_DETAILS] = PLT_STRING,
[PREZZY_LARGE_IMAGE] = PLT_STRING,
[PREZZY_LARGE_TOOLTIP] = PLT_STRING,
[PREZZY_ICON] = PLT_STRING,
[PREZZY_ICON_TOOLTIP] = PLT_STRING,
[PREZZY_PARTY_SIZE] = PLT_INT,
[PREZZY_PARTY_LIMIT] = PLT_INT,
[PREZZY_TIMESTAMP_START] = PLT_INT64,
[PREZZY_TIMESTAMP_END] = PLT_INT64
};
#else
extern PrezzyPayloadType payloadTypeMapping[PREZZY_PROPERTY_MAX];
#endif
typedef struct {
PrezzyProperty property;
PrezzyPayloadType type;
char *pString;
int64_t pInteger;
} PrezzyIpcPacket;
void dumpPacket(PrezzyIpcPacket);
char *fromIpcPacket(PrezzyIpcPacket);
PrezzyIpcPacket *makeIpcPacket(const char *);
int squashLong(long);
size_t strbcpy(char *, const char *, size_t);
char *stringFrom(const char *);
#endif