Browse Source
Merge pull request #1332 from FearlessTobi/port-web-backend
Merge pull request #1332 from FearlessTobi/port-web-backend
Port web_service from Citrance_cpp
committed by
GitHub
55 changed files with 21637 additions and 40 deletions
-
6.gitmodules
-
4.travis/common/travis-ci.env
-
2.travis/linux/build.sh
-
2.travis/linux/docker.sh
-
2.travis/macos/build.sh
-
4CMakeLists.txt
-
5appveyor.yml
-
25externals/CMakeLists.txt
-
1externals/discord-rpc
-
15externals/httplib/README.md
-
2344externals/httplib/httplib.h
-
9externals/json/README.md
-
17300externals/json/json.hpp
-
1externals/libressl
-
8externals/lurlparser/CMakeLists.txt
-
265externals/lurlparser/LUrlParser.cpp
-
78externals/lurlparser/LUrlParser.h
-
19externals/lurlparser/README.md
-
0git
-
3src/CMakeLists.txt
-
3src/common/CMakeLists.txt
-
41src/common/detached_tasks.cpp
-
40src/common/detached_tasks.h
-
24src/common/web_result.h
-
4src/core/CMakeLists.txt
-
6src/core/settings.h
-
52src/core/telemetry_session.cpp
-
5src/core/telemetry_session.h
-
16src/web_service/CMakeLists.txt
-
99src/web_service/telemetry_json.cpp
-
58src/web_service/telemetry_json.h
-
27src/web_service/verify_login.cpp
-
22src/web_service/verify_login.h
-
149src/web_service/web_backend.cpp
-
92src/web_service/web_backend.h
-
20src/yuzu/CMakeLists.txt
-
65src/yuzu/compatdb.cpp
-
26src/yuzu/compatdb.h
-
215src/yuzu/compatdb.ui
-
18src/yuzu/configuration/config.cpp
-
11src/yuzu/configuration/configure.ui
-
1src/yuzu/configuration/configure_dialog.cpp
-
119src/yuzu/configuration/configure_web.cpp
-
38src/yuzu/configuration/configure_web.h
-
206src/yuzu/configuration/configure_web.ui
-
25src/yuzu/discord.h
-
52src/yuzu/discord_impl.cpp
-
20src/yuzu/discord_impl.h
-
82src/yuzu/main.cpp
-
10src/yuzu/main.h
-
16src/yuzu/main.ui
-
3src/yuzu/ui_settings.h
-
8src/yuzu_cmd/config.cpp
-
8src/yuzu_cmd/default_ini.h
-
3src/yuzu_cmd/yuzu.cpp
@ -1,4 +1,4 @@ |
|||||
#!/bin/bash -ex |
#!/bin/bash -ex |
||||
|
|
||||
mkdir -p "$HOME/.ccache" |
mkdir -p "$HOME/.ccache" |
||||
docker run --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh |
|
||||
|
docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh |
||||
@ -0,0 +1,15 @@ |
|||||
|
From https://github.com/yhirose/cpp-httplib/commit/d9479bc0b12e8a1e8bce2d34da4feeef488581f3 |
||||
|
|
||||
|
MIT License |
||||
|
|
||||
|
=== |
||||
|
|
||||
|
cpp-httplib |
||||
|
|
||||
|
A C++11 header-only HTTP library. |
||||
|
|
||||
|
It's extremely easy to setup. Just include httplib.h file in your code! |
||||
|
|
||||
|
Inspired by Sinatra and express. |
||||
|
|
||||
|
© 2017 Yuji Hirose |
||||
2344
externals/httplib/httplib.h
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,9 @@ |
|||||
|
JSON for Modern C++ |
||||
|
=================== |
||||
|
|
||||
|
v3.1.2 |
||||
|
|
||||
|
This is a mirror providing the single required header file. |
||||
|
|
||||
|
The original repository can be found at: |
||||
|
https://github.com/nlohmann/json/commit/d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe |
||||
17300
externals/json/json.hpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,8 @@ |
|||||
|
add_library(lurlparser |
||||
|
LUrlParser.cpp |
||||
|
LUrlParser.h |
||||
|
) |
||||
|
|
||||
|
create_target_directory_groups(lurlparser) |
||||
|
|
||||
|
target_include_directories(lurlparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) |
||||
@ -0,0 +1,265 @@ |
|||||
|
/*
|
||||
|
* Lightweight URL & URI parser (RFC 1738, RFC 3986) |
||||
|
* https://github.com/corporateshark/LUrlParser
|
||||
|
* |
||||
|
* The MIT License (MIT) |
||||
|
* |
||||
|
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) |
||||
|
* |
||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||
|
* in the Software without restriction, including without limitation the rights |
||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||
|
* furnished to do so, subject to the following conditions: |
||||
|
* |
||||
|
* The above copyright notice and this permission notice shall be included in all |
||||
|
* copies or substantial portions of the Software. |
||||
|
* |
||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
* SOFTWARE. |
||||
|
*/ |
||||
|
|
||||
|
#include "LUrlParser.h"
|
||||
|
|
||||
|
#include <algorithm>
|
||||
|
#include <cstring>
|
||||
|
#include <stdlib.h>
|
||||
|
|
||||
|
// check if the scheme name is valid
|
||||
|
static bool IsSchemeValid( const std::string& SchemeName ) |
||||
|
{ |
||||
|
for ( auto c : SchemeName ) |
||||
|
{ |
||||
|
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const |
||||
|
{ |
||||
|
if ( !IsValid() ) { return false; } |
||||
|
|
||||
|
int Port = atoi( m_Port.c_str() ); |
||||
|
|
||||
|
if ( Port <= 0 || Port > 65535 ) { return false; } |
||||
|
|
||||
|
if ( OutPort ) { *OutPort = Port; } |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
// based on RFC 1738 and RFC 3986
|
||||
|
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL ) |
||||
|
{ |
||||
|
LUrlParser::clParseURL Result; |
||||
|
|
||||
|
const char* CurrentString = URL.c_str(); |
||||
|
|
||||
|
/*
|
||||
|
* <scheme>:<scheme-specific-part> |
||||
|
* <scheme> := [a-z\+\-\.]+ |
||||
|
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names |
||||
|
*/ |
||||
|
|
||||
|
// try to read scheme
|
||||
|
{ |
||||
|
const char* LocalString = strchr( CurrentString, ':' ); |
||||
|
|
||||
|
if ( !LocalString ) |
||||
|
{ |
||||
|
return clParseURL( LUrlParserError_NoUrlCharacter ); |
||||
|
} |
||||
|
|
||||
|
// save the scheme name
|
||||
|
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString ); |
||||
|
|
||||
|
if ( !IsSchemeValid( Result.m_Scheme ) ) |
||||
|
{ |
||||
|
return clParseURL( LUrlParserError_InvalidSchemeName ); |
||||
|
} |
||||
|
|
||||
|
// scheme should be lowercase
|
||||
|
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower ); |
||||
|
|
||||
|
// skip ':'
|
||||
|
CurrentString = LocalString+1; |
||||
|
} |
||||
|
|
||||
|
/*
|
||||
|
* //<user>:<password>@<host>:<port>/<url-path>
|
||||
|
* any ":", "@" and "/" must be normalized |
||||
|
*/ |
||||
|
|
||||
|
// skip "//"
|
||||
|
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash ); |
||||
|
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash ); |
||||
|
|
||||
|
// check if the user name and password are specified
|
||||
|
bool bHasUserName = false; |
||||
|
|
||||
|
const char* LocalString = CurrentString; |
||||
|
|
||||
|
while ( *LocalString ) |
||||
|
{ |
||||
|
if ( *LocalString == '@' ) |
||||
|
{ |
||||
|
// user name and password are specified
|
||||
|
bHasUserName = true; |
||||
|
break; |
||||
|
} |
||||
|
else if ( *LocalString == '/' ) |
||||
|
{ |
||||
|
// end of <host>:<port> specification
|
||||
|
bHasUserName = false; |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
LocalString++; |
||||
|
} |
||||
|
|
||||
|
// user name and password
|
||||
|
LocalString = CurrentString; |
||||
|
|
||||
|
if ( bHasUserName ) |
||||
|
{ |
||||
|
// read user name
|
||||
|
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++; |
||||
|
|
||||
|
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString ); |
||||
|
|
||||
|
// proceed with the current pointer
|
||||
|
CurrentString = LocalString; |
||||
|
|
||||
|
if ( *CurrentString == ':' ) |
||||
|
{ |
||||
|
// skip ':'
|
||||
|
CurrentString++; |
||||
|
|
||||
|
// read password
|
||||
|
LocalString = CurrentString; |
||||
|
|
||||
|
while ( *LocalString && *LocalString != '@' ) LocalString++; |
||||
|
|
||||
|
Result.m_Password = std::string( CurrentString, LocalString - CurrentString ); |
||||
|
|
||||
|
CurrentString = LocalString; |
||||
|
} |
||||
|
|
||||
|
// skip '@'
|
||||
|
if ( *CurrentString != '@' ) |
||||
|
{ |
||||
|
return clParseURL( LUrlParserError_NoAtSign ); |
||||
|
} |
||||
|
|
||||
|
CurrentString++; |
||||
|
} |
||||
|
|
||||
|
bool bHasBracket = ( *CurrentString == '[' ); |
||||
|
|
||||
|
// go ahead, read the host name
|
||||
|
LocalString = CurrentString; |
||||
|
|
||||
|
while ( *LocalString ) |
||||
|
{ |
||||
|
if ( bHasBracket && *LocalString == ']' ) |
||||
|
{ |
||||
|
// end of IPv6 address
|
||||
|
LocalString++; |
||||
|
break; |
||||
|
} |
||||
|
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) ) |
||||
|
{ |
||||
|
// port number is specified
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
LocalString++; |
||||
|
} |
||||
|
|
||||
|
Result.m_Host = std::string( CurrentString, LocalString - CurrentString ); |
||||
|
|
||||
|
CurrentString = LocalString; |
||||
|
|
||||
|
// is port number specified?
|
||||
|
if ( *CurrentString == ':' ) |
||||
|
{ |
||||
|
CurrentString++; |
||||
|
|
||||
|
// read port number
|
||||
|
LocalString = CurrentString; |
||||
|
|
||||
|
while ( *LocalString && *LocalString != '/' ) LocalString++; |
||||
|
|
||||
|
Result.m_Port = std::string( CurrentString, LocalString - CurrentString ); |
||||
|
|
||||
|
CurrentString = LocalString; |
||||
|
} |
||||
|
|
||||
|
// end of string
|
||||
|
if ( !*CurrentString ) |
||||
|
{ |
||||
|
Result.m_ErrorCode = LUrlParserError_Ok; |
||||
|
|
||||
|
return Result; |
||||
|
} |
||||
|
|
||||
|
// skip '/'
|
||||
|
if ( *CurrentString != '/' ) |
||||
|
{ |
||||
|
return clParseURL( LUrlParserError_NoSlash ); |
||||
|
} |
||||
|
|
||||
|
CurrentString++; |
||||
|
|
||||
|
// parse the path
|
||||
|
LocalString = CurrentString; |
||||
|
|
||||
|
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++; |
||||
|
|
||||
|
Result.m_Path = std::string( CurrentString, LocalString - CurrentString ); |
||||
|
|
||||
|
CurrentString = LocalString; |
||||
|
|
||||
|
// check for query
|
||||
|
if ( *CurrentString == '?' ) |
||||
|
{ |
||||
|
// skip '?'
|
||||
|
CurrentString++; |
||||
|
|
||||
|
// read query
|
||||
|
LocalString = CurrentString; |
||||
|
|
||||
|
while ( *LocalString && *LocalString != '#' ) LocalString++; |
||||
|
|
||||
|
Result.m_Query = std::string( CurrentString, LocalString - CurrentString ); |
||||
|
|
||||
|
CurrentString = LocalString; |
||||
|
} |
||||
|
|
||||
|
// check for fragment
|
||||
|
if ( *CurrentString == '#' ) |
||||
|
{ |
||||
|
// skip '#'
|
||||
|
CurrentString++; |
||||
|
|
||||
|
// read fragment
|
||||
|
LocalString = CurrentString; |
||||
|
|
||||
|
while ( *LocalString ) LocalString++; |
||||
|
|
||||
|
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString ); |
||||
|
|
||||
|
CurrentString = LocalString; |
||||
|
} |
||||
|
|
||||
|
Result.m_ErrorCode = LUrlParserError_Ok; |
||||
|
|
||||
|
return Result; |
||||
|
} |
||||
@ -0,0 +1,78 @@ |
|||||
|
/* |
||||
|
* Lightweight URL & URI parser (RFC 1738, RFC 3986) |
||||
|
* https://github.com/corporateshark/LUrlParser |
||||
|
* |
||||
|
* The MIT License (MIT) |
||||
|
* |
||||
|
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com) |
||||
|
* |
||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
|
* of this software and associated documentation files (the "Software"), to deal |
||||
|
* in the Software without restriction, including without limitation the rights |
||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
|
* copies of the Software, and to permit persons to whom the Software is |
||||
|
* furnished to do so, subject to the following conditions: |
||||
|
* |
||||
|
* The above copyright notice and this permission notice shall be included in all |
||||
|
* copies or substantial portions of the Software. |
||||
|
* |
||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
|
* SOFTWARE. |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
namespace LUrlParser |
||||
|
{ |
||||
|
enum LUrlParserError |
||||
|
{ |
||||
|
LUrlParserError_Ok = 0, |
||||
|
LUrlParserError_Uninitialized = 1, |
||||
|
LUrlParserError_NoUrlCharacter = 2, |
||||
|
LUrlParserError_InvalidSchemeName = 3, |
||||
|
LUrlParserError_NoDoubleSlash = 4, |
||||
|
LUrlParserError_NoAtSign = 5, |
||||
|
LUrlParserError_UnexpectedEndOfLine = 6, |
||||
|
LUrlParserError_NoSlash = 7, |
||||
|
}; |
||||
|
|
||||
|
class clParseURL |
||||
|
{ |
||||
|
public: |
||||
|
LUrlParserError m_ErrorCode; |
||||
|
std::string m_Scheme; |
||||
|
std::string m_Host; |
||||
|
std::string m_Port; |
||||
|
std::string m_Path; |
||||
|
std::string m_Query; |
||||
|
std::string m_Fragment; |
||||
|
std::string m_UserName; |
||||
|
std::string m_Password; |
||||
|
|
||||
|
clParseURL() |
||||
|
: m_ErrorCode( LUrlParserError_Uninitialized ) |
||||
|
{} |
||||
|
|
||||
|
/// return 'true' if the parsing was successful |
||||
|
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; } |
||||
|
|
||||
|
/// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range) |
||||
|
bool GetPort( int* OutPort ) const; |
||||
|
|
||||
|
/// parse the URL |
||||
|
static clParseURL ParseURL( const std::string& URL ); |
||||
|
|
||||
|
private: |
||||
|
explicit clParseURL( LUrlParserError ErrorCode ) |
||||
|
: m_ErrorCode( ErrorCode ) |
||||
|
{} |
||||
|
}; |
||||
|
|
||||
|
} // namespace LUrlParser |
||||
@ -0,0 +1,19 @@ |
|||||
|
From https://github.com/corporateshark/LUrlParser/commit/455d5e2d27e3946f11ad0328fee9ee2628e6a8e2 |
||||
|
|
||||
|
MIT License |
||||
|
|
||||
|
=== |
||||
|
|
||||
|
Lightweight URL & URI parser (RFC 1738, RFC 3986) |
||||
|
|
||||
|
(C) Sergey Kosarevsky, 2015 |
||||
|
|
||||
|
@corporateshark sk@linderdaum.com |
||||
|
|
||||
|
http://www.linderdaum.com |
||||
|
|
||||
|
http://blog.linderdaum.com |
||||
|
|
||||
|
============================= |
||||
|
|
||||
|
A tiny and lightweight URL & URI parser (RFC 1738, RFC 3986) written in C++. |
||||
@ -0,0 +1,41 @@ |
|||||
|
// Copyright 2018 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <thread>
|
||||
|
#include "common/assert.h"
|
||||
|
#include "common/detached_tasks.h"
|
||||
|
|
||||
|
namespace Common { |
||||
|
|
||||
|
DetachedTasks* DetachedTasks::instance = nullptr; |
||||
|
|
||||
|
DetachedTasks::DetachedTasks() { |
||||
|
ASSERT(instance == nullptr); |
||||
|
instance = this; |
||||
|
} |
||||
|
|
||||
|
void DetachedTasks::WaitForAllTasks() { |
||||
|
std::unique_lock<std::mutex> lock(mutex); |
||||
|
cv.wait(lock, [this]() { return count == 0; }); |
||||
|
} |
||||
|
|
||||
|
DetachedTasks::~DetachedTasks() { |
||||
|
std::unique_lock<std::mutex> lock(mutex); |
||||
|
ASSERT(count == 0); |
||||
|
instance = nullptr; |
||||
|
} |
||||
|
|
||||
|
void DetachedTasks::AddTask(std::function<void()> task) { |
||||
|
std::unique_lock<std::mutex> lock(instance->mutex); |
||||
|
++instance->count; |
||||
|
std::thread([task{std::move(task)}]() { |
||||
|
task(); |
||||
|
std::unique_lock<std::mutex> lock(instance->mutex); |
||||
|
--instance->count; |
||||
|
std::notify_all_at_thread_exit(instance->cv, std::move(lock)); |
||||
|
}) |
||||
|
.detach(); |
||||
|
} |
||||
|
|
||||
|
} // namespace Common
|
||||
@ -0,0 +1,40 @@ |
|||||
|
// Copyright 2018 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <condition_variable> |
||||
|
#include <functional> |
||||
|
|
||||
|
namespace Common { |
||||
|
|
||||
|
/** |
||||
|
* A background manager which ensures that all detached task is finished before program exits. |
||||
|
* |
||||
|
* Some tasks, telemetry submission for example, prefer executing asynchronously and don't care |
||||
|
* about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if |
||||
|
* the task is launched just before the program exits (which is a common case for telemetry), so we |
||||
|
* need to block on these tasks on program exit. |
||||
|
* |
||||
|
* To make detached task safe, a single DetachedTasks object should be placed in the main(), and |
||||
|
* call WaitForAllTasks() after all program execution but before global/static variable destruction. |
||||
|
* Any potentially unsafe detached task should be executed via DetachedTasks::AddTask. |
||||
|
*/ |
||||
|
class DetachedTasks { |
||||
|
public: |
||||
|
DetachedTasks(); |
||||
|
~DetachedTasks(); |
||||
|
void WaitForAllTasks(); |
||||
|
|
||||
|
static void AddTask(std::function<void()> task); |
||||
|
|
||||
|
private: |
||||
|
static DetachedTasks* instance; |
||||
|
|
||||
|
std::condition_variable cv; |
||||
|
std::mutex mutex; |
||||
|
int count = 0; |
||||
|
}; |
||||
|
|
||||
|
} // namespace Common |
||||
@ -0,0 +1,24 @@ |
|||||
|
// Copyright 2018 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
namespace Common { |
||||
|
struct WebResult { |
||||
|
enum class Code : u32 { |
||||
|
Success, |
||||
|
InvalidURL, |
||||
|
CredentialsMissing, |
||||
|
LibError, |
||||
|
HttpError, |
||||
|
WrongContent, |
||||
|
NoWebservice, |
||||
|
}; |
||||
|
Code result_code; |
||||
|
std::string result_string; |
||||
|
std::string returned_data; |
||||
|
}; |
||||
|
} // namespace Common |
||||
@ -0,0 +1,16 @@ |
|||||
|
add_library(web_service STATIC |
||||
|
telemetry_json.cpp |
||||
|
telemetry_json.h |
||||
|
verify_login.cpp |
||||
|
verify_login.h |
||||
|
web_backend.cpp |
||||
|
web_backend.h |
||||
|
) |
||||
|
|
||||
|
create_target_directory_groups(web_service) |
||||
|
|
||||
|
get_directory_property(OPENSSL_LIBS |
||||
|
DIRECTORY ${CMAKE_SOURCE_DIR}/externals/libressl |
||||
|
DEFINITION OPENSSL_LIBS) |
||||
|
target_compile_definitions(web_service PUBLIC -DCPPHTTPLIB_OPENSSL_SUPPORT) |
||||
|
target_link_libraries(web_service PRIVATE common json-headers ${OPENSSL_LIBS} httplib lurlparser) |
||||
@ -0,0 +1,99 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <thread>
|
||||
|
#include "common/assert.h"
|
||||
|
#include "common/detached_tasks.h"
|
||||
|
#include "web_service/telemetry_json.h"
|
||||
|
#include "web_service/web_backend.h"
|
||||
|
|
||||
|
namespace WebService { |
||||
|
|
||||
|
TelemetryJson::TelemetryJson(const std::string& host, const std::string& username, |
||||
|
const std::string& token) |
||||
|
: host(std::move(host)), username(std::move(username)), token(std::move(token)) {} |
||||
|
TelemetryJson::~TelemetryJson() = default; |
||||
|
|
||||
|
template <class T> |
||||
|
void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) { |
||||
|
sections[static_cast<u8>(type)][name] = value; |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) { |
||||
|
TopSection()[name] = sections[static_cast<unsigned>(type)]; |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<bool>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<double>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<float>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<u8>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<u16>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<u32>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<u64>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<s8>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<s16>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<s32>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<s64>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) { |
||||
|
Serialize(field.GetType(), field.GetName(), field.GetValue().count()); |
||||
|
} |
||||
|
|
||||
|
void TelemetryJson::Complete() { |
||||
|
SerializeSection(Telemetry::FieldType::App, "App"); |
||||
|
SerializeSection(Telemetry::FieldType::Session, "Session"); |
||||
|
SerializeSection(Telemetry::FieldType::Performance, "Performance"); |
||||
|
SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); |
||||
|
SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); |
||||
|
SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); |
||||
|
|
||||
|
auto content = TopSection().dump(); |
||||
|
// Send the telemetry async but don't handle the errors since they were written to the log
|
||||
|
Common::DetachedTasks::AddTask( |
||||
|
[host{this->host}, username{this->username}, token{this->token}, content]() { |
||||
|
Client{host, username, token}.PostJson("/telemetry", content, true); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} // namespace WebService
|
||||
@ -0,0 +1,58 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <array> |
||||
|
#include <string> |
||||
|
#include <json.hpp> |
||||
|
#include "common/telemetry.h" |
||||
|
#include "common/web_result.h" |
||||
|
|
||||
|
namespace WebService { |
||||
|
|
||||
|
/** |
||||
|
* Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the |
||||
|
* yuzu web service |
||||
|
*/ |
||||
|
class TelemetryJson : public Telemetry::VisitorInterface { |
||||
|
public: |
||||
|
TelemetryJson(const std::string& host, const std::string& username, const std::string& token); |
||||
|
~TelemetryJson(); |
||||
|
|
||||
|
void Visit(const Telemetry::Field<bool>& field) override; |
||||
|
void Visit(const Telemetry::Field<double>& field) override; |
||||
|
void Visit(const Telemetry::Field<float>& field) override; |
||||
|
void Visit(const Telemetry::Field<u8>& field) override; |
||||
|
void Visit(const Telemetry::Field<u16>& field) override; |
||||
|
void Visit(const Telemetry::Field<u32>& field) override; |
||||
|
void Visit(const Telemetry::Field<u64>& field) override; |
||||
|
void Visit(const Telemetry::Field<s8>& field) override; |
||||
|
void Visit(const Telemetry::Field<s16>& field) override; |
||||
|
void Visit(const Telemetry::Field<s32>& field) override; |
||||
|
void Visit(const Telemetry::Field<s64>& field) override; |
||||
|
void Visit(const Telemetry::Field<std::string>& field) override; |
||||
|
void Visit(const Telemetry::Field<const char*>& field) override; |
||||
|
void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override; |
||||
|
|
||||
|
void Complete() override; |
||||
|
|
||||
|
private: |
||||
|
nlohmann::json& TopSection() { |
||||
|
return sections[static_cast<u8>(Telemetry::FieldType::None)]; |
||||
|
} |
||||
|
|
||||
|
template <class T> |
||||
|
void Serialize(Telemetry::FieldType type, const std::string& name, T value); |
||||
|
|
||||
|
void SerializeSection(Telemetry::FieldType type, const std::string& name); |
||||
|
|
||||
|
nlohmann::json output; |
||||
|
std::array<nlohmann::json, 7> sections; |
||||
|
std::string host; |
||||
|
std::string username; |
||||
|
std::string token; |
||||
|
}; |
||||
|
|
||||
|
} // namespace WebService |
||||
@ -0,0 +1,27 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <json.hpp>
|
||||
|
#include "web_service/verify_login.h"
|
||||
|
#include "web_service/web_backend.h"
|
||||
|
|
||||
|
namespace WebService { |
||||
|
|
||||
|
bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token) { |
||||
|
Client client(host, username, token); |
||||
|
auto reply = client.GetJson("/profile", false).returned_data; |
||||
|
if (reply.empty()) { |
||||
|
return false; |
||||
|
} |
||||
|
nlohmann::json json = nlohmann::json::parse(reply); |
||||
|
const auto iter = json.find("username"); |
||||
|
|
||||
|
if (iter == json.end()) { |
||||
|
return username.empty(); |
||||
|
} |
||||
|
|
||||
|
return username == *iter; |
||||
|
} |
||||
|
|
||||
|
} // namespace WebService
|
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <functional> |
||||
|
#include <future> |
||||
|
#include <string> |
||||
|
|
||||
|
namespace WebService { |
||||
|
|
||||
|
/** |
||||
|
* Checks if username and token is valid |
||||
|
* @param host the web API URL |
||||
|
* @param username yuzu username to use for authentication. |
||||
|
* @param token yuzu token to use for authentication. |
||||
|
* @returns a bool indicating whether the verification succeeded |
||||
|
*/ |
||||
|
bool VerifyLogin(const std::string& host, const std::string& username, const std::string& token); |
||||
|
|
||||
|
} // namespace WebService |
||||
@ -0,0 +1,149 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <cstdlib>
|
||||
|
#include <string>
|
||||
|
#include <thread>
|
||||
|
#include <LUrlParser.h>
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "common/web_result.h"
|
||||
|
#include "core/settings.h"
|
||||
|
#include "web_service/web_backend.h"
|
||||
|
|
||||
|
namespace WebService { |
||||
|
|
||||
|
constexpr std::array<const char, 1> API_VERSION{'1'}; |
||||
|
|
||||
|
constexpr u32 HTTP_PORT = 80; |
||||
|
constexpr u32 HTTPS_PORT = 443; |
||||
|
|
||||
|
constexpr u32 TIMEOUT_SECONDS = 30; |
||||
|
|
||||
|
Client::JWTCache Client::jwt_cache{}; |
||||
|
|
||||
|
Client::Client(const std::string& host, const std::string& username, const std::string& token) |
||||
|
: host(host), username(username), token(token) { |
||||
|
std::lock_guard<std::mutex> lock(jwt_cache.mutex); |
||||
|
if (username == jwt_cache.username && token == jwt_cache.token) { |
||||
|
jwt = jwt_cache.jwt; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, |
||||
|
const std::string& data, const std::string& jwt, |
||||
|
const std::string& username, const std::string& token) { |
||||
|
if (cli == nullptr) { |
||||
|
auto parsedUrl = LUrlParser::clParseURL::ParseURL(host); |
||||
|
int port; |
||||
|
if (parsedUrl.m_Scheme == "http") { |
||||
|
if (!parsedUrl.GetPort(&port)) { |
||||
|
port = HTTP_PORT; |
||||
|
} |
||||
|
cli = |
||||
|
std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS); |
||||
|
} else if (parsedUrl.m_Scheme == "https") { |
||||
|
if (!parsedUrl.GetPort(&port)) { |
||||
|
port = HTTPS_PORT; |
||||
|
} |
||||
|
cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port, |
||||
|
TIMEOUT_SECONDS); |
||||
|
} else { |
||||
|
LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme); |
||||
|
return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"}; |
||||
|
} |
||||
|
} |
||||
|
if (cli == nullptr) { |
||||
|
LOG_ERROR(WebService, "Invalid URL {}", host + path); |
||||
|
return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"}; |
||||
|
} |
||||
|
|
||||
|
httplib::Headers params; |
||||
|
if (!jwt.empty()) { |
||||
|
params = { |
||||
|
{std::string("Authorization"), fmt::format("Bearer {}", jwt)}, |
||||
|
}; |
||||
|
} else if (!username.empty()) { |
||||
|
params = { |
||||
|
{std::string("x-username"), username}, |
||||
|
{std::string("x-token"), token}, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end())); |
||||
|
if (method != "GET") { |
||||
|
params.emplace(std::string("Content-Type"), std::string("application/json")); |
||||
|
}; |
||||
|
|
||||
|
httplib::Request request; |
||||
|
request.method = method; |
||||
|
request.path = path; |
||||
|
request.headers = params; |
||||
|
request.body = data; |
||||
|
|
||||
|
httplib::Response response; |
||||
|
|
||||
|
if (!cli->send(request, response)) { |
||||
|
LOG_ERROR(WebService, "{} to {} returned null", method, host + path); |
||||
|
return Common::WebResult{Common::WebResult::Code::LibError, "Null response"}; |
||||
|
} |
||||
|
|
||||
|
if (response.status >= 400) { |
||||
|
LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path, |
||||
|
response.status); |
||||
|
return Common::WebResult{Common::WebResult::Code::HttpError, |
||||
|
std::to_string(response.status)}; |
||||
|
} |
||||
|
|
||||
|
auto content_type = response.headers.find("content-type"); |
||||
|
|
||||
|
if (content_type == response.headers.end()) { |
||||
|
LOG_ERROR(WebService, "{} to {} returned no content", method, host + path); |
||||
|
return Common::WebResult{Common::WebResult::Code::WrongContent, ""}; |
||||
|
} |
||||
|
|
||||
|
if (content_type->second.find("application/json") == std::string::npos && |
||||
|
content_type->second.find("text/html; charset=utf-8") == std::string::npos) { |
||||
|
LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path, |
||||
|
content_type->second); |
||||
|
return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"}; |
||||
|
} |
||||
|
return Common::WebResult{Common::WebResult::Code::Success, "", response.body}; |
||||
|
} |
||||
|
|
||||
|
void Client::UpdateJWT() { |
||||
|
if (!username.empty() && !token.empty()) { |
||||
|
auto result = GenericJson("POST", "/jwt/internal", "", "", username, token); |
||||
|
if (result.result_code != Common::WebResult::Code::Success) { |
||||
|
LOG_ERROR(WebService, "UpdateJWT failed"); |
||||
|
} else { |
||||
|
std::lock_guard<std::mutex> lock(jwt_cache.mutex); |
||||
|
jwt_cache.username = username; |
||||
|
jwt_cache.token = token; |
||||
|
jwt_cache.jwt = jwt = result.returned_data; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Common::WebResult Client::GenericJson(const std::string& method, const std::string& path, |
||||
|
const std::string& data, bool allow_anonymous) { |
||||
|
if (jwt.empty()) { |
||||
|
UpdateJWT(); |
||||
|
} |
||||
|
|
||||
|
if (jwt.empty() && !allow_anonymous) { |
||||
|
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); |
||||
|
return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"}; |
||||
|
} |
||||
|
|
||||
|
auto result = GenericJson(method, path, data, jwt); |
||||
|
if (result.result_string == "401") { |
||||
|
// Try again with new JWT
|
||||
|
UpdateJWT(); |
||||
|
result = GenericJson(method, path, data, jwt); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
} // namespace WebService
|
||||
@ -0,0 +1,92 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <functional> |
||||
|
#include <mutex> |
||||
|
#include <string> |
||||
|
#include <tuple> |
||||
|
#include <httplib.h> |
||||
|
#include "common/common_types.h" |
||||
|
#include "common/web_result.h" |
||||
|
|
||||
|
namespace httplib { |
||||
|
class Client; |
||||
|
} |
||||
|
|
||||
|
namespace WebService { |
||||
|
|
||||
|
class Client { |
||||
|
public: |
||||
|
Client(const std::string& host, const std::string& username, const std::string& token); |
||||
|
|
||||
|
/** |
||||
|
* Posts JSON to the specified path. |
||||
|
* @param path the URL segment after the host address. |
||||
|
* @param data String of JSON data to use for the body of the POST request. |
||||
|
* @param allow_anonymous If true, allow anonymous unauthenticated requests. |
||||
|
* @return the result of the request. |
||||
|
*/ |
||||
|
Common::WebResult PostJson(const std::string& path, const std::string& data, |
||||
|
bool allow_anonymous) { |
||||
|
return GenericJson("POST", path, data, allow_anonymous); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Gets JSON from the specified path. |
||||
|
* @param path the URL segment after the host address. |
||||
|
* @param allow_anonymous If true, allow anonymous unauthenticated requests. |
||||
|
* @return the result of the request. |
||||
|
*/ |
||||
|
Common::WebResult GetJson(const std::string& path, bool allow_anonymous) { |
||||
|
return GenericJson("GET", path, "", allow_anonymous); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Deletes JSON to the specified path. |
||||
|
* @param path the URL segment after the host address. |
||||
|
* @param data String of JSON data to use for the body of the DELETE request. |
||||
|
* @param allow_anonymous If true, allow anonymous unauthenticated requests. |
||||
|
* @return the result of the request. |
||||
|
*/ |
||||
|
Common::WebResult DeleteJson(const std::string& path, const std::string& data, |
||||
|
bool allow_anonymous) { |
||||
|
return GenericJson("DELETE", path, data, allow_anonymous); |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
/// A generic function handles POST, GET and DELETE request together |
||||
|
Common::WebResult GenericJson(const std::string& method, const std::string& path, |
||||
|
const std::string& data, bool allow_anonymous); |
||||
|
|
||||
|
/** |
||||
|
* A generic function with explicit authentication method specified |
||||
|
* JWT is used if the jwt parameter is not empty |
||||
|
* username + token is used if jwt is empty but username and token are not empty |
||||
|
* anonymous if all of jwt, username and token are empty |
||||
|
*/ |
||||
|
Common::WebResult GenericJson(const std::string& method, const std::string& path, |
||||
|
const std::string& data, const std::string& jwt = "", |
||||
|
const std::string& username = "", const std::string& token = ""); |
||||
|
|
||||
|
// Retrieve a new JWT from given username and token |
||||
|
void UpdateJWT(); |
||||
|
|
||||
|
std::string host; |
||||
|
std::string username; |
||||
|
std::string token; |
||||
|
std::string jwt; |
||||
|
std::unique_ptr<httplib::Client> cli; |
||||
|
|
||||
|
struct JWTCache { |
||||
|
std::mutex mutex; |
||||
|
std::string username; |
||||
|
std::string token; |
||||
|
std::string jwt; |
||||
|
}; |
||||
|
static JWTCache jwt_cache; |
||||
|
}; |
||||
|
|
||||
|
} // namespace WebService |
||||
@ -0,0 +1,65 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <QButtonGroup>
|
||||
|
#include <QMessageBox>
|
||||
|
#include <QPushButton>
|
||||
|
#include "common/logging/log.h"
|
||||
|
#include "common/telemetry.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/telemetry_session.h"
|
||||
|
#include "ui_compatdb.h"
|
||||
|
#include "yuzu/compatdb.h"
|
||||
|
|
||||
|
CompatDB::CompatDB(QWidget* parent) |
||||
|
: QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), |
||||
|
ui{std::make_unique<Ui::CompatDB>()} { |
||||
|
ui->setupUi(this); |
||||
|
connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
||||
|
connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
||||
|
connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
||||
|
connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
||||
|
connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
||||
|
connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext); |
||||
|
connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit); |
||||
|
} |
||||
|
|
||||
|
CompatDB::~CompatDB() = default; |
||||
|
|
||||
|
enum class CompatDBPage { |
||||
|
Intro = 0, |
||||
|
Selection = 1, |
||||
|
Final = 2, |
||||
|
}; |
||||
|
|
||||
|
void CompatDB::Submit() { |
||||
|
QButtonGroup* compatibility = new QButtonGroup(this); |
||||
|
compatibility->addButton(ui->radioButton_Perfect, 0); |
||||
|
compatibility->addButton(ui->radioButton_Great, 1); |
||||
|
compatibility->addButton(ui->radioButton_Okay, 2); |
||||
|
compatibility->addButton(ui->radioButton_Bad, 3); |
||||
|
compatibility->addButton(ui->radioButton_IntroMenu, 4); |
||||
|
compatibility->addButton(ui->radioButton_WontBoot, 5); |
||||
|
switch ((static_cast<CompatDBPage>(currentId()))) { |
||||
|
case CompatDBPage::Selection: |
||||
|
if (compatibility->checkedId() == -1) { |
||||
|
button(NextButton)->setEnabled(false); |
||||
|
} |
||||
|
break; |
||||
|
case CompatDBPage::Final: |
||||
|
LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); |
||||
|
Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility", |
||||
|
compatibility->checkedId()); |
||||
|
// older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
|
||||
|
// workaround
|
||||
|
button(QWizard::CancelButton)->setVisible(false); |
||||
|
break; |
||||
|
default: |
||||
|
LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CompatDB::EnableNext() { |
||||
|
button(NextButton)->setEnabled(true); |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <QWizard> |
||||
|
|
||||
|
namespace Ui { |
||||
|
class CompatDB; |
||||
|
} |
||||
|
|
||||
|
class CompatDB : public QWizard { |
||||
|
Q_OBJECT |
||||
|
|
||||
|
public: |
||||
|
explicit CompatDB(QWidget* parent = nullptr); |
||||
|
~CompatDB(); |
||||
|
|
||||
|
private: |
||||
|
std::unique_ptr<Ui::CompatDB> ui; |
||||
|
|
||||
|
void Submit(); |
||||
|
void EnableNext(); |
||||
|
}; |
||||
@ -0,0 +1,215 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<ui version="4.0"> |
||||
|
<class>CompatDB</class> |
||||
|
<widget class="QWizard" name="CompatDB"> |
||||
|
<property name="geometry"> |
||||
|
<rect> |
||||
|
<x>0</x> |
||||
|
<y>0</y> |
||||
|
<width>600</width> |
||||
|
<height>482</height> |
||||
|
</rect> |
||||
|
</property> |
||||
|
<property name="minimumSize"> |
||||
|
<size> |
||||
|
<width>500</width> |
||||
|
<height>410</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
<property name="windowTitle"> |
||||
|
<string>Report Compatibility</string> |
||||
|
</property> |
||||
|
<property name="options"> |
||||
|
<set>QWizard::DisabledBackButtonOnLastPage|QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage</set> |
||||
|
</property> |
||||
|
<widget class="QWizardPage" name="wizard_Info"> |
||||
|
<property name="title"> |
||||
|
<string>Report Game Compatibility</string> |
||||
|
</property> |
||||
|
<attribute name="pageId"> |
||||
|
<string notr="true">0</string> |
||||
|
</attribute> |
||||
|
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="lbl_Spiel"> |
||||
|
<property name="text"> |
||||
|
<string><html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of yuzu you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected yuzu account</li></ul></body></html></string> |
||||
|
</property> |
||||
|
<property name="wordWrap"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
<property name="openExternalLinks"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<spacer name="verticalSpacer_2"> |
||||
|
<property name="orientation"> |
||||
|
<enum>Qt::Vertical</enum> |
||||
|
</property> |
||||
|
<property name="sizeHint" stdset="0"> |
||||
|
<size> |
||||
|
<width>20</width> |
||||
|
<height>0</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
</spacer> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
<widget class="QWizardPage" name="wizard_Report"> |
||||
|
<property name="title"> |
||||
|
<string>Report Game Compatibility</string> |
||||
|
</property> |
||||
|
<attribute name="pageId"> |
||||
|
<string notr="true">1</string> |
||||
|
</attribute> |
||||
|
<layout class="QFormLayout" name="formLayout"> |
||||
|
<item row="2" column="0"> |
||||
|
<widget class="QRadioButton" name="radioButton_Perfect"> |
||||
|
<property name="text"> |
||||
|
<string>Perfect</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="2" column="1"> |
||||
|
<widget class="QLabel" name="lbl_Perfect"> |
||||
|
<property name="text"> |
||||
|
<string><html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html></string> |
||||
|
</property> |
||||
|
<property name="wordWrap"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="4" column="0"> |
||||
|
<widget class="QRadioButton" name="radioButton_Great"> |
||||
|
<property name="text"> |
||||
|
<string>Great </string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="4" column="1"> |
||||
|
<widget class="QLabel" name="lbl_Great"> |
||||
|
<property name="text"> |
||||
|
<string><html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html></string> |
||||
|
</property> |
||||
|
<property name="wordWrap"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="5" column="0"> |
||||
|
<widget class="QRadioButton" name="radioButton_Okay"> |
||||
|
<property name="text"> |
||||
|
<string>Okay</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="5" column="1"> |
||||
|
<widget class="QLabel" name="lbl_Okay"> |
||||
|
<property name="text"> |
||||
|
<string><html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html></string> |
||||
|
</property> |
||||
|
<property name="wordWrap"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="6" column="0"> |
||||
|
<widget class="QRadioButton" name="radioButton_Bad"> |
||||
|
<property name="text"> |
||||
|
<string>Bad</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="6" column="1"> |
||||
|
<widget class="QLabel" name="lbl_Bad"> |
||||
|
<property name="text"> |
||||
|
<string><html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html></string> |
||||
|
</property> |
||||
|
<property name="wordWrap"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="7" column="0"> |
||||
|
<widget class="QRadioButton" name="radioButton_IntroMenu"> |
||||
|
<property name="text"> |
||||
|
<string>Intro/Menu</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="7" column="1"> |
||||
|
<widget class="QLabel" name="lbl_IntroMenu"> |
||||
|
<property name="text"> |
||||
|
<string><html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html></string> |
||||
|
</property> |
||||
|
<property name="wordWrap"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="8" column="0"> |
||||
|
<widget class="QRadioButton" name="radioButton_WontBoot"> |
||||
|
<property name="text"> |
||||
|
<string>Won't Boot</string> |
||||
|
</property> |
||||
|
<property name="checkable"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
<property name="checked"> |
||||
|
<bool>false</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="8" column="1"> |
||||
|
<widget class="QLabel" name="lbl_WontBoot"> |
||||
|
<property name="text"> |
||||
|
<string><html><head/><body><p>The game crashes when attempting to startup.</p></body></html></string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="0" column="0" colspan="2"> |
||||
|
<widget class="QLabel" name="lbl_Independent"> |
||||
|
<property name="font"> |
||||
|
<font> |
||||
|
<pointsize>10</pointsize> |
||||
|
</font> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string><html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html></string> |
||||
|
</property> |
||||
|
<property name="wordWrap"> |
||||
|
<bool>true</bool> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="1" column="0" colspan="2"> |
||||
|
<spacer name="verticalSpacer"> |
||||
|
<property name="orientation"> |
||||
|
<enum>Qt::Vertical</enum> |
||||
|
</property> |
||||
|
<property name="sizeHint" stdset="0"> |
||||
|
<size> |
||||
|
<width>20</width> |
||||
|
<height>0</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
</spacer> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
<widget class="QWizardPage" name="wizard_ThankYou"> |
||||
|
<property name="title"> |
||||
|
<string>Thank you for your submission!</string> |
||||
|
</property> |
||||
|
<attribute name="pageId"> |
||||
|
<string notr="true">2</string> |
||||
|
</attribute> |
||||
|
</widget> |
||||
|
</widget> |
||||
|
<resources/> |
||||
|
<connections/> |
||||
|
</ui> |
||||
@ -0,0 +1,119 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <QIcon>
|
||||
|
#include <QMessageBox>
|
||||
|
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
#include "core/settings.h"
|
||||
|
#include "core/telemetry_session.h"
|
||||
|
#include "ui_configure_web.h"
|
||||
|
#include "yuzu/configuration/configure_web.h"
|
||||
|
#include "yuzu/ui_settings.h"
|
||||
|
|
||||
|
ConfigureWeb::ConfigureWeb(QWidget* parent) |
||||
|
: QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) { |
||||
|
ui->setupUi(this); |
||||
|
connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this, |
||||
|
&ConfigureWeb::RefreshTelemetryID); |
||||
|
connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin); |
||||
|
connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified); |
||||
|
|
||||
|
#ifndef USE_DISCORD_PRESENCE
|
||||
|
ui->discord_group->setVisible(false); |
||||
|
#endif
|
||||
|
this->setConfiguration(); |
||||
|
} |
||||
|
|
||||
|
ConfigureWeb::~ConfigureWeb() = default; |
||||
|
|
||||
|
void ConfigureWeb::setConfiguration() { |
||||
|
ui->web_credentials_disclaimer->setWordWrap(true); |
||||
|
ui->telemetry_learn_more->setOpenExternalLinks(true); |
||||
|
ui->telemetry_learn_more->setText( |
||||
|
tr("<a href='https://yuzu-emu.org/help/features/telemetry/'><span style=\"text-decoration: " |
||||
|
"underline; color:#039be5;\">Learn more</span></a>")); |
||||
|
|
||||
|
ui->web_signup_link->setOpenExternalLinks(true); |
||||
|
ui->web_signup_link->setText( |
||||
|
tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; " |
||||
|
"color:#039be5;\">Sign up</span></a>")); |
||||
|
ui->web_token_info_link->setOpenExternalLinks(true); |
||||
|
ui->web_token_info_link->setText( |
||||
|
tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: " |
||||
|
"underline; color:#039be5;\">What is my token?</span></a>")); |
||||
|
|
||||
|
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry); |
||||
|
ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username)); |
||||
|
ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token)); |
||||
|
// Connect after setting the values, to avoid calling OnLoginChanged now
|
||||
|
connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); |
||||
|
connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); |
||||
|
ui->label_telemetry_id->setText( |
||||
|
tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper())); |
||||
|
user_verified = true; |
||||
|
|
||||
|
ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence); |
||||
|
} |
||||
|
|
||||
|
void ConfigureWeb::applyConfiguration() { |
||||
|
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked(); |
||||
|
UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked(); |
||||
|
if (user_verified) { |
||||
|
Settings::values.yuzu_username = ui->edit_username->text().toStdString(); |
||||
|
Settings::values.yuzu_token = ui->edit_token->text().toStdString(); |
||||
|
} else { |
||||
|
QMessageBox::warning(this, tr("Username and token not verified"), |
||||
|
tr("Username and token were not verified. The changes to your " |
||||
|
"username and/or token have not been saved.")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureWeb::RefreshTelemetryID() { |
||||
|
const u64 new_telemetry_id{Core::RegenerateTelemetryId()}; |
||||
|
ui->label_telemetry_id->setText( |
||||
|
tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper())); |
||||
|
} |
||||
|
|
||||
|
void ConfigureWeb::OnLoginChanged() { |
||||
|
if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) { |
||||
|
user_verified = true; |
||||
|
ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); |
||||
|
ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); |
||||
|
} else { |
||||
|
user_verified = false; |
||||
|
ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); |
||||
|
ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureWeb::VerifyLogin() { |
||||
|
ui->button_verify_login->setDisabled(true); |
||||
|
ui->button_verify_login->setText(tr("Verifying")); |
||||
|
verify_watcher.setFuture( |
||||
|
QtConcurrent::run([this, username = ui->edit_username->text().toStdString(), |
||||
|
token = ui->edit_token->text().toStdString()]() { |
||||
|
return Core::VerifyLogin(username, token); |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
void ConfigureWeb::OnLoginVerified() { |
||||
|
ui->button_verify_login->setEnabled(true); |
||||
|
ui->button_verify_login->setText(tr("Verify")); |
||||
|
if (verify_watcher.result()) { |
||||
|
user_verified = true; |
||||
|
ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); |
||||
|
ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); |
||||
|
} else { |
||||
|
ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); |
||||
|
ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); |
||||
|
QMessageBox::critical( |
||||
|
this, tr("Verification failed"), |
||||
|
tr("Verification failed. Check that you have entered your username and token " |
||||
|
"correctly, and that your internet connection is working.")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ConfigureWeb::retranslateUi() { |
||||
|
ui->retranslateUi(this); |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
// Copyright 2017 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <memory> |
||||
|
#include <QFutureWatcher> |
||||
|
#include <QWidget> |
||||
|
|
||||
|
namespace Ui { |
||||
|
class ConfigureWeb; |
||||
|
} |
||||
|
|
||||
|
class ConfigureWeb : public QWidget { |
||||
|
Q_OBJECT |
||||
|
|
||||
|
public: |
||||
|
explicit ConfigureWeb(QWidget* parent = nullptr); |
||||
|
~ConfigureWeb(); |
||||
|
|
||||
|
void applyConfiguration(); |
||||
|
void retranslateUi(); |
||||
|
|
||||
|
public slots: |
||||
|
void RefreshTelemetryID(); |
||||
|
void OnLoginChanged(); |
||||
|
void VerifyLogin(); |
||||
|
void OnLoginVerified(); |
||||
|
|
||||
|
private: |
||||
|
void setConfiguration(); |
||||
|
|
||||
|
bool user_verified = true; |
||||
|
QFutureWatcher<bool> verify_watcher; |
||||
|
|
||||
|
std::unique_ptr<Ui::ConfigureWeb> ui; |
||||
|
}; |
||||
@ -0,0 +1,206 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<ui version="4.0"> |
||||
|
<class>ConfigureWeb</class> |
||||
|
<widget class="QWidget" name="ConfigureWeb"> |
||||
|
<property name="geometry"> |
||||
|
<rect> |
||||
|
<x>0</x> |
||||
|
<y>0</y> |
||||
|
<width>926</width> |
||||
|
<height>561</height> |
||||
|
</rect> |
||||
|
</property> |
||||
|
<property name="windowTitle"> |
||||
|
<string>Form</string> |
||||
|
</property> |
||||
|
<layout class="QVBoxLayout" name="verticalLayout"> |
||||
|
<item> |
||||
|
<layout class="QVBoxLayout" name="verticalLayout_3"> |
||||
|
<item> |
||||
|
<widget class="QGroupBox" name="groupBoxWebConfig"> |
||||
|
<property name="title"> |
||||
|
<string>yuzu Web Service</string> |
||||
|
</property> |
||||
|
<layout class="QVBoxLayout" name="verticalLayoutYuzuWebService"> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="web_credentials_disclaimer"> |
||||
|
<property name="text"> |
||||
|
<string>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QGridLayout" name="gridLayoutYuzuUsername"> |
||||
|
<item row="2" column="3"> |
||||
|
<widget class="QPushButton" name="button_verify_login"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="layoutDirection"> |
||||
|
<enum>Qt::RightToLeft</enum> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string>Verify</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="2" column="0"> |
||||
|
<widget class="QLabel" name="web_signup_link"> |
||||
|
<property name="text"> |
||||
|
<string>Sign up</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="0" column="1" colspan="3"> |
||||
|
<widget class="QLineEdit" name="edit_username"> |
||||
|
<property name="maxLength"> |
||||
|
<number>36</number> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="1" column="0"> |
||||
|
<widget class="QLabel" name="label_token"> |
||||
|
<property name="text"> |
||||
|
<string>Token: </string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="1" column="4"> |
||||
|
<widget class="QLabel" name="label_token_verified"> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="0" column="0"> |
||||
|
<widget class="QLabel" name="label_username"> |
||||
|
<property name="text"> |
||||
|
<string>Username: </string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="0" column="4"> |
||||
|
<widget class="QLabel" name="label_username_verified"> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="1" column="1" colspan="3"> |
||||
|
<widget class="QLineEdit" name="edit_token"> |
||||
|
<property name="maxLength"> |
||||
|
<number>36</number> |
||||
|
</property> |
||||
|
<property name="echoMode"> |
||||
|
<enum>QLineEdit::Password</enum> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="2" column="1"> |
||||
|
<widget class="QLabel" name="web_token_info_link"> |
||||
|
<property name="text"> |
||||
|
<string>What is my token?</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="2" column="2"> |
||||
|
<spacer name="horizontalSpacer"> |
||||
|
<property name="orientation"> |
||||
|
<enum>Qt::Horizontal</enum> |
||||
|
</property> |
||||
|
<property name="sizeHint" stdset="0"> |
||||
|
<size> |
||||
|
<width>40</width> |
||||
|
<height>20</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
</spacer> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QGroupBox" name="groupBox"> |
||||
|
<property name="title"> |
||||
|
<string>Telemetry</string> |
||||
|
</property> |
||||
|
<layout class="QVBoxLayout" name="verticalLayout_2"> |
||||
|
<item> |
||||
|
<widget class="QCheckBox" name="toggle_telemetry"> |
||||
|
<property name="text"> |
||||
|
<string>Share anonymous usage data with the yuzu team</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QLabel" name="telemetry_learn_more"> |
||||
|
<property name="text"> |
||||
|
<string>Learn more</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<layout class="QGridLayout" name="gridLayoutTelemetryId"> |
||||
|
<item row="0" column="0"> |
||||
|
<widget class="QLabel" name="label_telemetry_id"> |
||||
|
<property name="text"> |
||||
|
<string>Telemetry ID:</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item row="0" column="1"> |
||||
|
<widget class="QPushButton" name="button_regenerate_telemetry_id"> |
||||
|
<property name="sizePolicy"> |
||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"> |
||||
|
<horstretch>0</horstretch> |
||||
|
<verstretch>0</verstretch> |
||||
|
</sizepolicy> |
||||
|
</property> |
||||
|
<property name="layoutDirection"> |
||||
|
<enum>Qt::RightToLeft</enum> |
||||
|
</property> |
||||
|
<property name="text"> |
||||
|
<string>Regenerate</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</item> |
||||
|
<item> |
||||
|
<widget class="QGroupBox" name="discord_group"> |
||||
|
<property name="title"> |
||||
|
<string>Discord Presence</string> |
||||
|
</property> |
||||
|
<layout class="QVBoxLayout" name="verticalLayout_21"> |
||||
|
<item> |
||||
|
<widget class="QCheckBox" name="toggle_discordrpc"> |
||||
|
<property name="text"> |
||||
|
<string>Show Current Game in your Discord Status</string> |
||||
|
</property> |
||||
|
</widget> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
</item> |
||||
|
<item> |
||||
|
<spacer name="verticalSpacer"> |
||||
|
<property name="orientation"> |
||||
|
<enum>Qt::Vertical</enum> |
||||
|
</property> |
||||
|
<property name="sizeHint" stdset="0"> |
||||
|
<size> |
||||
|
<width>20</width> |
||||
|
<height>40</height> |
||||
|
</size> |
||||
|
</property> |
||||
|
</spacer> |
||||
|
</item> |
||||
|
</layout> |
||||
|
</widget> |
||||
|
<resources/> |
||||
|
<connections/> |
||||
|
</ui> |
||||
@ -0,0 +1,25 @@ |
|||||
|
// Copyright 2018 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
namespace DiscordRPC { |
||||
|
|
||||
|
class DiscordInterface { |
||||
|
public: |
||||
|
virtual ~DiscordInterface() = default; |
||||
|
|
||||
|
virtual void Pause() = 0; |
||||
|
virtual void Update() = 0; |
||||
|
}; |
||||
|
|
||||
|
class NullImpl : public DiscordInterface { |
||||
|
public: |
||||
|
~NullImpl() = default; |
||||
|
|
||||
|
void Pause() override {} |
||||
|
void Update() override {} |
||||
|
}; |
||||
|
|
||||
|
} // namespace DiscordRPC |
||||
@ -0,0 +1,52 @@ |
|||||
|
// Copyright 2018 Citra Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include <chrono>
|
||||
|
#include <string>
|
||||
|
#include <discord_rpc.h>
|
||||
|
#include "common/common_types.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/loader/loader.h"
|
||||
|
#include "yuzu/discord_impl.h"
|
||||
|
#include "yuzu/ui_settings.h"
|
||||
|
|
||||
|
namespace DiscordRPC { |
||||
|
|
||||
|
DiscordImpl::DiscordImpl() { |
||||
|
DiscordEventHandlers handlers{}; |
||||
|
|
||||
|
// The number is the client ID for yuzu, it's used for images and the
|
||||
|
// application name
|
||||
|
Discord_Initialize("471872241299226636", &handlers, 1, nullptr); |
||||
|
} |
||||
|
|
||||
|
DiscordImpl::~DiscordImpl() { |
||||
|
Discord_ClearPresence(); |
||||
|
Discord_Shutdown(); |
||||
|
} |
||||
|
|
||||
|
void DiscordImpl::Pause() { |
||||
|
Discord_ClearPresence(); |
||||
|
} |
||||
|
|
||||
|
void DiscordImpl::Update() { |
||||
|
s64 start_time = std::chrono::duration_cast<std::chrono::seconds>( |
||||
|
std::chrono::system_clock::now().time_since_epoch()) |
||||
|
.count(); |
||||
|
std::string title; |
||||
|
if (Core::System::GetInstance().IsPoweredOn()) |
||||
|
Core::System::GetInstance().GetAppLoader().ReadTitle(title); |
||||
|
DiscordRichPresence presence{}; |
||||
|
presence.largeImageKey = "yuzu_logo"; |
||||
|
presence.largeImageText = "yuzu is an emulator for the Nintendo Switch"; |
||||
|
if (Core::System::GetInstance().IsPoweredOn()) { |
||||
|
presence.state = title.c_str(); |
||||
|
presence.details = "Currently in game"; |
||||
|
} else { |
||||
|
presence.details = "Not in game"; |
||||
|
} |
||||
|
presence.startTimestamp = start_time; |
||||
|
Discord_UpdatePresence(&presence); |
||||
|
} |
||||
|
} // namespace DiscordRPC
|
||||
@ -0,0 +1,20 @@ |
|||||
|
// Copyright 2018 Citra Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "yuzu/discord.h" |
||||
|
|
||||
|
namespace DiscordRPC { |
||||
|
|
||||
|
class DiscordImpl : public DiscordInterface { |
||||
|
public: |
||||
|
DiscordImpl(); |
||||
|
~DiscordImpl() override; |
||||
|
|
||||
|
void Pause() override; |
||||
|
void Update() override; |
||||
|
}; |
||||
|
|
||||
|
} // namespace DiscordRPC |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue