|
- local ffi = require "ffi"
- local discordRPClib = ffi.load("discord-rpc")
-
- ffi.cdef[[
- 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 void (*readyPtr)(const DiscordUser* request);
- typedef void (*disconnectedPtr)(int errorCode, const char* message);
- typedef void (*erroredPtr)(int errorCode, const char* message);
- typedef void (*joinGamePtr)(const char* joinSecret);
- typedef void (*spectateGamePtr)(const char* spectateSecret);
- typedef void (*joinRequestPtr)(const DiscordUser* request);
-
- typedef struct DiscordEventHandlers {
- readyPtr ready;
- disconnectedPtr disconnected;
- erroredPtr errored;
- joinGamePtr joinGame;
- spectateGamePtr spectateGame;
- joinRequestPtr joinRequest;
- } DiscordEventHandlers;
-
- void Discord_Initialize(const char* applicationId,
- DiscordEventHandlers* handlers,
- int autoRegister,
- const char* optionalSteamId);
-
- void Discord_Shutdown(void);
-
- void Discord_RunCallbacks(void);
-
- void Discord_UpdatePresence(const DiscordRichPresence* presence);
-
- void Discord_ClearPresence(void);
-
- void Discord_Respond(const char* userid, int reply);
-
- void Discord_UpdateHandlers(DiscordEventHandlers* handlers);
- ]]
-
- local discordRPC = {} -- module table
-
- -- proxy to detect garbage collection of the module
- discordRPC.gcDummy = newproxy(true)
-
- local function unpackDiscordUser(request)
- return ffi.string(request.userId), ffi.string(request.username),
- ffi.string(request.discriminator), ffi.string(request.avatar)
- end
-
- -- callback proxies
- -- note: callbacks are not JIT compiled (= SLOW), try to avoid doing performance critical tasks in them
- -- luajit.org/ext_ffi_semantics.html
- local ready_proxy = ffi.cast("readyPtr", function(request)
- if discordRPC.ready then
- discordRPC.ready(unpackDiscordUser(request))
- end
- end)
-
- local disconnected_proxy = ffi.cast("disconnectedPtr", function(errorCode, message)
- if discordRPC.disconnected then
- discordRPC.disconnected(errorCode, ffi.string(message))
- end
- end)
-
- local errored_proxy = ffi.cast("erroredPtr", function(errorCode, message)
- if discordRPC.errored then
- discordRPC.errored(errorCode, ffi.string(message))
- end
- end)
-
- local joinGame_proxy = ffi.cast("joinGamePtr", function(joinSecret)
- if discordRPC.joinGame then
- discordRPC.joinGame(ffi.string(joinSecret))
- end
- end)
-
- local spectateGame_proxy = ffi.cast("spectateGamePtr", function(spectateSecret)
- if discordRPC.spectateGame then
- discordRPC.spectateGame(ffi.string(spectateSecret))
- end
- end)
-
- local joinRequest_proxy = ffi.cast("joinRequestPtr", function(request)
- if discordRPC.joinRequest then
- discordRPC.joinRequest(unpackDiscordUser(request))
- end
- end)
-
- -- helpers
- local function checkArg(arg, argType, argName, func, maybeNil)
- assert(type(arg) == argType or (maybeNil and arg == nil),
- string.format("Argument \"%s\" to function \"%s\" has to be of type \"%s\"",
- argName, func, argType))
- end
-
- local function checkStrArg(arg, maxLen, argName, func, maybeNil)
- if maxLen then
- assert(type(arg) == "string" and arg:len() <= maxLen or (maybeNil and arg == nil),
- string.format("Argument \"%s\" of function \"%s\" has to be of type string with maximum length %d",
- argName, func, maxLen))
- else
- checkArg(arg, "string", argName, func, true)
- end
- end
-
- local function checkIntArg(arg, maxBits, argName, func, maybeNil)
- maxBits = math.min(maxBits or 32, 52) -- lua number (double) can only store integers < 2^53
- local maxVal = 2^(maxBits-1) -- assuming signed integers, which, for now, are the only ones in use
- assert(type(arg) == "number" and math.floor(arg) == arg
- and arg < maxVal and arg >= -maxVal
- or (maybeNil and arg == nil),
- string.format("Argument \"%s\" of function \"%s\" has to be a whole number <= %d",
- argName, func, maxVal))
- end
-
- -- function wrappers
- function discordRPC.initialize(applicationId, autoRegister, optionalSteamId)
- local func = "discordRPC.Initialize"
- checkStrArg(applicationId, nil, "applicationId", func)
- checkArg(autoRegister, "boolean", "autoRegister", func)
- if optionalSteamId ~= nil then
- checkStrArg(optionalSteamId, nil, "optionalSteamId", func)
- end
-
- local eventHandlers = ffi.new("struct DiscordEventHandlers")
- eventHandlers.ready = ready_proxy
- eventHandlers.disconnected = disconnected_proxy
- eventHandlers.errored = errored_proxy
- eventHandlers.joinGame = joinGame_proxy
- eventHandlers.spectateGame = spectateGame_proxy
- eventHandlers.joinRequest = joinRequest_proxy
-
- discordRPClib.Discord_Initialize(applicationId, eventHandlers,
- autoRegister and 1 or 0, optionalSteamId)
- end
-
- function discordRPC.shutdown()
- discordRPClib.Discord_Shutdown()
- end
-
- function discordRPC.runCallbacks()
- discordRPClib.Discord_RunCallbacks()
- end
- -- http://luajit.org/ext_ffi_semantics.html#callback :
- -- It is not allowed, to let an FFI call into a C function (runCallbacks)
- -- get JIT-compiled, which in turn calls a callback, calling into Lua again (e.g. discordRPC.ready).
- -- Usually this attempt is caught by the interpreter first and the C function
- -- is blacklisted for compilation.
- -- solution:
- -- "Then you'll need to manually turn off JIT-compilation with jit.off() for
- -- the surrounding Lua function that invokes such a message polling function."
- jit.off(discordRPC.runCallbacks)
-
- function discordRPC.updatePresence(presence)
- local func = "discordRPC.updatePresence"
- checkArg(presence, "table", "presence", func)
-
- -- -1 for string length because of 0-termination
- checkStrArg(presence.state, 127, "presence.state", func, true)
- checkStrArg(presence.details, 127, "presence.details", func, true)
-
- checkIntArg(presence.startTimestamp, 64, "presence.startTimestamp", func, true)
- checkIntArg(presence.endTimestamp, 64, "presence.endTimestamp", func, true)
-
- checkStrArg(presence.largeImageKey, 31, "presence.largeImageKey", func, true)
- checkStrArg(presence.largeImageText, 127, "presence.largeImageText", func, true)
- checkStrArg(presence.smallImageKey, 31, "presence.smallImageKey", func, true)
- checkStrArg(presence.smallImageText, 127, "presence.smallImageText", func, true)
- checkStrArg(presence.partyId, 127, "presence.partyId", func, true)
-
- checkIntArg(presence.partySize, 32, "presence.partySize", func, true)
- checkIntArg(presence.partyMax, 32, "presence.partyMax", func, true)
-
- checkStrArg(presence.matchSecret, 127, "presence.matchSecret", func, true)
- checkStrArg(presence.joinSecret, 127, "presence.joinSecret", func, true)
- checkStrArg(presence.spectateSecret, 127, "presence.spectateSecret", func, true)
-
- checkIntArg(presence.instance, 8, "presence.instance", func, true)
-
- local cpresence = ffi.new("struct DiscordRichPresence")
- cpresence.state = presence.state
- cpresence.details = presence.details
- cpresence.startTimestamp = presence.startTimestamp or 0
- cpresence.endTimestamp = presence.endTimestamp or 0
- cpresence.largeImageKey = presence.largeImageKey
- cpresence.largeImageText = presence.largeImageText
- cpresence.smallImageKey = presence.smallImageKey
- cpresence.smallImageText = presence.smallImageText
- cpresence.partyId = presence.partyId
- cpresence.partySize = presence.partySize or 0
- cpresence.partyMax = presence.partyMax or 0
- cpresence.matchSecret = presence.matchSecret
- cpresence.joinSecret = presence.joinSecret
- cpresence.spectateSecret = presence.spectateSecret
- cpresence.instance = presence.instance or 0
-
- discordRPClib.Discord_UpdatePresence(cpresence)
- end
-
- function discordRPC.clearPresence()
- discordRPClib.Discord_ClearPresence()
- end
-
- local replyMap = {
- no = 0,
- yes = 1,
- ignore = 2
- }
-
- -- maybe let reply take ints too (0, 1, 2) and add constants to the module
- function discordRPC.respond(userId, reply)
- checkStrArg(userId, nil, "userId", "discordRPC.respond")
- assert(replyMap[reply], "Argument 'reply' to discordRPC.respond has to be one of \"yes\", \"no\" or \"ignore\"")
- discordRPClib.Discord_Respond(userId, replyMap[reply])
- end
-
- -- garbage collection callback
- getmetatable(discordRPC.gcDummy).__gc = function()
- discordRPC.shutdown()
- ready_proxy:free()
- disconnected_proxy:free()
- errored_proxy:free()
- joinGame_proxy:free()
- spectateGame_proxy:free()
- joinRequest_proxy:free()
- end
-
- return discordRPC
|