mirror of https://git.suyu.dev/suyu/suyu
Merge pull request #1332 from FearlessTobi/port-web-backend
Port web_service from Citramerge-requests/60/head
commit
b8b90ce6e6
@ -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 @@
|
|||||||
|
Subproject commit e32d001809c4aad56cef2a5321b54442d830174f
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 7d01cb01cb1a926ecb4c9c98b107ef3c26f59dfb
|
@ -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
|
Loading…
Reference in New Issue