mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Merge pull request #2897 from bunnei/telemetry-ui
Telemetry UI and final touches
This commit is contained in:
		
						commit
						22fc378fe9
					
				
					 20 changed files with 446 additions and 48 deletions
				
			
		|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue