Merge pull request #7732 from fr500/discord

WIP: Discord ask-to-join
This commit is contained in:
Twinaphex 2019-01-05 22:31:21 +01:00 committed by GitHub
commit 90baac517d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 303 additions and 70 deletions

View File

@ -1834,9 +1834,6 @@ void command_playlist_update_write(
**/
bool command_event(enum event_command cmd, void *data)
{
#ifdef HAVE_DISCORD
static bool discord_inited = false;
#endif
bool boolean = false;
switch (cmd)
@ -2986,26 +2983,24 @@ TODO: Add a setting for these tweaks */
if (!settings->bools.discord_enable)
return false;
if (discord_inited)
if (discord_is_ready())
return true;
discord_init();
discord_inited = true;
}
#endif
break;
case CMD_EVENT_DISCORD_DEINIT:
#ifdef HAVE_DISCORD
if (!discord_inited)
if (!discord_is_ready())
return false;
discord_shutdown();
discord_inited = false;
#endif
break;
case CMD_EVENT_DISCORD_UPDATE:
#ifdef HAVE_DISCORD
if (!data || !discord_inited)
if (!data || !discord_is_ready())
return false;
{

View File

@ -129,20 +129,20 @@ public class DiscordRpc
FreeMem();
}
_presence.state = StrToPtr(state, 128);
_presence.details = StrToPtr(details, 128);
_presence.state = StrToPtr(state);
_presence.details = StrToPtr(details);
_presence.startTimestamp = startTimestamp;
_presence.endTimestamp = endTimestamp;
_presence.largeImageKey = StrToPtr(largeImageKey, 32);
_presence.largeImageText = StrToPtr(largeImageText, 128);
_presence.smallImageKey = StrToPtr(smallImageKey, 32);
_presence.smallImageText = StrToPtr(smallImageText, 128);
_presence.partyId = StrToPtr(partyId, 128);
_presence.largeImageKey = StrToPtr(largeImageKey);
_presence.largeImageText = StrToPtr(largeImageText);
_presence.smallImageKey = StrToPtr(smallImageKey);
_presence.smallImageText = StrToPtr(smallImageText);
_presence.partyId = StrToPtr(partyId);
_presence.partySize = partySize;
_presence.partyMax = partyMax;
_presence.matchSecret = StrToPtr(matchSecret, 128);
_presence.joinSecret = StrToPtr(joinSecret, 128);
_presence.spectateSecret = StrToPtr(spectateSecret, 128);
_presence.matchSecret = StrToPtr(matchSecret);
_presence.joinSecret = StrToPtr(joinSecret);
_presence.spectateSecret = StrToPtr(spectateSecret);
_presence.instance = instance;
return _presence;
@ -152,16 +152,18 @@ public class DiscordRpc
/// Returns a pointer to a representation of the given string with a size of maxbytes
/// </summary>
/// <param name="input">String to convert</param>
/// <param name="maxbytes">Max number of bytes to use</param>
/// <returns>Pointer to the UTF-8 representation of <see cref="input"/></returns>
private IntPtr StrToPtr(string input, int maxbytes)
private IntPtr StrToPtr(string input)
{
if (string.IsNullOrEmpty(input)) return IntPtr.Zero;
var convstr = StrClampBytes(input, maxbytes);
var convbytecnt = Encoding.UTF8.GetByteCount(convstr);
var buffer = Marshal.AllocHGlobal(convbytecnt);
var convbytecnt = Encoding.UTF8.GetByteCount(input);
var buffer = Marshal.AllocHGlobal(convbytecnt + 1);
for (int i = 0; i < convbytecnt + 1; i++)
{
Marshal.WriteByte(buffer, i , 0);
}
_buffers.Add(buffer);
Marshal.Copy(Encoding.UTF8.GetBytes(convstr), 0, buffer, convbytecnt);
Marshal.Copy(Encoding.UTF8.GetBytes(input), 0, buffer, convbytecnt);
return buffer;
}
@ -181,30 +183,6 @@ public class DiscordRpc
return Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(str));
}
/// <summary>
/// Clamp the string to the given byte length preserving null termination
/// </summary>
/// <param name="toclamp">string to clamp</param>
/// <param name="maxbytes">max bytes the resulting string should have (including null termination)</param>
/// <returns>null terminated string with a byte length less or equal to <see cref="maxbytes"/></returns>
private static string StrClampBytes(string toclamp, int maxbytes)
{
var str = StrToUtf8NullTerm(toclamp);
var strbytes = Encoding.UTF8.GetBytes(str);
if (strbytes.Length <= maxbytes)
{
return str;
}
var newstrbytes = new byte[] { };
Array.Copy(strbytes, 0, newstrbytes, 0, maxbytes - 1);
newstrbytes[newstrbytes.Length - 1] = 0;
newstrbytes[newstrbytes.Length - 2] = 0;
return Encoding.UTF8.GetString(newstrbytes);
}
/// <summary>
/// Free the allocated memory for conversion to <see cref="RichPresenceStruct"/>
/// </summary>

View File

@ -24,6 +24,16 @@ struct FDiscordUserData {
FString avatar;
};
/**
* Valid response codes for Respond function
*/
UENUM(BlueprintType)
enum class EDiscordJoinResponseCodes : uint8
{
DISCORD_REPLY_NO UMETA(DisplayName="No"),
DISCORD_REPLY_YES UMETA(DisplayName="Yes"),
DISCORD_REPLY_IGNORE UMETA(DisplayName="Ignore")
};
DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All);

View File

@ -33,9 +33,11 @@ extern "C" DISCORD_EXPORT void Discord_Register(const char* applicationId, const
char exePath[1024];
if (!command || !command[0]) {
if (readlink("/proc/self/exe", exePath, sizeof(exePath)) <= 0) {
ssize_t size = readlink("/proc/self/exe", exePath, sizeof(exePath));
if (size <= 0 || size >= (ssize_t)sizeof(exePath)) {
return;
}
exePath[size] = '\0';
command = exePath;
}

View File

@ -89,10 +89,11 @@ public:
keepRunning.store(true);
ioThread = std::thread([&]() {
const std::chrono::duration<int64_t, std::milli> maxWait{500LL};
Discord_UpdateConnection();
while (keepRunning.load()) {
Discord_UpdateConnection();
std::unique_lock<std::mutex> lock(waitForIOMutex);
waitForIOActivity.wait_for(lock, maxWait);
Discord_UpdateConnection();
}
});
}

View File

@ -37,25 +37,104 @@
#include "../cheevos/cheevos.h"
#endif
static int FrustrationLevel = 0;
#ifdef HAVE_MENU
#include "../../menu/widgets/menu_input_dialog.h"
#include "../../menu/menu_cbs.h"
#endif
#include <net/net_http.h>
#include "../network/net_http_special.h"
#include "../tasks/tasks_internal.h"
#include <streams/file_stream.h>
#include <file/file_path.h>
#include "../file_path_special.h"
static int64_t start_time = 0;
static int64_t pause_time = 0;
static int64_t ellapsed_time = 0;
static bool discord_ready = false;
static bool discord_avatar_ready = false;
static unsigned discord_status = 0;
struct netplay_room *room;
static char user_id[128];
static char user_name[128];
static char user_avatar[PATH_MAX_LENGTH];
static char cdn_url[] = "https://cdn.discordapp.com/avatars";
DiscordRichPresence discord_presence;
char* discord_get_own_username(void)
{
return user_name;
}
char* discord_get_own_avatar(void)
{
return user_avatar;
}
bool discord_avatar_is_ready()
{
return discord_avatar_ready;
}
void discord_avatar_set_ready(bool ready)
{
discord_avatar_ready = ready;
}
bool discord_is_ready()
{
return discord_ready;
}
static bool discord_download_avatar(const char* user_id, const char* avatar_id)
{
static char url[PATH_MAX_LENGTH];
static char url_encoded[PATH_MAX_LENGTH];
static char full_path[PATH_MAX_LENGTH];
static char buf[PATH_MAX_LENGTH];
file_transfer_t *transf = NULL;
fill_pathname_application_special(buf,
sizeof(buf),
APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS);
fill_pathname_join(full_path, buf, avatar_id, sizeof(full_path));
strlcpy(user_avatar, avatar_id, sizeof(user_avatar));
if(filestream_exists(full_path))
return true;
else
{
snprintf(url, sizeof(url), "%s/%s/%s.png", cdn_url, user_id, avatar_id);
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
snprintf(buf, sizeof(buf), "%s.png", avatar_id);
transf = (file_transfer_t*)calloc(1, sizeof(*transf));
transf->enum_idx = MENU_ENUM_LABEL_CB_DISCORD_AVATAR;
strlcpy(transf->path, buf, sizeof(transf->path));
RARCH_LOG("[Discord] downloading avatar from: %s\n", url_encoded);
task_push_http_transfer(url_encoded, true, NULL, cb_generic_download, transf);
return false;
}
}
static void handle_discord_ready(const DiscordUser* connectedUser)
{
strlcpy(user_name, connectedUser->username, sizeof(user_name));
RARCH_LOG("[Discord] connected to user: %s#%s - avatar id: %s\n",
connectedUser->username,
connectedUser->discriminator,
connectedUser->userId);
discord_download_avatar(connectedUser->userId, connectedUser->avatar);
}
static void handle_discord_disconnected(int errcode, const char* message)
@ -93,14 +172,55 @@ static void handle_discord_spectate(const char* secret)
RARCH_LOG("[Discord] spectate (%s)\n", secret);
}
static void handle_discord_join_response(void *ignore, const char *line)
{
/* To-Do: needs in-game widgets
if (strstr(line, "yes"))
Discord_Respond(user_id, DISCORD_REPLY_YES);
#ifdef HAVE_MENU
menu_input_dialog_end();
rarch_menu_running_finished();
#endif
*/
}
static void handle_discord_join_request(const DiscordUser* request)
{
int response = -1;
char yn[4];
RARCH_LOG("[Discord] join request from %s#%s - %s\n",
static char url[PATH_MAX_LENGTH];
static char url_encoded[PATH_MAX_LENGTH];
static char filename[PATH_MAX_LENGTH];
char buf[PATH_MAX_LENGTH];
RARCH_LOG("[Discord] join request from %s#%s - %s %s\n",
request->username,
request->discriminator,
request->userId);
request->userId,
request->avatar);
discord_download_avatar(request->userId, request->avatar);
#ifdef HAVE_MENU
menu_input_ctx_line_t line;
/* To-Do: needs in-game widgets
rarch_menu_running();
*/
memset(&line, 0, sizeof(line));
snprintf(buf, sizeof(buf), "%s %s?", msg_hash_to_str(MSG_DISCORD_CONNECTION_REQUEST), request->username);
line.label = buf;
line.label_setting = "no_setting";
line.cb = handle_discord_join_response;
/* To-Do: needs in-game widgets
To-Do: bespoke dialog, should show while in-game and have a hotkey to accept
To-Do: show avatar of the user connecting
if (!menu_input_dialog_start(&line))
return;
*/
#endif
}
void discord_update(enum discord_presence presence)
@ -192,10 +312,11 @@ void discord_update(enum discord_presence presence)
room->gamename, room->gamecrc, room->corename);
RARCH_LOG("%s\n", join_secret);
discord_presence.joinSecret = strdup(join_secret);
discord_presence.spectateSecret = "SPECSPECSPEC";
discord_presence.partyId = party_id;
/* discord_presence.spectateSecret = "SPECSPECSPEC"; */
discord_presence.partyId = strdup(party_id);
discord_presence.partyMax = 0;
discord_presence.partySize = 0;
RARCH_LOG("[Discord] joining: \n Secret: %s\n Party: %s\n", discord_presence.joinSecret, discord_presence.partyId);
break;
case DISCORD_PRESENCE_NETPLAY_HOSTING_STOPPED:
case DISCORD_PRESENCE_NETPLAY_CLIENT:
@ -226,13 +347,14 @@ void discord_init(void)
handlers.spectateGame = handle_discord_spectate;
handlers.joinRequest = handle_discord_join_request;
/* To-Do: Add the arguments RetroArch was started with to the register URI*/
const char command[256] = "retroarch";
Discord_Initialize(settings->arrays.discord_app_id, &handlers, 0, NULL);
Discord_Register(settings->arrays.discord_app_id, NULL);
discord_ready = true;
char command[PATH_MAX_LENGTH];
strlcpy(command, get_retroarch_launch_arguments(), sizeof(command));
RARCH_LOG("[Discord] registering startup command: %s\n", command);
Discord_Register(settings->arrays.discord_app_id, command);
discord_ready = true;
}
void discord_shutdown(void)

View File

@ -55,4 +55,14 @@ void discord_update(enum discord_presence presence);
void discord_run_callbacks();
bool discord_is_ready();
void discord_avatar_set_ready(bool ready);
bool discord_avatar_is_ready();
char* discord_get_own_username(void);
char* discord_get_own_avatar(void);
#endif /* __RARCH_DISCORD_H */

View File

@ -338,6 +338,31 @@ void fill_pathname_application_special(char *s,
}
#endif
break;
case APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS:
{
char *s1 = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
char *s2 = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));
settings_t *settings = config_get_ptr();
s1[0] = s2[0] = '\0';
fill_pathname_join(s1,
settings->paths.directory_thumbnails,
"discord",
len);
fill_pathname_join(s2,
s1, "avatars",
PATH_MAX_LENGTH * sizeof(char)
);
fill_pathname_slash(s2,
PATH_MAX_LENGTH * sizeof(char)
);
strlcpy(s, s2, len);
free(s1);
free(s2);
}
break;
case APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES:
{
char *s1 = (char*)malloc(PATH_MAX_LENGTH * sizeof(char));

View File

@ -111,7 +111,8 @@ enum application_special_type
APPLICATION_SPECIAL_DIRECTORY_ASSETS_ZARCH,
APPLICATION_SPECIAL_DIRECTORY_ASSETS_ZARCH_FONT,
APPLICATION_SPECIAL_DIRECTORY_ASSETS_ZARCH_ICONS,
APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES
APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_CHEEVOS_BADGES,
APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS
};
/**

View File

@ -133,6 +133,10 @@ MSG_HASH(
MSG_NETPLAY_ENTER_PASSWORD,
"Enter netplay server password:"
)
MSG_HASH(
MSG_DISCORD_CONNECTION_REQUEST,
"Do you want to allow connection from user:"
)
MSG_HASH(
MSG_NETPLAY_INCORRECT_PASSWORD,
"Incorrect password"

View File

@ -26,6 +26,10 @@
#include "../../config.h"
#endif
#ifdef HAVE_DISCORD
#include "../../discord/discord.h"
#endif
#include "../../config.def.h"
#include "../../config.def.keybinds.h"
#include "../../wifi/wifi_driver.h"
@ -3343,7 +3347,7 @@ static void cb_generic_dir_download(void *task_data,
}
/* expects http_transfer_t*, file_transfer_t* */
static void cb_generic_download(void *task_data,
void cb_generic_download(void *task_data,
void *user_data, const char *err)
{
char output_path[PATH_MAX_LENGTH];
@ -3359,6 +3363,7 @@ static void cb_generic_download(void *task_data,
goto finish;
output_path[0] = '\0';
char buf[PATH_MAX_LENGTH];;
/* we have to determine dir_path at the time of writting or else
* we'd run into races when the user changes the setting during an
@ -3423,6 +3428,14 @@ static void cb_generic_download(void *task_data,
case MENU_ENUM_LABEL_CB_LAKKA_DOWNLOAD:
dir_path = LAKKA_UPDATE_DIR;
break;
case MENU_ENUM_LABEL_CB_DISCORD_AVATAR:
{
fill_pathname_application_special(buf,
PATH_MAX_LENGTH * sizeof(char),
APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS);
dir_path = buf;
break;
}
default:
RARCH_WARN("Unknown transfer type '%s' bailing out.\n",
msg_hash_to_str(transf->enum_idx));
@ -3495,6 +3508,10 @@ finish:
RARCH_ERR("Download of '%s' failed: %s\n",
(transf ? transf->path: "unknown"), err);
}
#ifdef HAVE_DISCORD
else if (transf->enum_idx == MENU_ENUM_LABEL_CB_DISCORD_AVATAR)
discord_avatar_set_ready(true);
#endif
if (data)
{

View File

@ -23,6 +23,10 @@
#include "ozone_texture.h"
#include "ozone_sidebar.h"
#ifdef HAVE_DISCORD
#include "discord/discord.h"
#endif
#include <file/file_path.h>
#include <string/stdstring.h>
#include <encodings/utf.h>
@ -348,14 +352,37 @@ static void ozone_context_reset(void *data, bool is_threaded)
for (i = 0; i < OZONE_TEXTURE_LAST; i++)
{
char filename[PATH_MAX_LENGTH];
strlcpy(filename, OZONE_TEXTURES_FILES[i], sizeof(filename));
#ifdef HAVE_DISCORD
if (i == OZONE_TEXTURE_DISCORD_OWN_AVATAR)
strlcpy(filename, discord_get_own_avatar(), sizeof(filename));
else
#endif
strlcpy(filename, OZONE_TEXTURES_FILES[i], sizeof(filename));
strlcat(filename, ".png", sizeof(filename));
if (!menu_display_reset_textures_list(filename, ozone->png_path, &ozone->textures[i], TEXTURE_FILTER_MIPMAP_LINEAR))
#ifdef HAVE_DISCORD
if (i == OZONE_TEXTURE_DISCORD_OWN_AVATAR && discord_avatar_is_ready())
{
ozone->has_all_assets = false;
RARCH_WARN("[OZONE] Asset missing: %s%s%s\n", ozone->png_path, path_default_slash(), filename);
char buf[PATH_MAX_LENGTH];
fill_pathname_application_special(buf,
sizeof(buf),
APPLICATION_SPECIAL_DIRECTORY_THUMBNAILS_DISCORD_AVATARS);
if (!menu_display_reset_textures_list(filename, buf, &ozone->textures[i], TEXTURE_FILTER_MIPMAP_LINEAR))
RARCH_WARN("[OZONE] Asset missing: %s%s%s\n", ozone->png_path, path_default_slash(), filename);
}
else
{
#endif
if (!menu_display_reset_textures_list(filename, ozone->png_path, &ozone->textures[i], TEXTURE_FILTER_MIPMAP_LINEAR))
{
ozone->has_all_assets = false;
RARCH_WARN("[OZONE] Asset missing: %s%s%s\n", ozone->png_path, path_default_slash(), filename);
}
#ifdef HAVE_DISCORD
}
#endif
}
/* Sidebar textures */
@ -930,7 +957,12 @@ static void ozone_draw_header(ozone_handle_t *ozone, video_frame_info_t *video_i
/* Icon */
menu_display_blend_begin(video_info);
ozone_draw_icon(video_info, 60, 60, ozone->textures[OZONE_TEXTURE_RETROARCH], 47, 14, video_info->width, video_info->height, 0, 1, ozone->theme->entries_icon);
#ifdef HAVE_DISCORD
if (discord_avatar_is_ready())
ozone_draw_icon(video_info, 60, 60, ozone->textures[OZONE_TEXTURE_DISCORD_OWN_AVATAR], 47, 14, video_info->width, video_info->height, 0, 1, ozone->theme->entries_icon);
else
#endif
ozone_draw_icon(video_info, 60, 60, ozone->textures[OZONE_TEXTURE_RETROARCH], 47, 14, video_info->width, video_info->height, 0, 1, ozone->theme->entries_icon);
menu_display_blend_end(video_info);
/* Battery */
@ -1117,6 +1149,16 @@ static void ozone_frame(void *data, video_frame_info_t *video_info)
bool draw_osk = menu_input_dialog_get_display_kb();
static bool draw_osk_old = false;
#ifdef HAVE_DISCORD
static bool reset = false;
if (discord_avatar_is_ready() && !reset)
{
ozone_context_reset(data, false);
reset = true;
}
#endif
menu_animation_ctx_entry_t entry;
if (!ozone)

View File

@ -28,7 +28,9 @@
enum OZONE_TEXTURE {
OZONE_TEXTURE_RETROARCH = 0,
OZONE_TEXTURE_CURSOR_BORDER,
#ifdef HAVE_DISCORD
OZONE_TEXTURE_DISCORD_OWN_AVATAR,
#endif
OZONE_TEXTURE_LAST
};

View File

@ -286,6 +286,10 @@ void menu_cbs_init(void *data,
int menu_cbs_exit(void);
void cb_generic_download(void *task_data,
void *user_data, const char *err);
RETRO_END_DECLS
#endif

View File

@ -189,6 +189,7 @@ enum msg_hash_enums
MSG_RESAMPLER_QUALITY_NORMAL,
MSG_RESAMPLER_QUALITY_HIGHER,
MSG_RESAMPLER_QUALITY_HIGHEST,
MSG_DISCORD_CONNECTION_REQUEST,
MSG_ADDED_TO_FAVORITES,
MSG_RESET_CORE_ASSOCIATION,
MSG_CORE_ASSOCIATION_RESET,
@ -1933,6 +1934,7 @@ enum msg_hash_enums
MENU_ENUM_LABEL_CB_UPDATE_SHADERS_CG,
MENU_ENUM_LABEL_CB_UPDATE_SHADERS_GLSL,
MENU_ENUM_LABEL_CB_UPDATE_SHADERS_SLANG,
MENU_ENUM_LABEL_CB_DISCORD_AVATAR,
/* Sublabels */
MENU_ENUM_SUBLABEL_MIXER_ACTION_PLAY,

View File

@ -279,6 +279,8 @@ static retro_time_t frame_limit_last_time = 0.0;
extern bool input_driver_flushing_input;
static char launch_arguments[4096];
#ifdef HAVE_DYNAMIC
bool retroarch_core_set_on_cmdline(void)
{
@ -640,6 +642,15 @@ static void retroarch_parse_input_and_config(int argc, char *argv[])
bool explicit_menu = false;
global_t *global = global_get_ptr();
/* Nasty hack to copy the args into a global so discord can register the same arguments for launch */
char buf[4096];
for (unsigned i = 0; i < argc; i++)
{
snprintf(buf, sizeof(buf), "%s %s", launch_arguments, argv[i]);
strlcpy(launch_arguments, buf, sizeof(launch_arguments));
}
string_trim_whitespace_left(launch_arguments);
const struct option opts[] = {
#ifdef HAVE_DYNAMIC
{ "libretro", 1, NULL, 'L' },
@ -3667,3 +3678,8 @@ struct retro_system_info *runloop_get_libretro_system_info(void)
struct retro_system_info *system = &runloop_system.info;
return system;
}
char *get_retroarch_launch_arguments(void)
{
return launch_arguments;
}

View File

@ -391,6 +391,8 @@ void rarch_menu_running_finished(void);
bool retroarch_is_on_main_thread(void);
char *get_retroarch_launch_arguments(void);
rarch_system_info_t *runloop_get_system_info(void);
struct retro_system_info *runloop_get_libretro_system_info(void);