/* * prezzyd * Copyright (c) 2024 Jeremy Baxter. */ #define _PREZZYD_C_ #define _DEFAULT_SOURCE #ifndef BUILD_DATE #define BUILD_DATE "1970-01-01" #endif #include #include #include #include #include #include #include #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; }