Merge pull request #2897 from bunnei/telemetry-ui
Telemetry UI and final touches
This commit is contained in:
commit
22fc378fe9
@ -165,6 +165,8 @@ int main(int argc, char** argv) {
|
|||||||
break; // Expected case
|
break; // Expected case
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
|
||||||
|
|
||||||
while (emu_window->IsOpen()) {
|
while (emu_window->IsOpen()) {
|
||||||
system.RunLoop();
|
system.RunLoop();
|
||||||
}
|
}
|
||||||
|
@ -156,8 +156,12 @@ void Config::ReadValues() {
|
|||||||
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
|
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
|
||||||
|
|
||||||
// Web Service
|
// Web Service
|
||||||
|
Settings::values.enable_telemetry =
|
||||||
|
sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
|
||||||
Settings::values.telemetry_endpoint_url = sdl2_config->Get(
|
Settings::values.telemetry_endpoint_url = sdl2_config->Get(
|
||||||
"WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
|
"WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
|
||||||
|
Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", "");
|
||||||
|
Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::Reload() {
|
void Config::Reload() {
|
||||||
|
@ -176,7 +176,14 @@ use_gdbstub=false
|
|||||||
gdbstub_port=24689
|
gdbstub_port=24689
|
||||||
|
|
||||||
[WebService]
|
[WebService]
|
||||||
|
# Whether or not to enable telemetry
|
||||||
|
# 0: No, 1 (default): Yes
|
||||||
|
enable_telemetry =
|
||||||
# Endpoint URL for submitting telemetry data
|
# Endpoint URL for submitting telemetry data
|
||||||
telemetry_endpoint_url =
|
telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
|
||||||
|
# Username and token for Citra Web Service
|
||||||
|
# See https://services.citra-emu.org/ for more info
|
||||||
|
citra_username =
|
||||||
|
citra_token =
|
||||||
)";
|
)";
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ set(SRCS
|
|||||||
configuration/configure_graphics.cpp
|
configuration/configure_graphics.cpp
|
||||||
configuration/configure_input.cpp
|
configuration/configure_input.cpp
|
||||||
configuration/configure_system.cpp
|
configuration/configure_system.cpp
|
||||||
|
configuration/configure_web.cpp
|
||||||
debugger/graphics/graphics.cpp
|
debugger/graphics/graphics.cpp
|
||||||
debugger/graphics/graphics_breakpoint_observer.cpp
|
debugger/graphics/graphics_breakpoint_observer.cpp
|
||||||
debugger/graphics/graphics_breakpoints.cpp
|
debugger/graphics/graphics_breakpoints.cpp
|
||||||
@ -42,6 +43,7 @@ set(HEADERS
|
|||||||
configuration/configure_graphics.h
|
configuration/configure_graphics.h
|
||||||
configuration/configure_input.h
|
configuration/configure_input.h
|
||||||
configuration/configure_system.h
|
configuration/configure_system.h
|
||||||
|
configuration/configure_web.h
|
||||||
debugger/graphics/graphics.h
|
debugger/graphics/graphics.h
|
||||||
debugger/graphics/graphics_breakpoint_observer.h
|
debugger/graphics/graphics_breakpoint_observer.h
|
||||||
debugger/graphics/graphics_breakpoints.h
|
debugger/graphics/graphics_breakpoints.h
|
||||||
@ -71,6 +73,7 @@ set(UIS
|
|||||||
configuration/configure_graphics.ui
|
configuration/configure_graphics.ui
|
||||||
configuration/configure_input.ui
|
configuration/configure_input.ui
|
||||||
configuration/configure_system.ui
|
configuration/configure_system.ui
|
||||||
|
configuration/configure_web.ui
|
||||||
debugger/registers.ui
|
debugger/registers.ui
|
||||||
hotkeys.ui
|
hotkeys.ui
|
||||||
main.ui
|
main.ui
|
||||||
|
@ -139,10 +139,13 @@ void Config::ReadValues() {
|
|||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("WebService");
|
qt_config->beginGroup("WebService");
|
||||||
|
Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
|
||||||
Settings::values.telemetry_endpoint_url =
|
Settings::values.telemetry_endpoint_url =
|
||||||
qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
|
qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
|
||||||
.toString()
|
.toString()
|
||||||
.toStdString();
|
.toStdString();
|
||||||
|
Settings::values.citra_username = qt_config->value("citra_username").toString().toStdString();
|
||||||
|
Settings::values.citra_token = qt_config->value("citra_token").toString().toStdString();
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("UI");
|
qt_config->beginGroup("UI");
|
||||||
@ -194,6 +197,7 @@ void Config::ReadValues() {
|
|||||||
UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();
|
UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();
|
||||||
UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
|
UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
|
||||||
UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
|
UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
|
||||||
|
UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
}
|
}
|
||||||
@ -283,8 +287,11 @@ void Config::SaveValues() {
|
|||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("WebService");
|
qt_config->beginGroup("WebService");
|
||||||
|
qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
|
||||||
qt_config->setValue("telemetry_endpoint_url",
|
qt_config->setValue("telemetry_endpoint_url",
|
||||||
QString::fromStdString(Settings::values.telemetry_endpoint_url));
|
QString::fromStdString(Settings::values.telemetry_endpoint_url));
|
||||||
|
qt_config->setValue("citra_username", QString::fromStdString(Settings::values.citra_username));
|
||||||
|
qt_config->setValue("citra_token", QString::fromStdString(Settings::values.citra_token));
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("UI");
|
qt_config->beginGroup("UI");
|
||||||
@ -320,6 +327,7 @@ void Config::SaveValues() {
|
|||||||
qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
|
qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
|
||||||
qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
|
qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
|
||||||
qt_config->setValue("firstStart", UISettings::values.first_start);
|
qt_config->setValue("firstStart", UISettings::values.first_start);
|
||||||
|
qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
|
||||||
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>441</width>
|
<width>740</width>
|
||||||
<height>501</height>
|
<height>500</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -49,6 +49,11 @@
|
|||||||
<string>Debug</string>
|
<string>Debug</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="ConfigureWeb" name="webTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Web</string>
|
||||||
|
</attribute>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -97,6 +102,12 @@
|
|||||||
<header>configuration/configure_graphics.h</header>
|
<header>configuration/configure_graphics.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>ConfigureWeb</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>configuration/configure_web.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections>
|
<connections>
|
||||||
|
@ -23,5 +23,6 @@ void ConfigureDialog::applyConfiguration() {
|
|||||||
ui->graphicsTab->applyConfiguration();
|
ui->graphicsTab->applyConfiguration();
|
||||||
ui->audioTab->applyConfiguration();
|
ui->audioTab->applyConfiguration();
|
||||||
ui->debugTab->applyConfiguration();
|
ui->debugTab->applyConfiguration();
|
||||||
|
ui->webTab->applyConfiguration();
|
||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
}
|
}
|
||||||
|
52
src/citra_qt/configuration/configure_web.cpp
Normal file
52
src/citra_qt/configuration/configure_web.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "citra_qt/configuration/configure_web.h"
|
||||||
|
#include "core/settings.h"
|
||||||
|
#include "core/telemetry_session.h"
|
||||||
|
#include "ui_configure_web.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);
|
||||||
|
|
||||||
|
this->setConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigureWeb::~ConfigureWeb() {}
|
||||||
|
|
||||||
|
void ConfigureWeb::setConfiguration() {
|
||||||
|
ui->web_credentials_disclaimer->setWordWrap(true);
|
||||||
|
ui->telemetry_learn_more->setOpenExternalLinks(true);
|
||||||
|
ui->telemetry_learn_more->setText("<a "
|
||||||
|
"href='https://citra-emu.org/entry/"
|
||||||
|
"telemetry-and-why-thats-a-good-thing/'>Learn more</a>");
|
||||||
|
|
||||||
|
ui->web_signup_link->setOpenExternalLinks(true);
|
||||||
|
ui->web_signup_link->setText("<a href='https://services.citra-emu.org/'>Sign up</a>");
|
||||||
|
ui->web_token_info_link->setOpenExternalLinks(true);
|
||||||
|
ui->web_token_info_link->setText(
|
||||||
|
"<a href='https://citra-emu.org/wiki/citra-web-service/'>What is my token?</a>");
|
||||||
|
|
||||||
|
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
|
||||||
|
ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username));
|
||||||
|
ui->edit_token->setText(QString::fromStdString(Settings::values.citra_token));
|
||||||
|
ui->label_telemetry_id->setText("Telemetry ID: 0x" +
|
||||||
|
QString::number(Core::GetTelemetryId(), 16).toUpper());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureWeb::applyConfiguration() {
|
||||||
|
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
|
||||||
|
Settings::values.citra_username = ui->edit_username->text().toStdString();
|
||||||
|
Settings::values.citra_token = ui->edit_token->text().toStdString();
|
||||||
|
Settings::Apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfigureWeb::refreshTelemetryID() {
|
||||||
|
const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
|
||||||
|
ui->label_telemetry_id->setText("Telemetry ID: 0x" +
|
||||||
|
QString::number(new_telemetry_id, 16).toUpper());
|
||||||
|
}
|
30
src/citra_qt/configuration/configure_web.h
Normal file
30
src/citra_qt/configuration/configure_web.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ConfigureWeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConfigureWeb : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ConfigureWeb(QWidget* parent = nullptr);
|
||||||
|
~ConfigureWeb();
|
||||||
|
|
||||||
|
void applyConfiguration();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void refreshTelemetryID();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setConfiguration();
|
||||||
|
|
||||||
|
std::unique_ptr<Ui::ConfigureWeb> ui;
|
||||||
|
};
|
153
src/citra_qt/configuration/configure_web.ui
Normal file
153
src/citra_qt/configuration/configure_web.ui
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
<?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>400</width>
|
||||||
|
<height>300</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>Citra Web Service</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayoutCitraWebService">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="web_credentials_disclaimer">
|
||||||
|
<property name="text">
|
||||||
|
<string>By providing your username and token, you agree to allow Citra to collect additional usage data, which may include user identifying information.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QGridLayout" name="gridLayoutCitraUsername">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_username">
|
||||||
|
<property name="text">
|
||||||
|
<string>Username: </string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<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="1">
|
||||||
|
<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="0">
|
||||||
|
<widget class="QLabel" name="web_signup_link">
|
||||||
|
<property name="text">
|
||||||
|
<string>Sign up</string>
|
||||||
|
</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>
|
||||||
|
</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 Citra 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>
|
||||||
|
<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>
|
@ -48,6 +48,47 @@
|
|||||||
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
|
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
|
||||||
|
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
|
||||||
|
* user. This is 32-bits - if we have more than 32 callouts, we should retire and recyle old ones.
|
||||||
|
*/
|
||||||
|
enum class CalloutFlag : uint32_t {
|
||||||
|
Telemetry = 0x1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
|
||||||
|
if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UISettings::values.callout_flags |= static_cast<uint32_t>(flag);
|
||||||
|
|
||||||
|
QMessageBox msg;
|
||||||
|
msg.setText(message);
|
||||||
|
msg.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
msg.setStyleSheet("QLabel{min-width: 900px;}");
|
||||||
|
msg.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GMainWindow::ShowCallouts() {
|
||||||
|
static const QString telemetry_message =
|
||||||
|
tr("To help improve Citra, the Citra Team collects anonymous usage data. No private or "
|
||||||
|
"personally identifying information is collected. This data helps us to understand how "
|
||||||
|
"people use Citra and prioritize our efforts. Furthermore, it helps us to more easily "
|
||||||
|
"identify emulation bugs and performance issues. This data includes:<ul><li>Information"
|
||||||
|
" about the version of Citra you are using</li><li>Performance data about the games you "
|
||||||
|
"play</li><li>Your configuration settings</li><li>Information about your computer "
|
||||||
|
"hardware</li><li>Emulation errors and crash information</li></ul>By default, this "
|
||||||
|
"feature is enabled. To disable this feature, click 'Emulation' from the menu and then "
|
||||||
|
"select 'Configure...'. Then, on the 'Web' tab, uncheck 'Share anonymous usage data with"
|
||||||
|
" the Citra team'. <br/><br/>By using this software, you agree to the above terms.<br/>"
|
||||||
|
"<br/><a href='https://citra-emu.org/entry/telemetry-and-why-thats-a-good-thing/'>Learn "
|
||||||
|
"more</a>");
|
||||||
|
ShowCalloutMessage(telemetry_message, CalloutFlag::Telemetry);
|
||||||
|
}
|
||||||
|
|
||||||
GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
||||||
Pica::g_debug_context = Pica::DebugContext::Construct();
|
Pica::g_debug_context = Pica::DebugContext::Construct();
|
||||||
setAcceptDrops(true);
|
setAcceptDrops(true);
|
||||||
@ -73,6 +114,9 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
|||||||
|
|
||||||
UpdateUITheme();
|
UpdateUITheme();
|
||||||
|
|
||||||
|
// Show one-time "callout" messages to the user
|
||||||
|
ShowCallouts();
|
||||||
|
|
||||||
QStringList args = QApplication::arguments();
|
QStringList args = QApplication::arguments();
|
||||||
if (args.length() >= 2) {
|
if (args.length() >= 2) {
|
||||||
BootGame(args[1]);
|
BootGame(args[1]);
|
||||||
@ -320,6 +364,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
|||||||
|
|
||||||
const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
|
const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
|
||||||
|
|
||||||
|
Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
|
||||||
|
|
||||||
if (result != Core::System::ResultStatus::Success) {
|
if (result != Core::System::ResultStatus::Success) {
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case Core::System::ResultStatus::ErrorGetLoader:
|
case Core::System::ResultStatus::ErrorGetLoader:
|
||||||
|
@ -80,6 +80,8 @@ private:
|
|||||||
void BootGame(const QString& filename);
|
void BootGame(const QString& filename);
|
||||||
void ShutdownGame();
|
void ShutdownGame();
|
||||||
|
|
||||||
|
void ShowCallouts();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the filename in the recently loaded files list.
|
* Stores the filename in the recently loaded files list.
|
||||||
* The new filename is stored at the beginning of the recently loaded files list.
|
* The new filename is stored at the beginning of the recently loaded files list.
|
||||||
|
@ -48,6 +48,8 @@ struct Values {
|
|||||||
|
|
||||||
// Shortcut name <Shortcut, context>
|
// Shortcut name <Shortcut, context>
|
||||||
std::vector<Shortcut> shortcuts;
|
std::vector<Shortcut> shortcuts;
|
||||||
|
|
||||||
|
uint32_t callout_flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Values values;
|
extern Values values;
|
||||||
|
@ -130,7 +130,10 @@ struct Values {
|
|||||||
u16 gdbstub_port;
|
u16 gdbstub_port;
|
||||||
|
|
||||||
// WebService
|
// WebService
|
||||||
|
bool enable_telemetry;
|
||||||
std::string telemetry_endpoint_url;
|
std::string telemetry_endpoint_url;
|
||||||
|
std::string citra_username;
|
||||||
|
std::string citra_token;
|
||||||
} extern values;
|
} extern values;
|
||||||
|
|
||||||
// a special value for Values::region_value indicating that citra will automatically select a region
|
// a special value for Values::region_value indicating that citra will automatically select a region
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cryptopp/osrng.h>
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/file_util.h"
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/x64/cpu_detect.h"
|
#include "common/x64/cpu_detect.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
@ -29,12 +31,65 @@ static const char* CpuVendorToStr(Common::CPUVendor vendor) {
|
|||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static u64 GenerateTelemetryId() {
|
||||||
|
u64 telemetry_id{};
|
||||||
|
CryptoPP::AutoSeededRandomPool rng;
|
||||||
|
rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&telemetry_id), sizeof(u64));
|
||||||
|
return telemetry_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GetTelemetryId() {
|
||||||
|
u64 telemetry_id{};
|
||||||
|
static const std::string& filename{FileUtil::GetUserPath(D_CONFIG_IDX) + "telemetry_id"};
|
||||||
|
|
||||||
|
if (FileUtil::Exists(filename)) {
|
||||||
|
FileUtil::IOFile file(filename, "rb");
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
file.ReadBytes(&telemetry_id, sizeof(u64));
|
||||||
|
} else {
|
||||||
|
FileUtil::IOFile file(filename, "wb");
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
telemetry_id = GenerateTelemetryId();
|
||||||
|
file.WriteBytes(&telemetry_id, sizeof(u64));
|
||||||
|
}
|
||||||
|
|
||||||
|
return telemetry_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 RegenerateTelemetryId() {
|
||||||
|
const u64 new_telemetry_id{GenerateTelemetryId()};
|
||||||
|
static const std::string& filename{FileUtil::GetUserPath(D_CONFIG_IDX) + "telemetry_id"};
|
||||||
|
|
||||||
|
FileUtil::IOFile file(filename, "wb");
|
||||||
|
if (!file.IsOpen()) {
|
||||||
|
LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
file.WriteBytes(&new_telemetry_id, sizeof(u64));
|
||||||
|
return new_telemetry_id;
|
||||||
|
}
|
||||||
|
|
||||||
TelemetrySession::TelemetrySession() {
|
TelemetrySession::TelemetrySession() {
|
||||||
#ifdef ENABLE_WEB_SERVICE
|
#ifdef ENABLE_WEB_SERVICE
|
||||||
backend = std::make_unique<WebService::TelemetryJson>();
|
if (Settings::values.enable_telemetry) {
|
||||||
|
backend = std::make_unique<WebService::TelemetryJson>(
|
||||||
|
Settings::values.telemetry_endpoint_url, Settings::values.citra_username,
|
||||||
|
Settings::values.citra_token);
|
||||||
|
} else {
|
||||||
|
backend = std::make_unique<Telemetry::NullVisitor>();
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
backend = std::make_unique<Telemetry::NullVisitor>();
|
backend = std::make_unique<Telemetry::NullVisitor>();
|
||||||
#endif
|
#endif
|
||||||
|
// Log one-time top-level information
|
||||||
|
AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
|
||||||
|
|
||||||
// Log one-time session start information
|
// Log one-time session start information
|
||||||
const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>(
|
const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
std::chrono::system_clock::now().time_since_epoch())
|
std::chrono::system_clock::now().time_since_epoch())
|
||||||
|
@ -35,4 +35,16 @@ private:
|
|||||||
std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
|
std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets TelemetryId, a unique identifier used for the user's telemetry sessions.
|
||||||
|
* @returns The current TelemetryId for the session.
|
||||||
|
*/
|
||||||
|
u64 GetTelemetryId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regenerates TelemetryId, a unique identifier used for the user's telemetry sessions.
|
||||||
|
* @returns The new TelemetryId that was generated.
|
||||||
|
*/
|
||||||
|
u64 RegenerateTelemetryId();
|
||||||
|
|
||||||
} // namespace Core
|
} // namespace Core
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "core/settings.h"
|
|
||||||
#include "web_service/telemetry_json.h"
|
#include "web_service/telemetry_json.h"
|
||||||
#include "web_service/web_backend.h"
|
#include "web_service/web_backend.h"
|
||||||
|
|
||||||
@ -81,7 +80,7 @@ void TelemetryJson::Complete() {
|
|||||||
SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
|
SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
|
||||||
SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
|
SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
|
||||||
SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
|
SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
|
||||||
PostJson(Settings::values.telemetry_endpoint_url, TopSection().dump());
|
PostJson(endpoint_url, TopSection().dump(), true, username, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace WebService
|
} // namespace WebService
|
||||||
|
@ -17,7 +17,9 @@ namespace WebService {
|
|||||||
*/
|
*/
|
||||||
class TelemetryJson : public Telemetry::VisitorInterface {
|
class TelemetryJson : public Telemetry::VisitorInterface {
|
||||||
public:
|
public:
|
||||||
TelemetryJson() = default;
|
TelemetryJson(const std::string& endpoint_url, const std::string& username,
|
||||||
|
const std::string& token)
|
||||||
|
: endpoint_url(endpoint_url), username(username), token(token) {}
|
||||||
~TelemetryJson() = default;
|
~TelemetryJson() = default;
|
||||||
|
|
||||||
void Visit(const Telemetry::Field<bool>& field) override;
|
void Visit(const Telemetry::Field<bool>& field) override;
|
||||||
@ -49,6 +51,9 @@ private:
|
|||||||
|
|
||||||
nlohmann::json output;
|
nlohmann::json output;
|
||||||
std::array<nlohmann::json, 7> sections;
|
std::array<nlohmann::json, 7> sections;
|
||||||
|
std::string endpoint_url;
|
||||||
|
std::string username;
|
||||||
|
std::string token;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace WebService
|
} // namespace WebService
|
||||||
|
@ -2,51 +2,62 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <thread>
|
||||||
#include <cpr/cpr.h>
|
#include <cpr/cpr.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "web_service/web_backend.h"
|
#include "web_service/web_backend.h"
|
||||||
|
|
||||||
namespace WebService {
|
namespace WebService {
|
||||||
|
|
||||||
static constexpr char API_VERSION[]{"1"};
|
static constexpr char API_VERSION[]{"1"};
|
||||||
static constexpr char ENV_VAR_USERNAME[]{"CITRA_WEB_SERVICES_USERNAME"};
|
|
||||||
static constexpr char ENV_VAR_TOKEN[]{"CITRA_WEB_SERVICES_TOKEN"};
|
|
||||||
|
|
||||||
static std::string GetEnvironmentVariable(const char* name) {
|
static std::unique_ptr<cpr::Session> g_session;
|
||||||
const char* value{getenv(name)};
|
|
||||||
if (value) {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& GetUsername() {
|
void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
|
||||||
static const std::string username{GetEnvironmentVariable(ENV_VAR_USERNAME)};
|
const std::string& username, const std::string& token) {
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string& GetToken() {
|
|
||||||
static const std::string token{GetEnvironmentVariable(ENV_VAR_TOKEN)};
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PostJson(const std::string& url, const std::string& data) {
|
|
||||||
if (url.empty()) {
|
if (url.empty()) {
|
||||||
LOG_ERROR(WebService, "URL is invalid");
|
LOG_ERROR(WebService, "URL is invalid");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetUsername().empty() || GetToken().empty()) {
|
const bool are_credentials_provided{!token.empty() && !username.empty()};
|
||||||
LOG_ERROR(WebService, "Environment variables %s and %s must be set to POST JSON",
|
if (!allow_anonymous && !are_credentials_provided) {
|
||||||
ENV_VAR_USERNAME, ENV_VAR_TOKEN);
|
LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cpr::PostAsync(cpr::Url{url}, cpr::Body{data}, cpr::Header{{"Content-Type", "application/json"},
|
#ifdef _WIN32
|
||||||
{"x-username", GetUsername()},
|
// On Windows, CPR/libcurl does not properly initialize Winsock. The below code is used to
|
||||||
{"x-token", GetToken()},
|
// initialize Winsock globally, which fixes this problem. Without this, only the first CPR
|
||||||
{"api-version", API_VERSION}});
|
// session will properly be created, and subsequent ones will fail.
|
||||||
|
WSADATA wsa_data;
|
||||||
|
const int wsa_result{WSAStartup(MAKEWORD(2, 2), &wsa_data)};
|
||||||
|
if (wsa_result) {
|
||||||
|
LOG_CRITICAL(WebService, "WSAStartup failed: %d", wsa_result);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Built request header
|
||||||
|
cpr::Header header;
|
||||||
|
if (are_credentials_provided) {
|
||||||
|
// Authenticated request if credentials are provided
|
||||||
|
header = {{"Content-Type", "application/json"},
|
||||||
|
{"x-username", username.c_str()},
|
||||||
|
{"x-token", token.c_str()},
|
||||||
|
{"api-version", API_VERSION}};
|
||||||
|
} else {
|
||||||
|
// Otherwise, anonymous request
|
||||||
|
header = cpr::Header{{"Content-Type", "application/json"}, {"api-version", API_VERSION}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post JSON asynchronously
|
||||||
|
static cpr::AsyncResponse future;
|
||||||
|
future = cpr::PostAsync(cpr::Url{url.c_str()}, cpr::Body{data.c_str()}, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace WebService
|
} // namespace WebService
|
||||||
|
@ -9,23 +9,15 @@
|
|||||||
|
|
||||||
namespace WebService {
|
namespace WebService {
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current username for accessing services.citra-emu.org.
|
|
||||||
* @returns Username as a string, empty if not set.
|
|
||||||
*/
|
|
||||||
const std::string& GetUsername();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the current token for accessing services.citra-emu.org.
|
|
||||||
* @returns Token as a string, empty if not set.
|
|
||||||
*/
|
|
||||||
const std::string& GetToken();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Posts JSON to services.citra-emu.org.
|
* Posts JSON to services.citra-emu.org.
|
||||||
* @param url URL of the services.citra-emu.org endpoint to post data to.
|
* @param url URL of the services.citra-emu.org endpoint to post data to.
|
||||||
* @param data String of JSON data to use for the body of the POST request.
|
* @param data String of JSON data to use for the body of the POST request.
|
||||||
|
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
|
||||||
|
* @param username Citra username to use for authentication.
|
||||||
|
* @param token Citra token to use for authentication.
|
||||||
*/
|
*/
|
||||||
void PostJson(const std::string& url, const std::string& data);
|
void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
|
||||||
|
const std::string& username = {}, const std::string& token = {});
|
||||||
|
|
||||||
} // namespace WebService
|
} // namespace WebService
|
||||||
|
Loading…
Reference in New Issue
Block a user