summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar bunnei2017-08-26 20:15:15 -0400
committerGravatar GitHub2017-08-26 20:15:15 -0400
commit22fc378fe9f3314b08d81ffaaf57fd8688e9e3cc (patch)
treed263eaca71ee08c3f9b81441b42bcd0e35977510
parentSidebySide Layout (#2859) (diff)
parentweb_backend: Fix CPR bug where Winsock is not properly initializing. (diff)
downloadyuzu-22fc378fe9f3314b08d81ffaaf57fd8688e9e3cc.tar.gz
yuzu-22fc378fe9f3314b08d81ffaaf57fd8688e9e3cc.tar.xz
yuzu-22fc378fe9f3314b08d81ffaaf57fd8688e9e3cc.zip
Merge pull request #2897 from bunnei/telemetry-ui
Telemetry UI and final touches
-rw-r--r--src/citra/citra.cpp2
-rw-r--r--src/citra/config.cpp4
-rw-r--r--src/citra/default_ini.h9
-rw-r--r--src/citra_qt/CMakeLists.txt3
-rw-r--r--src/citra_qt/configuration/config.cpp8
-rw-r--r--src/citra_qt/configuration/configure.ui15
-rw-r--r--src/citra_qt/configuration/configure_dialog.cpp1
-rw-r--r--src/citra_qt/configuration/configure_web.cpp52
-rw-r--r--src/citra_qt/configuration/configure_web.h30
-rw-r--r--src/citra_qt/configuration/configure_web.ui153
-rw-r--r--src/citra_qt/main.cpp46
-rw-r--r--src/citra_qt/main.h2
-rw-r--r--src/citra_qt/ui_settings.h2
-rw-r--r--src/core/settings.h3
-rw-r--r--src/core/telemetry_session.cpp57
-rw-r--r--src/core/telemetry_session.h12
-rw-r--r--src/web_service/telemetry_json.cpp3
-rw-r--r--src/web_service/telemetry_json.h7
-rw-r--r--src/web_service/web_backend.cpp67
-rw-r--r--src/web_service/web_backend.h18
20 files changed, 446 insertions, 48 deletions
diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp
index 14574e56c..e524c5535 100644
--- a/src/citra/citra.cpp
+++ b/src/citra/citra.cpp
@@ -165,6 +165,8 @@ int main(int argc, char** argv) {
165 break; // Expected case 165 break; // Expected case
166 } 166 }
167 167
168 Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "SDL");
169
168 while (emu_window->IsOpen()) { 170 while (emu_window->IsOpen()) {
169 system.RunLoop(); 171 system.RunLoop();
170 } 172 }
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index 73846ed91..3869b6b5d 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -156,8 +156,12 @@ void Config::ReadValues() {
156 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); 156 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
157 157
158 // Web Service 158 // Web Service
159 Settings::values.enable_telemetry =
160 sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
159 Settings::values.telemetry_endpoint_url = sdl2_config->Get( 161 Settings::values.telemetry_endpoint_url = sdl2_config->Get(
160 "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry"); 162 "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
163 Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", "");
164 Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", "");
161} 165}
162 166
163void Config::Reload() { 167void Config::Reload() {
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index 9ea779dd8..ea02a788d 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -176,7 +176,14 @@ use_gdbstub=false
176gdbstub_port=24689 176gdbstub_port=24689
177 177
178[WebService] 178[WebService]
179# Whether or not to enable telemetry
180# 0: No, 1 (default): Yes
181enable_telemetry =
179# Endpoint URL for submitting telemetry data 182# Endpoint URL for submitting telemetry data
180telemetry_endpoint_url = 183telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
184# Username and token for Citra Web Service
185# See https://services.citra-emu.org/ for more info
186citra_username =
187citra_token =
181)"; 188)";
182} 189}
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index f364b2284..e0a19fd9e 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -12,6 +12,7 @@ set(SRCS
12 configuration/configure_graphics.cpp 12 configuration/configure_graphics.cpp
13 configuration/configure_input.cpp 13 configuration/configure_input.cpp
14 configuration/configure_system.cpp 14 configuration/configure_system.cpp
15 configuration/configure_web.cpp
15 debugger/graphics/graphics.cpp 16 debugger/graphics/graphics.cpp
16 debugger/graphics/graphics_breakpoint_observer.cpp 17 debugger/graphics/graphics_breakpoint_observer.cpp
17 debugger/graphics/graphics_breakpoints.cpp 18 debugger/graphics/graphics_breakpoints.cpp
@@ -42,6 +43,7 @@ set(HEADERS
42 configuration/configure_graphics.h 43 configuration/configure_graphics.h
43 configuration/configure_input.h 44 configuration/configure_input.h
44 configuration/configure_system.h 45 configuration/configure_system.h
46 configuration/configure_web.h
45 debugger/graphics/graphics.h 47 debugger/graphics/graphics.h
46 debugger/graphics/graphics_breakpoint_observer.h 48 debugger/graphics/graphics_breakpoint_observer.h
47 debugger/graphics/graphics_breakpoints.h 49 debugger/graphics/graphics_breakpoints.h
@@ -71,6 +73,7 @@ set(UIS
71 configuration/configure_graphics.ui 73 configuration/configure_graphics.ui
72 configuration/configure_input.ui 74 configuration/configure_input.ui
73 configuration/configure_system.ui 75 configuration/configure_system.ui
76 configuration/configure_web.ui
74 debugger/registers.ui 77 debugger/registers.ui
75 hotkeys.ui 78 hotkeys.ui
76 main.ui 79 main.ui
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 6e42db007..e2dceaa4c 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -139,10 +139,13 @@ void Config::ReadValues() {
139 qt_config->endGroup(); 139 qt_config->endGroup();
140 140
141 qt_config->beginGroup("WebService"); 141 qt_config->beginGroup("WebService");
142 Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
142 Settings::values.telemetry_endpoint_url = 143 Settings::values.telemetry_endpoint_url =
143 qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry") 144 qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
144 .toString() 145 .toString()
145 .toStdString(); 146 .toStdString();
147 Settings::values.citra_username = qt_config->value("citra_username").toString().toStdString();
148 Settings::values.citra_token = qt_config->value("citra_token").toString().toStdString();
146 qt_config->endGroup(); 149 qt_config->endGroup();
147 150
148 qt_config->beginGroup("UI"); 151 qt_config->beginGroup("UI");
@@ -194,6 +197,7 @@ void Config::ReadValues() {
194 UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); 197 UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();
195 UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); 198 UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
196 UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); 199 UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
200 UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
197 201
198 qt_config->endGroup(); 202 qt_config->endGroup();
199} 203}
@@ -283,8 +287,11 @@ void Config::SaveValues() {
283 qt_config->endGroup(); 287 qt_config->endGroup();
284 288
285 qt_config->beginGroup("WebService"); 289 qt_config->beginGroup("WebService");
290 qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
286 qt_config->setValue("telemetry_endpoint_url", 291 qt_config->setValue("telemetry_endpoint_url",
287 QString::fromStdString(Settings::values.telemetry_endpoint_url)); 292 QString::fromStdString(Settings::values.telemetry_endpoint_url));
293 qt_config->setValue("citra_username", QString::fromStdString(Settings::values.citra_username));
294 qt_config->setValue("citra_token", QString::fromStdString(Settings::values.citra_token));
288 qt_config->endGroup(); 295 qt_config->endGroup();
289 296
290 qt_config->beginGroup("UI"); 297 qt_config->beginGroup("UI");
@@ -320,6 +327,7 @@ void Config::SaveValues() {
320 qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); 327 qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
321 qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); 328 qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
322 qt_config->setValue("firstStart", UISettings::values.first_start); 329 qt_config->setValue("firstStart", UISettings::values.first_start);
330 qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
323 331
324 qt_config->endGroup(); 332 qt_config->endGroup();
325} 333}
diff --git a/src/citra_qt/configuration/configure.ui b/src/citra_qt/configuration/configure.ui
index 85e206e42..6abd1917e 100644
--- a/src/citra_qt/configuration/configure.ui
+++ b/src/citra_qt/configuration/configure.ui
@@ -6,8 +6,8 @@
6 <rect> 6 <rect>
7 <x>0</x> 7 <x>0</x>
8 <y>0</y> 8 <y>0</y>
9 <width>441</width> 9 <width>740</width>
10 <height>501</height> 10 <height>500</height>
11 </rect> 11 </rect>
12 </property> 12 </property>
13 <property name="windowTitle"> 13 <property name="windowTitle">
@@ -49,6 +49,11 @@
49 <string>Debug</string> 49 <string>Debug</string>
50 </attribute> 50 </attribute>
51 </widget> 51 </widget>
52 <widget class="ConfigureWeb" name="webTab">
53 <attribute name="title">
54 <string>Web</string>
55 </attribute>
56 </widget>
52 </widget> 57 </widget>
53 </item> 58 </item>
54 <item> 59 <item>
@@ -97,6 +102,12 @@
97 <header>configuration/configure_graphics.h</header> 102 <header>configuration/configure_graphics.h</header>
98 <container>1</container> 103 <container>1</container>
99 </customwidget> 104 </customwidget>
105 <customwidget>
106 <class>ConfigureWeb</class>
107 <extends>QWidget</extends>
108 <header>configuration/configure_web.h</header>
109 <container>1</container>
110 </customwidget>
100 </customwidgets> 111 </customwidgets>
101 <resources/> 112 <resources/>
102 <connections> 113 <connections>
diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp
index dfc8c03a7..b87dc0e6c 100644
--- a/src/citra_qt/configuration/configure_dialog.cpp
+++ b/src/citra_qt/configuration/configure_dialog.cpp
@@ -23,5 +23,6 @@ void ConfigureDialog::applyConfiguration() {
23 ui->graphicsTab->applyConfiguration(); 23 ui->graphicsTab->applyConfiguration();
24 ui->audioTab->applyConfiguration(); 24 ui->audioTab->applyConfiguration();
25 ui->debugTab->applyConfiguration(); 25 ui->debugTab->applyConfiguration();
26 ui->webTab->applyConfiguration();
26 Settings::Apply(); 27 Settings::Apply();
27} 28}
diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp
new file mode 100644
index 000000000..8715fb018
--- /dev/null
+++ b/src/citra_qt/configuration/configure_web.cpp
@@ -0,0 +1,52 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "citra_qt/configuration/configure_web.h"
6#include "core/settings.h"
7#include "core/telemetry_session.h"
8#include "ui_configure_web.h"
9
10ConfigureWeb::ConfigureWeb(QWidget* parent)
11 : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) {
12 ui->setupUi(this);
13 connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
14 &ConfigureWeb::refreshTelemetryID);
15
16 this->setConfiguration();
17}
18
19ConfigureWeb::~ConfigureWeb() {}
20
21void ConfigureWeb::setConfiguration() {
22 ui->web_credentials_disclaimer->setWordWrap(true);
23 ui->telemetry_learn_more->setOpenExternalLinks(true);
24 ui->telemetry_learn_more->setText("<a "
25 "href='https://citra-emu.org/entry/"
26 "telemetry-and-why-thats-a-good-thing/'>Learn more</a>");
27
28 ui->web_signup_link->setOpenExternalLinks(true);
29 ui->web_signup_link->setText("<a href='https://services.citra-emu.org/'>Sign up</a>");
30 ui->web_token_info_link->setOpenExternalLinks(true);
31 ui->web_token_info_link->setText(
32 "<a href='https://citra-emu.org/wiki/citra-web-service/'>What is my token?</a>");
33
34 ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
35 ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username));
36 ui->edit_token->setText(QString::fromStdString(Settings::values.citra_token));
37 ui->label_telemetry_id->setText("Telemetry ID: 0x" +
38 QString::number(Core::GetTelemetryId(), 16).toUpper());
39}
40
41void ConfigureWeb::applyConfiguration() {
42 Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
43 Settings::values.citra_username = ui->edit_username->text().toStdString();
44 Settings::values.citra_token = ui->edit_token->text().toStdString();
45 Settings::Apply();
46}
47
48void ConfigureWeb::refreshTelemetryID() {
49 const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
50 ui->label_telemetry_id->setText("Telemetry ID: 0x" +
51 QString::number(new_telemetry_id, 16).toUpper());
52}
diff --git a/src/citra_qt/configuration/configure_web.h b/src/citra_qt/configuration/configure_web.h
new file mode 100644
index 000000000..20bc254b9
--- /dev/null
+++ b/src/citra_qt/configuration/configure_web.h
@@ -0,0 +1,30 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <QWidget>
9
10namespace Ui {
11class ConfigureWeb;
12}
13
14class ConfigureWeb : public QWidget {
15 Q_OBJECT
16
17public:
18 explicit ConfigureWeb(QWidget* parent = nullptr);
19 ~ConfigureWeb();
20
21 void applyConfiguration();
22
23public slots:
24 void refreshTelemetryID();
25
26private:
27 void setConfiguration();
28
29 std::unique_ptr<Ui::ConfigureWeb> ui;
30};
diff --git a/src/citra_qt/configuration/configure_web.ui b/src/citra_qt/configuration/configure_web.ui
new file mode 100644
index 000000000..d8d283fad
--- /dev/null
+++ b/src/citra_qt/configuration/configure_web.ui
@@ -0,0 +1,153 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigureWeb</class>
4 <widget class="QWidget" name="ConfigureWeb">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>400</width>
10 <height>300</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>Form</string>
15 </property>
16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="verticalLayout_3">
19 <item>
20 <widget class="QGroupBox" name="groupBoxWebConfig">
21 <property name="title">
22 <string>Citra Web Service</string>
23 </property>
24 <layout class="QVBoxLayout" name="verticalLayoutCitraWebService">
25 <item>
26 <widget class="QLabel" name="web_credentials_disclaimer">
27 <property name="text">
28 <string>By providing your username and token, you agree to allow Citra to collect additional usage data, which may include user identifying information.</string>
29 </property>
30 </widget>
31 </item>
32 <item>
33 <layout class="QGridLayout" name="gridLayoutCitraUsername">
34 <item row="0" column="0">
35 <widget class="QLabel" name="label_username">
36 <property name="text">
37 <string>Username: </string>
38 </property>
39 </widget>
40 </item>
41 <item row="0" column="1">
42 <widget class="QLineEdit" name="edit_username">
43 <property name="maxLength">
44 <number>36</number>
45 </property>
46 </widget>
47 </item>
48 <item row="1" column="0">
49 <widget class="QLabel" name="label_token">
50 <property name="text">
51 <string>Token: </string>
52 </property>
53 </widget>
54 </item>
55 <item row="1" column="1">
56 <widget class="QLineEdit" name="edit_token">
57 <property name="maxLength">
58 <number>36</number>
59 </property>
60 <property name="echoMode">
61 <enum>QLineEdit::Password</enum>
62 </property>
63 </widget>
64 </item>
65 <item row="2" column="0">
66 <widget class="QLabel" name="web_signup_link">
67 <property name="text">
68 <string>Sign up</string>
69 </property>
70 </widget>
71 </item>
72 <item row="2" column="1">
73 <widget class="QLabel" name="web_token_info_link">
74 <property name="text">
75 <string>What is my token?</string>
76 </property>
77 </widget>
78 </item>
79 </layout>
80 </item>
81 </layout>
82 </widget>
83 </item>
84 <item>
85 <widget class="QGroupBox" name="groupBox">
86 <property name="title">
87 <string>Telemetry</string>
88 </property>
89 <layout class="QVBoxLayout" name="verticalLayout_2">
90 <item>
91 <widget class="QCheckBox" name="toggle_telemetry">
92 <property name="text">
93 <string>Share anonymous usage data with the Citra team</string>
94 </property>
95 </widget>
96 </item>
97 <item>
98 <widget class="QLabel" name="telemetry_learn_more">
99 <property name="text">
100 <string>Learn more</string>
101 </property>
102 </widget>
103 </item>
104 <item>
105 <layout class="QGridLayout" name="gridLayoutTelemetryId">
106 <item row="0" column="0">
107 <widget class="QLabel" name="label_telemetry_id">
108 <property name="text">
109 <string>Telemetry ID:</string>
110 </property>
111 </widget>
112 </item>
113 <item row="0" column="1">
114 <widget class="QPushButton" name="button_regenerate_telemetry_id">
115 <property name="sizePolicy">
116 <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
117 <horstretch>0</horstretch>
118 <verstretch>0</verstretch>
119 </sizepolicy>
120 </property>
121 <property name="layoutDirection">
122 <enum>Qt::RightToLeft</enum>
123 </property>
124 <property name="text">
125 <string>Regenerate</string>
126 </property>
127 </widget>
128 </item>
129 </layout>
130 </item>
131 </layout>
132 </widget>
133 </item>
134 </layout>
135 </item>
136 <item>
137 <spacer name="verticalSpacer">
138 <property name="orientation">
139 <enum>Qt::Vertical</enum>
140 </property>
141 <property name="sizeHint" stdset="0">
142 <size>
143 <width>20</width>
144 <height>40</height>
145 </size>
146 </property>
147 </spacer>
148 </item>
149 </layout>
150 </widget>
151 <resources/>
152 <connections/>
153</ui>
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index c1ae0ccc8..8adbcfe86 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -48,6 +48,47 @@
48Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); 48Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
49#endif 49#endif
50 50
51/**
52 * "Callouts" are one-time instructional messages shown to the user. In the config settings, there
53 * is a bitfield "callout_flags" options, used to track if a message has already been shown to the
54 * user. This is 32-bits - if we have more than 32 callouts, we should retire and recyle old ones.
55 */
56enum class CalloutFlag : uint32_t {
57 Telemetry = 0x1,
58};
59
60static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
61 if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) {
62 return;
63 }
64
65 UISettings::values.callout_flags |= static_cast<uint32_t>(flag);
66
67 QMessageBox msg;
68 msg.setText(message);
69 msg.setStandardButtons(QMessageBox::Ok);
70 msg.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
71 msg.setStyleSheet("QLabel{min-width: 900px;}");
72 msg.exec();
73}
74
75void GMainWindow::ShowCallouts() {
76 static const QString telemetry_message =
77 tr("To help improve Citra, the Citra Team collects anonymous usage data. No private or "
78 "personally identifying information is collected. This data helps us to understand how "
79 "people use Citra and prioritize our efforts. Furthermore, it helps us to more easily "
80 "identify emulation bugs and performance issues. This data includes:<ul><li>Information"
81 " about the version of Citra you are using</li><li>Performance data about the games you "
82 "play</li><li>Your configuration settings</li><li>Information about your computer "
83 "hardware</li><li>Emulation errors and crash information</li></ul>By default, this "
84 "feature is enabled. To disable this feature, click 'Emulation' from the menu and then "
85 "select 'Configure...'. Then, on the 'Web' tab, uncheck 'Share anonymous usage data with"
86 " the Citra team'. <br/><br/>By using this software, you agree to the above terms.<br/>"
87 "<br/><a href='https://citra-emu.org/entry/telemetry-and-why-thats-a-good-thing/'>Learn "
88 "more</a>");
89 ShowCalloutMessage(telemetry_message, CalloutFlag::Telemetry);
90}
91
51GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { 92GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
52 Pica::g_debug_context = Pica::DebugContext::Construct(); 93 Pica::g_debug_context = Pica::DebugContext::Construct();
53 setAcceptDrops(true); 94 setAcceptDrops(true);
@@ -73,6 +114,9 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
73 114
74 UpdateUITheme(); 115 UpdateUITheme();
75 116
117 // Show one-time "callout" messages to the user
118 ShowCallouts();
119
76 QStringList args = QApplication::arguments(); 120 QStringList args = QApplication::arguments();
77 if (args.length() >= 2) { 121 if (args.length() >= 2) {
78 BootGame(args[1]); 122 BootGame(args[1]);
@@ -320,6 +364,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
320 364
321 const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())}; 365 const Core::System::ResultStatus result{system.Load(render_window, filename.toStdString())};
322 366
367 Core::Telemetry().AddField(Telemetry::FieldType::App, "Frontend", "Qt");
368
323 if (result != Core::System::ResultStatus::Success) { 369 if (result != Core::System::ResultStatus::Success) {
324 switch (result) { 370 switch (result) {
325 case Core::System::ResultStatus::ErrorGetLoader: 371 case Core::System::ResultStatus::ErrorGetLoader:
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 360de2ced..d59a6d67d 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -80,6 +80,8 @@ private:
80 void BootGame(const QString& filename); 80 void BootGame(const QString& filename);
81 void ShutdownGame(); 81 void ShutdownGame();
82 82
83 void ShowCallouts();
84
83 /** 85 /**
84 * Stores the filename in the recently loaded files list. 86 * Stores the filename in the recently loaded files list.
85 * The new filename is stored at the beginning of the recently loaded files list. 87 * The new filename is stored at the beginning of the recently loaded files list.
diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h
index 025c73f84..d85c92765 100644
--- a/src/citra_qt/ui_settings.h
+++ b/src/citra_qt/ui_settings.h
@@ -48,6 +48,8 @@ struct Values {
48 48
49 // Shortcut name <Shortcut, context> 49 // Shortcut name <Shortcut, context>
50 std::vector<Shortcut> shortcuts; 50 std::vector<Shortcut> shortcuts;
51
52 uint32_t callout_flags;
51}; 53};
52 54
53extern Values values; 55extern Values values;
diff --git a/src/core/settings.h b/src/core/settings.h
index ca657719a..bf8014c5a 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -130,7 +130,10 @@ struct Values {
130 u16 gdbstub_port; 130 u16 gdbstub_port;
131 131
132 // WebService 132 // WebService
133 bool enable_telemetry;
133 std::string telemetry_endpoint_url; 134 std::string telemetry_endpoint_url;
135 std::string citra_username;
136 std::string citra_token;
134} extern values; 137} extern values;
135 138
136// a special value for Values::region_value indicating that citra will automatically select a region 139// a special value for Values::region_value indicating that citra will automatically select a region
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 94483f385..104a16cc9 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -3,8 +3,10 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <cstring> 5#include <cstring>
6#include <cryptopp/osrng.h>
6 7
7#include "common/assert.h" 8#include "common/assert.h"
9#include "common/file_util.h"
8#include "common/scm_rev.h" 10#include "common/scm_rev.h"
9#include "common/x64/cpu_detect.h" 11#include "common/x64/cpu_detect.h"
10#include "core/core.h" 12#include "core/core.h"
@@ -29,12 +31,65 @@ static const char* CpuVendorToStr(Common::CPUVendor vendor) {
29 UNREACHABLE(); 31 UNREACHABLE();
30} 32}
31 33
34static u64 GenerateTelemetryId() {
35 u64 telemetry_id{};
36 CryptoPP::AutoSeededRandomPool rng;
37 rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&telemetry_id), sizeof(u64));
38 return telemetry_id;
39}
40
41u64 GetTelemetryId() {
42 u64 telemetry_id{};
43 static const std::string& filename{FileUtil::GetUserPath(D_CONFIG_IDX) + "telemetry_id"};
44
45 if (FileUtil::Exists(filename)) {
46 FileUtil::IOFile file(filename, "rb");
47 if (!file.IsOpen()) {
48 LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
49 return {};
50 }
51 file.ReadBytes(&telemetry_id, sizeof(u64));
52 } else {
53 FileUtil::IOFile file(filename, "wb");
54 if (!file.IsOpen()) {
55 LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
56 return {};
57 }
58 telemetry_id = GenerateTelemetryId();
59 file.WriteBytes(&telemetry_id, sizeof(u64));
60 }
61
62 return telemetry_id;
63}
64
65u64 RegenerateTelemetryId() {
66 const u64 new_telemetry_id{GenerateTelemetryId()};
67 static const std::string& filename{FileUtil::GetUserPath(D_CONFIG_IDX) + "telemetry_id"};
68
69 FileUtil::IOFile file(filename, "wb");
70 if (!file.IsOpen()) {
71 LOG_ERROR(Core, "failed to open telemetry_id: %s", filename.c_str());
72 return {};
73 }
74 file.WriteBytes(&new_telemetry_id, sizeof(u64));
75 return new_telemetry_id;
76}
77
32TelemetrySession::TelemetrySession() { 78TelemetrySession::TelemetrySession() {
33#ifdef ENABLE_WEB_SERVICE 79#ifdef ENABLE_WEB_SERVICE
34 backend = std::make_unique<WebService::TelemetryJson>(); 80 if (Settings::values.enable_telemetry) {
81 backend = std::make_unique<WebService::TelemetryJson>(
82 Settings::values.telemetry_endpoint_url, Settings::values.citra_username,
83 Settings::values.citra_token);
84 } else {
85 backend = std::make_unique<Telemetry::NullVisitor>();
86 }
35#else 87#else
36 backend = std::make_unique<Telemetry::NullVisitor>(); 88 backend = std::make_unique<Telemetry::NullVisitor>();
37#endif 89#endif
90 // Log one-time top-level information
91 AddField(Telemetry::FieldType::None, "TelemetryId", GetTelemetryId());
92
38 // Log one-time session start information 93 // Log one-time session start information
39 const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>( 94 const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>(
40 std::chrono::system_clock::now().time_since_epoch()) 95 std::chrono::system_clock::now().time_since_epoch())
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index cf53835c3..65613daae 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -35,4 +35,16 @@ private:
35 std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields 35 std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
36}; 36};
37 37
38/**
39 * Gets TelemetryId, a unique identifier used for the user's telemetry sessions.
40 * @returns The current TelemetryId for the session.
41 */
42u64 GetTelemetryId();
43
44/**
45 * Regenerates TelemetryId, a unique identifier used for the user's telemetry sessions.
46 * @returns The new TelemetryId that was generated.
47 */
48u64 RegenerateTelemetryId();
49
38} // namespace Core 50} // namespace Core
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index a2d007e77..6ad2ffcd4 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -3,7 +3,6 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "common/assert.h" 5#include "common/assert.h"
6#include "core/settings.h"
7#include "web_service/telemetry_json.h" 6#include "web_service/telemetry_json.h"
8#include "web_service/web_backend.h" 7#include "web_service/web_backend.h"
9 8
@@ -81,7 +80,7 @@ void TelemetryJson::Complete() {
81 SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); 80 SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
82 SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); 81 SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
83 SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); 82 SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
84 PostJson(Settings::values.telemetry_endpoint_url, TopSection().dump()); 83 PostJson(endpoint_url, TopSection().dump(), true, username, token);
85} 84}
86 85
87} // namespace WebService 86} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
index 39038b4f9..9e78c6803 100644
--- a/src/web_service/telemetry_json.h
+++ b/src/web_service/telemetry_json.h
@@ -17,7 +17,9 @@ namespace WebService {
17 */ 17 */
18class TelemetryJson : public Telemetry::VisitorInterface { 18class TelemetryJson : public Telemetry::VisitorInterface {
19public: 19public:
20 TelemetryJson() = default; 20 TelemetryJson(const std::string& endpoint_url, const std::string& username,
21 const std::string& token)
22 : endpoint_url(endpoint_url), username(username), token(token) {}
21 ~TelemetryJson() = default; 23 ~TelemetryJson() = default;
22 24
23 void Visit(const Telemetry::Field<bool>& field) override; 25 void Visit(const Telemetry::Field<bool>& field) override;
@@ -49,6 +51,9 @@ private:
49 51
50 nlohmann::json output; 52 nlohmann::json output;
51 std::array<nlohmann::json, 7> sections; 53 std::array<nlohmann::json, 7> sections;
54 std::string endpoint_url;
55 std::string username;
56 std::string token;
52}; 57};
53 58
54} // namespace WebService 59} // namespace WebService
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 13e4555ac..d28a3f757 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -2,51 +2,62 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#ifdef _WIN32
6#include <winsock.h>
7#endif
8
9#include <cstdlib>
10#include <thread>
5#include <cpr/cpr.h> 11#include <cpr/cpr.h>
6#include <stdlib.h>
7#include "common/logging/log.h" 12#include "common/logging/log.h"
8#include "web_service/web_backend.h" 13#include "web_service/web_backend.h"
9 14
10namespace WebService { 15namespace WebService {
11 16
12static constexpr char API_VERSION[]{"1"}; 17static constexpr char API_VERSION[]{"1"};
13static constexpr char ENV_VAR_USERNAME[]{"CITRA_WEB_SERVICES_USERNAME"};
14static constexpr char ENV_VAR_TOKEN[]{"CITRA_WEB_SERVICES_TOKEN"};
15
16static std::string GetEnvironmentVariable(const char* name) {
17 const char* value{getenv(name)};
18 if (value) {
19 return value;
20 }
21 return {};
22}
23
24const std::string& GetUsername() {
25 static const std::string username{GetEnvironmentVariable(ENV_VAR_USERNAME)};
26 return username;
27}
28 18
29const std::string& GetToken() { 19static std::unique_ptr<cpr::Session> g_session;
30 static const std::string token{GetEnvironmentVariable(ENV_VAR_TOKEN)};
31 return token;
32}
33 20
34void PostJson(const std::string& url, const std::string& data) { 21void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
22 const std::string& username, const std::string& token) {
35 if (url.empty()) { 23 if (url.empty()) {
36 LOG_ERROR(WebService, "URL is invalid"); 24 LOG_ERROR(WebService, "URL is invalid");
37 return; 25 return;
38 } 26 }
39 27
40 if (GetUsername().empty() || GetToken().empty()) { 28 const bool are_credentials_provided{!token.empty() && !username.empty()};
41 LOG_ERROR(WebService, "Environment variables %s and %s must be set to POST JSON", 29 if (!allow_anonymous && !are_credentials_provided) {
42 ENV_VAR_USERNAME, ENV_VAR_TOKEN); 30 LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
43 return; 31 return;
44 } 32 }
45 33
46 cpr::PostAsync(cpr::Url{url}, cpr::Body{data}, cpr::Header{{"Content-Type", "application/json"}, 34#ifdef _WIN32
47 {"x-username", GetUsername()}, 35 // On Windows, CPR/libcurl does not properly initialize Winsock. The below code is used to
48 {"x-token", GetToken()}, 36 // initialize Winsock globally, which fixes this problem. Without this, only the first CPR
49 {"api-version", API_VERSION}}); 37 // session will properly be created, and subsequent ones will fail.
38 WSADATA wsa_data;
39 const int wsa_result{WSAStartup(MAKEWORD(2, 2), &wsa_data)};
40 if (wsa_result) {
41 LOG_CRITICAL(WebService, "WSAStartup failed: %d", wsa_result);
42 }
43#endif
44
45 // Built request header
46 cpr::Header header;
47 if (are_credentials_provided) {
48 // Authenticated request if credentials are provided
49 header = {{"Content-Type", "application/json"},
50 {"x-username", username.c_str()},
51 {"x-token", token.c_str()},
52 {"api-version", API_VERSION}};
53 } else {
54 // Otherwise, anonymous request
55 header = cpr::Header{{"Content-Type", "application/json"}, {"api-version", API_VERSION}};
56 }
57
58 // Post JSON asynchronously
59 static cpr::AsyncResponse future;
60 future = cpr::PostAsync(cpr::Url{url.c_str()}, cpr::Body{data.c_str()}, header);
50} 61}
51 62
52} // namespace WebService 63} // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index 2753d3b68..d17100398 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -10,22 +10,14 @@
10namespace WebService { 10namespace WebService {
11 11
12/** 12/**
13 * Gets the current username for accessing services.citra-emu.org.
14 * @returns Username as a string, empty if not set.
15 */
16const std::string& GetUsername();
17
18/**
19 * Gets the current token for accessing services.citra-emu.org.
20 * @returns Token as a string, empty if not set.
21 */
22const std::string& GetToken();
23
24/**
25 * Posts JSON to services.citra-emu.org. 13 * Posts JSON to services.citra-emu.org.
26 * @param url URL of the services.citra-emu.org endpoint to post data to. 14 * @param url URL of the services.citra-emu.org endpoint to post data to.
27 * @param data String of JSON data to use for the body of the POST request. 15 * @param data String of JSON data to use for the body of the POST request.
16 * @param allow_anonymous If true, allow anonymous unauthenticated requests.
17 * @param username Citra username to use for authentication.
18 * @param token Citra token to use for authentication.
28 */ 19 */
29void PostJson(const std::string& url, const std::string& data); 20void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
21 const std::string& username = {}, const std::string& token = {});
30 22
31} // namespace WebService 23} // namespace WebService