diff options
Diffstat (limited to 'src/yuzu')
| -rw-r--r-- | src/yuzu/CMakeLists.txt | 20 | ||||
| -rw-r--r-- | src/yuzu/compatdb.cpp | 65 | ||||
| -rw-r--r-- | src/yuzu/compatdb.h | 26 | ||||
| -rw-r--r-- | src/yuzu/compatdb.ui | 215 | ||||
| -rw-r--r-- | src/yuzu/configuration/config.cpp | 18 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure.ui | 11 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_dialog.cpp | 1 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_general.cpp | 2 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_input.cpp | 87 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_input.h | 3 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_input.ui | 28 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_web.cpp | 119 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_web.h | 38 | ||||
| -rw-r--r-- | src/yuzu/configuration/configure_web.ui | 206 | ||||
| -rw-r--r-- | src/yuzu/debugger/wait_tree.cpp | 47 | ||||
| -rw-r--r-- | src/yuzu/discord.h | 25 | ||||
| -rw-r--r-- | src/yuzu/discord_impl.cpp | 52 | ||||
| -rw-r--r-- | src/yuzu/discord_impl.h | 20 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 84 | ||||
| -rw-r--r-- | src/yuzu/main.h | 10 | ||||
| -rw-r--r-- | src/yuzu/main.ui | 16 | ||||
| -rw-r--r-- | src/yuzu/ui_settings.cpp | 8 | ||||
| -rw-r--r-- | src/yuzu/ui_settings.h | 8 | 
23 files changed, 1049 insertions, 60 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index f48b69809..04464ad5e 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -29,6 +29,8 @@ add_executable(yuzu      configuration/configure_input.h      configuration/configure_system.cpp      configuration/configure_system.h +    configuration/configure_web.cpp +    configuration/configure_web.h      debugger/graphics/graphics_breakpoint_observer.cpp      debugger/graphics/graphics_breakpoint_observer.h      debugger/graphics/graphics_breakpoints.cpp @@ -42,6 +44,7 @@ add_executable(yuzu      debugger/profiler.h      debugger/wait_tree.cpp      debugger/wait_tree.h +    discord.h      game_list.cpp      game_list.h      game_list_p.h @@ -57,6 +60,8 @@ add_executable(yuzu      util/spinbox.h      util/util.cpp      util/util.h +    compatdb.cpp +    compatdb.h      yuzu.rc  ) @@ -70,8 +75,10 @@ set(UIS      configuration/configure_graphics.ui      configuration/configure_input.ui      configuration/configure_system.ui +    configuration/configure_web.ui      hotkeys.ui      main.ui +    compatdb.ui  )  file(GLOB COMPAT_LIST @@ -113,6 +120,19 @@ target_link_libraries(yuzu PRIVATE common core input_common video_core)  target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)  target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) +if (YUZU_ENABLE_COMPATIBILITY_REPORTING) +    add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING) +endif() + +if (USE_DISCORD_PRESENCE) +    target_sources(yuzu PUBLIC +        discord_impl.cpp +        discord_impl.h +    ) +    target_link_libraries(yuzu PRIVATE discord-rpc) +    target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE) +endif() +  if(UNIX AND NOT APPLE)      install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")  endif() diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp new file mode 100644 index 000000000..91e754274 --- /dev/null +++ b/src/yuzu/compatdb.cpp @@ -0,0 +1,65 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QButtonGroup> +#include <QMessageBox> +#include <QPushButton> +#include "common/logging/log.h" +#include "common/telemetry.h" +#include "core/core.h" +#include "core/telemetry_session.h" +#include "ui_compatdb.h" +#include "yuzu/compatdb.h" + +CompatDB::CompatDB(QWidget* parent) +    : QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), +      ui{std::make_unique<Ui::CompatDB>()} { +    ui->setupUi(this); +    connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext); +    connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit); +} + +CompatDB::~CompatDB() = default; + +enum class CompatDBPage { +    Intro = 0, +    Selection = 1, +    Final = 2, +}; + +void CompatDB::Submit() { +    QButtonGroup* compatibility = new QButtonGroup(this); +    compatibility->addButton(ui->radioButton_Perfect, 0); +    compatibility->addButton(ui->radioButton_Great, 1); +    compatibility->addButton(ui->radioButton_Okay, 2); +    compatibility->addButton(ui->radioButton_Bad, 3); +    compatibility->addButton(ui->radioButton_IntroMenu, 4); +    compatibility->addButton(ui->radioButton_WontBoot, 5); +    switch ((static_cast<CompatDBPage>(currentId()))) { +    case CompatDBPage::Selection: +        if (compatibility->checkedId() == -1) { +            button(NextButton)->setEnabled(false); +        } +        break; +    case CompatDBPage::Final: +        LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId()); +        Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility", +                                   compatibility->checkedId()); +        // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a +        // workaround +        button(QWizard::CancelButton)->setVisible(false); +        break; +    default: +        LOG_ERROR(Frontend, "Unexpected page: {}", currentId()); +    } +} + +void CompatDB::EnableNext() { +    button(NextButton)->setEnabled(true); +} diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h new file mode 100644 index 000000000..ca0dd11d6 --- /dev/null +++ b/src/yuzu/compatdb.h @@ -0,0 +1,26 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QWizard> + +namespace Ui { +class CompatDB; +} + +class CompatDB : public QWizard { +    Q_OBJECT + +public: +    explicit CompatDB(QWidget* parent = nullptr); +    ~CompatDB(); + +private: +    std::unique_ptr<Ui::CompatDB> ui; + +    void Submit(); +    void EnableNext(); +}; diff --git a/src/yuzu/compatdb.ui b/src/yuzu/compatdb.ui new file mode 100644 index 000000000..fed402176 --- /dev/null +++ b/src/yuzu/compatdb.ui @@ -0,0 +1,215 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CompatDB</class> + <widget class="QWizard" name="CompatDB"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>600</width> +    <height>482</height> +   </rect> +  </property> +  <property name="minimumSize"> +   <size> +    <width>500</width> +    <height>410</height> +   </size> +  </property> +  <property name="windowTitle"> +   <string>Report Compatibility</string> +  </property> +  <property name="options"> +   <set>QWizard::DisabledBackButtonOnLastPage|QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage</set> +  </property> +  <widget class="QWizardPage" name="wizard_Info"> +   <property name="title"> +    <string>Report Game Compatibility</string> +   </property> +   <attribute name="pageId"> +    <string notr="true">0</string> +   </attribute> +   <layout class="QVBoxLayout" name="verticalLayout"> +    <item> +     <widget class="QLabel" name="lbl_Spiel"> +      <property name="text"> +       <string><html><head/><body><p><span style=" font-size:10pt;">Should you choose to submit a test case to the </span><a href="https://yuzu-emu.org/game/"><span style=" font-size:10pt; text-decoration: underline; color:#0000ff;">yuzu Compatibility List</span></a><span style=" font-size:10pt;">, The following information will be collected and displayed on the site:</span></p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Hardware Information (CPU / GPU / Operating System)</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Which version of yuzu you are running</li><li style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">The connected yuzu account</li></ul></body></html></string> +      </property> +      <property name="wordWrap"> +       <bool>true</bool> +      </property> +      <property name="openExternalLinks"> +       <bool>true</bool> +      </property> +     </widget> +    </item> +    <item> +     <spacer name="verticalSpacer_2"> +      <property name="orientation"> +       <enum>Qt::Vertical</enum> +      </property> +      <property name="sizeHint" stdset="0"> +       <size> +        <width>20</width> +        <height>0</height> +       </size> +      </property> +     </spacer> +    </item> +   </layout> +  </widget> +  <widget class="QWizardPage" name="wizard_Report"> +   <property name="title"> +    <string>Report Game Compatibility</string> +   </property> +   <attribute name="pageId"> +    <string notr="true">1</string> +   </attribute> +   <layout class="QFormLayout" name="formLayout"> +    <item row="2" column="0"> +     <widget class="QRadioButton" name="radioButton_Perfect"> +      <property name="text"> +       <string>Perfect</string> +      </property> +     </widget> +    </item> +    <item row="2" column="1"> +     <widget class="QLabel" name="lbl_Perfect"> +      <property name="text"> +       <string><html><head/><body><p>Game functions flawlessly with no audio or graphical glitches.</p></body></html></string> +      </property> +      <property name="wordWrap"> +       <bool>true</bool> +      </property> +     </widget> +    </item> +    <item row="4" column="0"> +     <widget class="QRadioButton" name="radioButton_Great"> +      <property name="text"> +       <string>Great </string> +      </property> +     </widget> +    </item> +    <item row="4" column="1"> +     <widget class="QLabel" name="lbl_Great"> +      <property name="text"> +       <string><html><head/><body><p>Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.</p></body></html></string> +      </property> +      <property name="wordWrap"> +       <bool>true</bool> +      </property> +     </widget> +    </item> +    <item row="5" column="0"> +     <widget class="QRadioButton" name="radioButton_Okay"> +      <property name="text"> +       <string>Okay</string> +      </property> +     </widget> +    </item> +    <item row="5" column="1"> +     <widget class="QLabel" name="lbl_Okay"> +      <property name="text"> +       <string><html><head/><body><p>Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.</p></body></html></string> +      </property> +      <property name="wordWrap"> +       <bool>true</bool> +      </property> +     </widget> +    </item> +    <item row="6" column="0"> +     <widget class="QRadioButton" name="radioButton_Bad"> +      <property name="text"> +       <string>Bad</string> +      </property> +     </widget> +    </item> +    <item row="6" column="1"> +     <widget class="QLabel" name="lbl_Bad"> +      <property name="text"> +       <string><html><head/><body><p>Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.</p></body></html></string> +      </property> +      <property name="wordWrap"> +       <bool>true</bool> +      </property> +     </widget> +    </item> +    <item row="7" column="0"> +     <widget class="QRadioButton" name="radioButton_IntroMenu"> +      <property name="text"> +       <string>Intro/Menu</string> +      </property> +     </widget> +    </item> +    <item row="7" column="1"> +     <widget class="QLabel" name="lbl_IntroMenu"> +      <property name="text"> +       <string><html><head/><body><p>Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.</p></body></html></string> +      </property> +      <property name="wordWrap"> +       <bool>true</bool> +      </property> +     </widget> +    </item> +    <item row="8" column="0"> +     <widget class="QRadioButton" name="radioButton_WontBoot"> +      <property name="text"> +       <string>Won't Boot</string> +      </property> +      <property name="checkable"> +       <bool>true</bool> +      </property> +      <property name="checked"> +       <bool>false</bool> +      </property> +     </widget> +    </item> +    <item row="8" column="1"> +     <widget class="QLabel" name="lbl_WontBoot"> +      <property name="text"> +       <string><html><head/><body><p>The game crashes when attempting to startup.</p></body></html></string> +      </property> +     </widget> +    </item> +    <item row="0" column="0" colspan="2"> +     <widget class="QLabel" name="lbl_Independent"> +      <property name="font"> +       <font> +        <pointsize>10</pointsize> +       </font> +      </property> +      <property name="text"> +       <string><html><head/><body><p>Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?</p></body></html></string> +      </property> +      <property name="wordWrap"> +       <bool>true</bool> +      </property> +     </widget> +    </item> +    <item row="1" column="0" colspan="2"> +     <spacer name="verticalSpacer"> +      <property name="orientation"> +       <enum>Qt::Vertical</enum> +      </property> +      <property name="sizeHint" stdset="0"> +       <size> +        <width>20</width> +        <height>0</height> +       </size> +      </property> +     </spacer> +    </item> +   </layout> +  </widget> +  <widget class="QWizardPage" name="wizard_ThankYou"> +   <property name="title"> +    <string>Thank you for your submission!</string> +   </property> +   <attribute name="pageId"> +    <string notr="true">2</string> +   </attribute> +  </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index d229225b4..650dd03c0 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -136,8 +136,18 @@ void Config::ReadValues() {      Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();      qt_config->endGroup(); +    qt_config->beginGroup("WebService"); +    Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool(); +    Settings::values.web_api_url = +        qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString(); +    Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString(); +    Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString(); +    qt_config->endGroup(); +      qt_config->beginGroup("UI");      UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); +    UISettings::values.enable_discord_presence = +        qt_config->value("enable_discord_presence", true).toBool();      qt_config->beginGroup("UIGameList");      UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool(); @@ -261,8 +271,16 @@ void Config::SaveValues() {      qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);      qt_config->endGroup(); +    qt_config->beginGroup("WebService"); +    qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry); +    qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url)); +    qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username)); +    qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token)); +    qt_config->endGroup(); +      qt_config->beginGroup("UI");      qt_config->setValue("theme", UISettings::values.theme); +    qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);      qt_config->beginGroup("UIGameList");      qt_config->setValue("show_unknown", UISettings::values.show_unknown); diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui index 20f120134..9b297df28 100644 --- a/src/yuzu/configuration/configure.ui +++ b/src/yuzu/configuration/configure.ui @@ -54,6 +54,11 @@         <string>Debug</string>        </attribute>       </widget> +      <widget class="ConfigureWeb" name="webTab"> +        <attribute name="title"> +          <string>Web</string> +        </attribute> +      </widget>      </widget>     </item>     <item> @@ -108,6 +113,12 @@     <header>configuration/configure_graphics.h</header>     <container>1</container>    </customwidget> +   <customwidget> +     <class>ConfigureWeb</class> +     <extends>QWidget</extends> +     <header>configuration/configure_web.h</header> +     <container>1</container> +   </customwidget>   </customwidgets>   <resources/>   <connections> diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index daa4cc0d9..3905423e9 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp @@ -27,5 +27,6 @@ void ConfigureDialog::applyConfiguration() {      ui->graphicsTab->applyConfiguration();      ui->audioTab->applyConfiguration();      ui->debugTab->applyConfiguration(); +    ui->webTab->applyConfiguration();      Settings::Apply();  } diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 9292d9a42..f5db9e55b 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -13,7 +13,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)      ui->setupUi(this); -    for (auto theme : UISettings::themes) { +    for (const auto& theme : UISettings::themes) {          ui->theme_combobox->addItem(theme.first, theme.second);      } diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 473937ea9..94789c064 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -5,6 +5,7 @@  #include <algorithm>  #include <memory>  #include <utility> +#include <QMenu>  #include <QMessageBox>  #include <QTimer>  #include "common/param_package.h" @@ -128,28 +129,63 @@ ConfigureInput::ConfigureInput(QWidget* parent)      analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};      for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { -        if (button_map[button_id]) -            connect(button_map[button_id], &QPushButton::released, [=]() { -                handleClick( -                    button_map[button_id], -                    [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, -                    InputCommon::Polling::DeviceType::Button); -            }); +        if (!button_map[button_id]) +            continue; +        button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu); +        connect(button_map[button_id], &QPushButton::released, [=]() { +            handleClick( +                button_map[button_id], +                [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; }, +                InputCommon::Polling::DeviceType::Button); +        }); +        connect(button_map[button_id], &QPushButton::customContextMenuRequested, +                [=](const QPoint& menu_location) { +                    QMenu context_menu; +                    context_menu.addAction(tr("Clear"), [&] { +                        buttons_param[button_id].Clear(); +                        button_map[button_id]->setText(tr("[not set]")); +                    }); +                    context_menu.addAction(tr("Restore Default"), [&] { +                        buttons_param[button_id] = Common::ParamPackage{ +                            InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; +                        button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); +                    }); +                    context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); +                });      }      for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {          for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { -            if (analog_map_buttons[analog_id][sub_button_id] != nullptr) { -                connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, -                        [=]() { -                            handleClick(analog_map_buttons[analog_id][sub_button_id], -                                        [=](const Common::ParamPackage& params) { -                                            SetAnalogButton(params, analogs_param[analog_id], -                                                            analog_sub_buttons[sub_button_id]); -                                        }, -                                        InputCommon::Polling::DeviceType::Button); +            if (!analog_map_buttons[analog_id][sub_button_id]) +                continue; +            analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy( +                Qt::CustomContextMenu); +            connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() { +                handleClick(analog_map_buttons[analog_id][sub_button_id], +                            [=](const Common::ParamPackage& params) { +                                SetAnalogButton(params, analogs_param[analog_id], +                                                analog_sub_buttons[sub_button_id]); +                            }, +                            InputCommon::Polling::DeviceType::Button); +            }); +            connect(analog_map_buttons[analog_id][sub_button_id], +                    &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) { +                        QMenu context_menu; +                        context_menu.addAction(tr("Clear"), [&] { +                            analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); +                            analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));                          }); -            } +                        context_menu.addAction(tr("Restore Default"), [&] { +                            Common::ParamPackage params{InputCommon::GenerateKeyboardParam( +                                Config::default_analogs[analog_id][sub_button_id])}; +                            SetAnalogButton(params, analogs_param[analog_id], +                                            analog_sub_buttons[sub_button_id]); +                            analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText( +                                analogs_param[analog_id], analog_sub_buttons[sub_button_id])); +                        }); +                        context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal( +                            menu_location)); +                    });          }          connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {              QMessageBox::information(this, tr("Information"), @@ -162,6 +198,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)          });      } +    connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });      connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });      timeout_timer->setSingleShot(true); @@ -215,7 +252,21 @@ void ConfigureInput::restoreDefaults() {          }      }      updateButtonLabels(); -    applyConfiguration(); +} + +void ConfigureInput::ClearAll() { +    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { +        if (button_map[button_id] && button_map[button_id]->isEnabled()) +            buttons_param[button_id].Clear(); +    } +    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { +        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) { +            if (analog_map_buttons[analog_id][sub_button_id] && +                analog_map_buttons[analog_id][sub_button_id]->isEnabled()) +                analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); +        } +    } +    updateButtonLabels();  }  void ConfigureInput::updateButtonLabels() { diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h index a0bef86d5..d1198db81 100644 --- a/src/yuzu/configuration/configure_input.h +++ b/src/yuzu/configuration/configure_input.h @@ -72,6 +72,9 @@ private:      void loadConfiguration();      /// Restore all buttons to their default values.      void restoreDefaults(); +    /// Clear all input configuration +    void ClearAll(); +      /// Update UI to reflect current configuration.      void updateButtonLabels(); diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui index 8bfa5df62..8a019a693 100644 --- a/src/yuzu/configuration/configure_input.ui +++ b/src/yuzu/configuration/configure_input.ui @@ -695,6 +695,34 @@ Capture:</string>        </spacer>       </item>       <item> +      <widget class="QPushButton" name="buttonClearAll"> +       <property name="sizePolicy"> +        <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> +         <horstretch>0</horstretch> +         <verstretch>0</verstretch> +        </sizepolicy> +       </property> +       <property name="sizeIncrement"> +        <size> +         <width>0</width> +         <height>0</height> +        </size> +       </property> +       <property name="baseSize"> +        <size> +         <width>0</width> +         <height>0</height> +        </size> +       </property> +       <property name="layoutDirection"> +        <enum>Qt::LeftToRight</enum> +       </property> +       <property name="text"> +        <string>Clear All</string> +       </property> +      </widget> +     </item> +     <item>        <widget class="QPushButton" name="buttonRestoreDefaults">         <property name="sizePolicy">          <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp new file mode 100644 index 000000000..3c2ccb76f --- /dev/null +++ b/src/yuzu/configuration/configure_web.cpp @@ -0,0 +1,119 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <QIcon> +#include <QMessageBox> +#include <QtConcurrent/QtConcurrentRun> +#include "core/settings.h" +#include "core/telemetry_session.h" +#include "ui_configure_web.h" +#include "yuzu/configuration/configure_web.h" +#include "yuzu/ui_settings.h" + +ConfigureWeb::ConfigureWeb(QWidget* parent) +    : QWidget(parent), ui(std::make_unique<Ui::ConfigureWeb>()) { +    ui->setupUi(this); +    connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this, +            &ConfigureWeb::RefreshTelemetryID); +    connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin); +    connect(&verify_watcher, &QFutureWatcher<bool>::finished, this, &ConfigureWeb::OnLoginVerified); + +#ifndef USE_DISCORD_PRESENCE +    ui->discord_group->setVisible(false); +#endif +    this->setConfiguration(); +} + +ConfigureWeb::~ConfigureWeb() = default; + +void ConfigureWeb::setConfiguration() { +    ui->web_credentials_disclaimer->setWordWrap(true); +    ui->telemetry_learn_more->setOpenExternalLinks(true); +    ui->telemetry_learn_more->setText( +        tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'><span style=\"text-decoration: " +           "underline; color:#039be5;\">Learn more</span></a>")); + +    ui->web_signup_link->setOpenExternalLinks(true); +    ui->web_signup_link->setText( +        tr("<a href='https://profile.yuzu-emu.org/'><span style=\"text-decoration: underline; " +           "color:#039be5;\">Sign up</span></a>")); +    ui->web_token_info_link->setOpenExternalLinks(true); +    ui->web_token_info_link->setText( +        tr("<a href='https://yuzu-emu.org/wiki/yuzu-web-service/'><span style=\"text-decoration: " +           "underline; color:#039be5;\">What is my token?</span></a>")); + +    ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry); +    ui->edit_username->setText(QString::fromStdString(Settings::values.yuzu_username)); +    ui->edit_token->setText(QString::fromStdString(Settings::values.yuzu_token)); +    // Connect after setting the values, to avoid calling OnLoginChanged now +    connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); +    connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); +    ui->label_telemetry_id->setText( +        tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper())); +    user_verified = true; + +    ui->toggle_discordrpc->setChecked(UISettings::values.enable_discord_presence); +} + +void ConfigureWeb::applyConfiguration() { +    Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked(); +    UISettings::values.enable_discord_presence = ui->toggle_discordrpc->isChecked(); +    if (user_verified) { +        Settings::values.yuzu_username = ui->edit_username->text().toStdString(); +        Settings::values.yuzu_token = ui->edit_token->text().toStdString(); +    } else { +        QMessageBox::warning(this, tr("Username and token not verified"), +                             tr("Username and token were not verified. The changes to your " +                                "username and/or token have not been saved.")); +    } +} + +void ConfigureWeb::RefreshTelemetryID() { +    const u64 new_telemetry_id{Core::RegenerateTelemetryId()}; +    ui->label_telemetry_id->setText( +        tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper())); +} + +void ConfigureWeb::OnLoginChanged() { +    if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) { +        user_verified = true; +        ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); +        ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); +    } else { +        user_verified = false; +        ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); +        ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); +    } +} + +void ConfigureWeb::VerifyLogin() { +    ui->button_verify_login->setDisabled(true); +    ui->button_verify_login->setText(tr("Verifying")); +    verify_watcher.setFuture( +        QtConcurrent::run([this, username = ui->edit_username->text().toStdString(), +                           token = ui->edit_token->text().toStdString()]() { +            return Core::VerifyLogin(username, token); +        })); +} + +void ConfigureWeb::OnLoginVerified() { +    ui->button_verify_login->setEnabled(true); +    ui->button_verify_login->setText(tr("Verify")); +    if (verify_watcher.result()) { +        user_verified = true; +        ui->label_username_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); +        ui->label_token_verified->setPixmap(QIcon::fromTheme("checked").pixmap(16)); +    } else { +        ui->label_username_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); +        ui->label_token_verified->setPixmap(QIcon::fromTheme("failed").pixmap(16)); +        QMessageBox::critical( +            this, tr("Verification failed"), +            tr("Verification failed. Check that you have entered your username and token " +               "correctly, and that your internet connection is working.")); +    } +} + +void ConfigureWeb::retranslateUi() { +    ui->retranslateUi(this); +} diff --git a/src/yuzu/configuration/configure_web.h b/src/yuzu/configuration/configure_web.h new file mode 100644 index 000000000..7741ab95d --- /dev/null +++ b/src/yuzu/configuration/configure_web.h @@ -0,0 +1,38 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include <QFutureWatcher> +#include <QWidget> + +namespace Ui { +class ConfigureWeb; +} + +class ConfigureWeb : public QWidget { +    Q_OBJECT + +public: +    explicit ConfigureWeb(QWidget* parent = nullptr); +    ~ConfigureWeb(); + +    void applyConfiguration(); +    void retranslateUi(); + +public slots: +    void RefreshTelemetryID(); +    void OnLoginChanged(); +    void VerifyLogin(); +    void OnLoginVerified(); + +private: +    void setConfiguration(); + +    bool user_verified = true; +    QFutureWatcher<bool> verify_watcher; + +    std::unique_ptr<Ui::ConfigureWeb> ui; +}; diff --git a/src/yuzu/configuration/configure_web.ui b/src/yuzu/configuration/configure_web.ui new file mode 100644 index 000000000..2f4b9dd73 --- /dev/null +++ b/src/yuzu/configuration/configure_web.ui @@ -0,0 +1,206 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ConfigureWeb</class> + <widget class="QWidget" name="ConfigureWeb"> +  <property name="geometry"> +   <rect> +    <x>0</x> +    <y>0</y> +    <width>926</width> +    <height>561</height> +   </rect> +  </property> +  <property name="windowTitle"> +   <string>Form</string> +  </property> +  <layout class="QVBoxLayout" name="verticalLayout"> +   <item> +    <layout class="QVBoxLayout" name="verticalLayout_3"> +     <item> +      <widget class="QGroupBox" name="groupBoxWebConfig"> +       <property name="title"> +        <string>yuzu Web Service</string> +       </property> +       <layout class="QVBoxLayout" name="verticalLayoutYuzuWebService"> +        <item> +         <widget class="QLabel" name="web_credentials_disclaimer"> +          <property name="text"> +           <string>By providing your username and token, you agree to allow yuzu to collect additional usage data, which may include user identifying information.</string> +          </property> +         </widget> +        </item> +        <item> +         <layout class="QGridLayout" name="gridLayoutYuzuUsername"> +          <item row="2" column="3"> +           <widget class="QPushButton" name="button_verify_login"> +            <property name="sizePolicy"> +             <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +              <horstretch>0</horstretch> +              <verstretch>0</verstretch> +             </sizepolicy> +            </property> +            <property name="layoutDirection"> +             <enum>Qt::RightToLeft</enum> +            </property> +            <property name="text"> +             <string>Verify</string> +            </property> +           </widget> +          </item> +          <item row="2" column="0"> +           <widget class="QLabel" name="web_signup_link"> +            <property name="text"> +             <string>Sign up</string> +            </property> +           </widget> +          </item> +          <item row="0" column="1" colspan="3"> +           <widget class="QLineEdit" name="edit_username"> +            <property name="maxLength"> +             <number>36</number> +            </property> +           </widget> +          </item> +          <item row="1" column="0"> +           <widget class="QLabel" name="label_token"> +            <property name="text"> +             <string>Token: </string> +            </property> +           </widget> +          </item> +          <item row="1" column="4"> +           <widget class="QLabel" name="label_token_verified"> +           </widget> +          </item> +          <item row="0" column="0"> +           <widget class="QLabel" name="label_username"> +            <property name="text"> +             <string>Username: </string> +            </property> +           </widget> +          </item> +          <item row="0" column="4"> +           <widget class="QLabel" name="label_username_verified"> +           </widget> +          </item> +          <item row="1" column="1" colspan="3"> +           <widget class="QLineEdit" name="edit_token"> +            <property name="maxLength"> +             <number>36</number> +            </property> +            <property name="echoMode"> +             <enum>QLineEdit::Password</enum> +            </property> +           </widget> +          </item> +          <item row="2" column="1"> +           <widget class="QLabel" name="web_token_info_link"> +            <property name="text"> +             <string>What is my token?</string> +            </property> +           </widget> +          </item> +          <item row="2" column="2"> +           <spacer name="horizontalSpacer"> +            <property name="orientation"> +             <enum>Qt::Horizontal</enum> +            </property> +            <property name="sizeHint" stdset="0"> +             <size> +              <width>40</width> +              <height>20</height> +             </size> +            </property> +           </spacer> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +     <item> +      <widget class="QGroupBox" name="groupBox"> +       <property name="title"> +        <string>Telemetry</string> +       </property> +       <layout class="QVBoxLayout" name="verticalLayout_2"> +        <item> +         <widget class="QCheckBox" name="toggle_telemetry"> +          <property name="text"> +           <string>Share anonymous usage data with the yuzu team</string> +          </property> +         </widget> +        </item> +        <item> +         <widget class="QLabel" name="telemetry_learn_more"> +          <property name="text"> +           <string>Learn more</string> +          </property> +         </widget> +        </item> +        <item> +         <layout class="QGridLayout" name="gridLayoutTelemetryId"> +          <item row="0" column="0"> +           <widget class="QLabel" name="label_telemetry_id"> +            <property name="text"> +             <string>Telemetry ID:</string> +            </property> +           </widget> +          </item> +          <item row="0" column="1"> +           <widget class="QPushButton" name="button_regenerate_telemetry_id"> +            <property name="sizePolicy"> +             <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> +              <horstretch>0</horstretch> +              <verstretch>0</verstretch> +             </sizepolicy> +            </property> +            <property name="layoutDirection"> +             <enum>Qt::RightToLeft</enum> +            </property> +            <property name="text"> +             <string>Regenerate</string> +            </property> +           </widget> +          </item> +         </layout> +        </item> +       </layout> +      </widget> +     </item> +    </layout> +   </item> +   <item> +     <widget class="QGroupBox" name="discord_group"> +      <property name="title"> +       <string>Discord Presence</string> +      </property> +      <layout class="QVBoxLayout" name="verticalLayout_21"> +       <item> +        <widget class="QCheckBox" name="toggle_discordrpc"> +         <property name="text"> +          <string>Show Current Game in your Discord Status</string> +         </property> +        </widget> +       </item> +      </layout> +     </widget> +   </item> +   <item> +    <spacer name="verticalSpacer"> +     <property name="orientation"> +      <enum>Qt::Vertical</enum> +     </property> +     <property name="sizeHint" stdset="0"> +      <size> +       <width>20</width> +       <height>40</height> +      </size> +     </property> +    </spacer> +   </item> +  </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index a3b1fd357..4a09da685 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -119,7 +119,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() cons      std::vector<std::unique_ptr<WaitTreeItem>> list;      constexpr std::size_t BaseRegister = 29; -    u64 base_pointer = thread.context.cpu_registers[BaseRegister]; +    u64 base_pointer = thread.GetContext().cpu_registers[BaseRegister];      while (base_pointer != 0) {          u64 lr = Memory::Read64(base_pointer + sizeof(u64)); @@ -213,7 +213,7 @@ WaitTreeThread::~WaitTreeThread() = default;  QString WaitTreeThread::GetText() const {      const auto& thread = static_cast<const Kernel::Thread&>(object);      QString status; -    switch (thread.status) { +    switch (thread.GetStatus()) {      case Kernel::ThreadStatus::Running:          status = tr("running");          break; @@ -246,15 +246,17 @@ QString WaitTreeThread::GetText() const {          status = tr("dead");          break;      } -    QString pc_info = tr(" PC = 0x%1 LR = 0x%2") -                          .arg(thread.context.pc, 8, 16, QLatin1Char('0')) -                          .arg(thread.context.cpu_registers[30], 8, 16, QLatin1Char('0')); + +    const auto& context = thread.GetContext(); +    const QString pc_info = tr(" PC = 0x%1 LR = 0x%2") +                                .arg(context.pc, 8, 16, QLatin1Char('0')) +                                .arg(context.cpu_registers[30], 8, 16, QLatin1Char('0'));      return WaitTreeWaitObject::GetText() + pc_info + " (" + status + ") ";  }  QColor WaitTreeThread::GetColor() const {      const auto& thread = static_cast<const Kernel::Thread&>(object); -    switch (thread.status) { +    switch (thread.GetStatus()) {      case Kernel::ThreadStatus::Running:          return QColor(Qt::GlobalColor::darkGreen);      case Kernel::ThreadStatus::Ready: @@ -284,7 +286,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {      const auto& thread = static_cast<const Kernel::Thread&>(object);      QString processor; -    switch (thread.processor_id) { +    switch (thread.GetProcessorID()) {      case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT:          processor = tr("default");          break; @@ -292,32 +294,35 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {      case Kernel::ThreadProcessorId::THREADPROCESSORID_1:      case Kernel::ThreadProcessorId::THREADPROCESSORID_2:      case Kernel::ThreadProcessorId::THREADPROCESSORID_3: -        processor = tr("core %1").arg(thread.processor_id); +        processor = tr("core %1").arg(thread.GetProcessorID());          break;      default: -        processor = tr("Unknown processor %1").arg(thread.processor_id); +        processor = tr("Unknown processor %1").arg(thread.GetProcessorID());          break;      }      list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor))); -    list.push_back(std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.ideal_core)));      list.push_back( -        std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.affinity_mask))); -    list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId()))); +        std::make_unique<WaitTreeText>(tr("ideal core = %1").arg(thread.GetIdealCore()))); +    list.push_back( +        std::make_unique<WaitTreeText>(tr("affinity mask = %1").arg(thread.GetAffinityMask()))); +    list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadID())));      list.push_back(std::make_unique<WaitTreeText>(tr("priority = %1(current) / %2(normal)") -                                                      .arg(thread.current_priority) -                                                      .arg(thread.nominal_priority))); +                                                      .arg(thread.GetPriority()) +                                                      .arg(thread.GetNominalPriority())));      list.push_back(std::make_unique<WaitTreeText>( -        tr("last running ticks = %1").arg(thread.last_running_ticks))); +        tr("last running ticks = %1").arg(thread.GetLastRunningTicks()))); -    if (thread.mutex_wait_address != 0) -        list.push_back(std::make_unique<WaitTreeMutexInfo>(thread.mutex_wait_address)); -    else +    const VAddr mutex_wait_address = thread.GetMutexWaitAddress(); +    if (mutex_wait_address != 0) { +        list.push_back(std::make_unique<WaitTreeMutexInfo>(mutex_wait_address)); +    } else {          list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex"))); +    } -    if (thread.status == Kernel::ThreadStatus::WaitSynchAny || -        thread.status == Kernel::ThreadStatus::WaitSynchAll) { -        list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, +    if (thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAny || +        thread.GetStatus() == Kernel::ThreadStatus::WaitSynchAll) { +        list.push_back(std::make_unique<WaitTreeObjectList>(thread.GetWaitObjects(),                                                              thread.IsSleepingOnWaitAll()));      } diff --git a/src/yuzu/discord.h b/src/yuzu/discord.h new file mode 100644 index 000000000..a867cc4d6 --- /dev/null +++ b/src/yuzu/discord.h @@ -0,0 +1,25 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace DiscordRPC { + +class DiscordInterface { +public: +    virtual ~DiscordInterface() = default; + +    virtual void Pause() = 0; +    virtual void Update() = 0; +}; + +class NullImpl : public DiscordInterface { +public: +    ~NullImpl() = default; + +    void Pause() override {} +    void Update() override {} +}; + +} // namespace DiscordRPC diff --git a/src/yuzu/discord_impl.cpp b/src/yuzu/discord_impl.cpp new file mode 100644 index 000000000..9d87a41eb --- /dev/null +++ b/src/yuzu/discord_impl.cpp @@ -0,0 +1,52 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <string> +#include <discord_rpc.h> +#include "common/common_types.h" +#include "core/core.h" +#include "core/loader/loader.h" +#include "yuzu/discord_impl.h" +#include "yuzu/ui_settings.h" + +namespace DiscordRPC { + +DiscordImpl::DiscordImpl() { +    DiscordEventHandlers handlers{}; + +    // The number is the client ID for yuzu, it's used for images and the +    // application name +    Discord_Initialize("471872241299226636", &handlers, 1, nullptr); +} + +DiscordImpl::~DiscordImpl() { +    Discord_ClearPresence(); +    Discord_Shutdown(); +} + +void DiscordImpl::Pause() { +    Discord_ClearPresence(); +} + +void DiscordImpl::Update() { +    s64 start_time = std::chrono::duration_cast<std::chrono::seconds>( +                         std::chrono::system_clock::now().time_since_epoch()) +                         .count(); +    std::string title; +    if (Core::System::GetInstance().IsPoweredOn()) +        Core::System::GetInstance().GetAppLoader().ReadTitle(title); +    DiscordRichPresence presence{}; +    presence.largeImageKey = "yuzu_logo"; +    presence.largeImageText = "yuzu is an emulator for the Nintendo Switch"; +    if (Core::System::GetInstance().IsPoweredOn()) { +        presence.state = title.c_str(); +        presence.details = "Currently in game"; +    } else { +        presence.details = "Not in game"; +    } +    presence.startTimestamp = start_time; +    Discord_UpdatePresence(&presence); +} +} // namespace DiscordRPC diff --git a/src/yuzu/discord_impl.h b/src/yuzu/discord_impl.h new file mode 100644 index 000000000..4bfda8cdf --- /dev/null +++ b/src/yuzu/discord_impl.h @@ -0,0 +1,20 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "yuzu/discord.h" + +namespace DiscordRPC { + +class DiscordImpl : public DiscordInterface { +public: +    DiscordImpl(); +    ~DiscordImpl() override; + +    void Pause() override; +    void Update() override; +}; + +} // namespace DiscordRPC diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 27015d02c..e11833c5a 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -35,6 +35,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include <QtWidgets>  #include <fmt/format.h>  #include "common/common_paths.h" +#include "common/detached_tasks.h"  #include "common/file_util.h"  #include "common/logging/backend.h"  #include "common/logging/filter.h" @@ -65,6 +66,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include "video_core/debug_utils/debug_utils.h"  #include "yuzu/about_dialog.h"  #include "yuzu/bootmanager.h" +#include "yuzu/compatdb.h"  #include "yuzu/compatibility_list.h"  #include "yuzu/configuration/config.h"  #include "yuzu/configuration/configure_dialog.h" @@ -73,12 +75,17 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual  #include "yuzu/debugger/graphics/graphics_surface.h"  #include "yuzu/debugger/profiler.h"  #include "yuzu/debugger/wait_tree.h" +#include "yuzu/discord.h"  #include "yuzu/game_list.h"  #include "yuzu/game_list_p.h"  #include "yuzu/hotkeys.h"  #include "yuzu/main.h"  #include "yuzu/ui_settings.h" +#ifdef USE_DISCORD_PRESENCE +#include "yuzu/discord_impl.h" +#endif +  #ifdef QT_STATICPLUGIN  Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);  #endif @@ -102,23 +109,22 @@ enum class CalloutFlag : uint32_t {      DRDDeprecation = 0x2,  }; -static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { -    if (UISettings::values.callout_flags & static_cast<uint32_t>(flag)) { +void GMainWindow::ShowTelemetryCallout() { +    if (UISettings::values.callout_flags & static_cast<uint32_t>(CalloutFlag::Telemetry)) {          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(); +    UISettings::values.callout_flags |= static_cast<uint32_t>(CalloutFlag::Telemetry); +    const QString telemetry_message = +        tr("<a href='https://yuzu-emu.org/help/feature/telemetry/'>Anonymous " +           "data is collected</a> to help improve yuzu. " +           "<br/><br/>Would you like to share your usage data with us?"); +    if (QMessageBox::question(this, tr("Telemetry"), telemetry_message) != QMessageBox::Yes) { +        Settings::values.enable_telemetry = false; +        Settings::Apply(); +    }  } -void GMainWindow::ShowCallouts() {} -  const int GMainWindow::max_recent_files_item;  static void InitializeLogging() { @@ -145,6 +151,9 @@ GMainWindow::GMainWindow()      default_theme_paths = QIcon::themeSearchPaths();      UpdateUITheme(); +    SetDiscordEnabled(UISettings::values.enable_discord_presence); +    discord_rpc->Update(); +      InitializeWidgets();      InitializeDebugWidgets();      InitializeRecentFileMenuActions(); @@ -168,7 +177,7 @@ GMainWindow::GMainWindow()      game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);      // Show one-time "callout" messages to the user -    ShowCallouts(); +    ShowTelemetryCallout();      QStringList args = QApplication::arguments();      if (args.length() >= 2) { @@ -183,6 +192,9 @@ GMainWindow::~GMainWindow() {  }  void GMainWindow::InitializeWidgets() { +#ifdef YUZU_ENABLE_COMPATIBILITY_REPORTING +    ui.action_Report_Compatibility->setVisible(true); +#endif      render_window = new GRenderWindow(this, emu_thread.get());      render_window->hide(); @@ -411,6 +423,8 @@ void GMainWindow::ConnectMenuEvents() {      connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);      connect(ui.action_Pause, &QAction::triggered, this, &GMainWindow::OnPauseGame);      connect(ui.action_Stop, &QAction::triggered, this, &GMainWindow::OnStopGame); +    connect(ui.action_Report_Compatibility, &QAction::triggered, this, +            &GMainWindow::OnMenuReportCompatibility);      connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });      connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); @@ -471,6 +485,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() {          unsupported_ext.append("ARB_texture_storage");      if (!GLAD_GL_ARB_multi_bind)          unsupported_ext.append("ARB_multi_bind"); +    if (!GLAD_GL_ARB_copy_image) +        unsupported_ext.append("ARB_copy_image");      // Extensions required to support some texture formats.      if (!GLAD_GL_EXT_texture_compression_s3tc) @@ -647,6 +663,7 @@ void GMainWindow::BootGame(const QString& filename) {  }  void GMainWindow::ShutdownGame() { +    discord_rpc->Pause();      emu_thread->RequestStop();      emit EmulationStopping(); @@ -655,6 +672,8 @@ void GMainWindow::ShutdownGame() {      emu_thread->wait();      emu_thread = nullptr; +    discord_rpc->Update(); +      // The emulation is stopped, so closing the window or not does not matter anymore      disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -664,6 +683,7 @@ void GMainWindow::ShutdownGame() {      ui.action_Pause->setEnabled(false);      ui.action_Stop->setEnabled(false);      ui.action_Restart->setEnabled(false); +    ui.action_Report_Compatibility->setEnabled(false);      render_window->hide();      game_list->show();      game_list->setFilterFocus(); @@ -1147,6 +1167,9 @@ void GMainWindow::OnStartGame() {      ui.action_Pause->setEnabled(true);      ui.action_Stop->setEnabled(true);      ui.action_Restart->setEnabled(true); +    ui.action_Report_Compatibility->setEnabled(true); + +    discord_rpc->Update();  }  void GMainWindow::OnPauseGame() { @@ -1161,6 +1184,20 @@ void GMainWindow::OnStopGame() {      ShutdownGame();  } +void GMainWindow::OnMenuReportCompatibility() { +    if (!Settings::values.yuzu_token.empty() && !Settings::values.yuzu_username.empty()) { +        CompatDB compatdb{this}; +        compatdb.exec(); +    } else { +        QMessageBox::critical( +            this, tr("Missing yuzu Account"), +            tr("In order to submit a game compatibility test case, you must link your yuzu " +               "account.<br><br/>To link your yuzu account, go to Emulation > Configuration " +               "> " +               "Web.")); +    } +} +  void GMainWindow::ToggleFullscreen() {      if (!emulation_running) {          return; @@ -1224,11 +1261,14 @@ void GMainWindow::ToggleWindowMode() {  void GMainWindow::OnConfigure() {      ConfigureDialog configureDialog(this, hotkey_registry);      auto old_theme = UISettings::values.theme; +    const bool old_discord_presence = UISettings::values.enable_discord_presence;      auto result = configureDialog.exec();      if (result == QDialog::Accepted) {          configureDialog.applyConfiguration();          if (UISettings::values.theme != old_theme)              UpdateUITheme(); +        if (UISettings::values.enable_discord_presence != old_discord_presence) +            SetDiscordEnabled(UISettings::values.enable_discord_presence);          game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);          config->Save();      } @@ -1443,11 +1483,25 @@ void GMainWindow::UpdateUITheme() {      emit UpdateThemedIcons();  } +void GMainWindow::SetDiscordEnabled(bool state) { +#ifdef USE_DISCORD_PRESENCE +    if (state) { +        discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>(); +    } else { +        discord_rpc = std::make_unique<DiscordRPC::NullImpl>(); +    } +#else +    discord_rpc = std::make_unique<DiscordRPC::NullImpl>(); +#endif +    discord_rpc->Update(); +} +  #ifdef main  #undef main  #endif  int main(int argc, char* argv[]) { +    Common::DetachedTasks detached_tasks;      MicroProfileOnThreadCreate("Frontend");      SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -1465,5 +1519,7 @@ int main(int argc, char* argv[]) {      GMainWindow main_window;      // After settings have been loaded by GMainWindow, apply the filter      main_window.show(); -    return app.exec(); +    int result = app.exec(); +    detached_tasks.WaitForAllTasks(); +    return result;  } diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 8ee9242b1..fe0e9a50a 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -41,6 +41,10 @@ enum class EmulatedDirectoryTarget {      SDMC,  }; +namespace DiscordRPC { +class DiscordInterface; +} +  class GMainWindow : public QMainWindow {      Q_OBJECT @@ -61,6 +65,8 @@ public:      GMainWindow();      ~GMainWindow() override; +    std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; +  signals:      /** @@ -99,7 +105,8 @@ private:      void BootGame(const QString& filename);      void ShutdownGame(); -    void ShowCallouts(); +    void ShowTelemetryCallout(); +    void SetDiscordEnabled(bool state);      /**       * Stores the filename in the recently loaded files list. @@ -135,6 +142,7 @@ private slots:      void OnStartGame();      void OnPauseGame();      void OnStopGame(); +    void OnMenuReportCompatibility();      /// Called whenever a user selects a game in the game list widget.      void OnGameListLoadFile(QString game_path);      void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 3879d4813..cb1664b21 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -45,7 +45,7 @@       <x>0</x>       <y>0</y>       <width>1081</width> -     <height>19</height> +     <height>21</height>      </rect>     </property>     <widget class="QMenu" name="menu_File"> @@ -101,6 +101,8 @@      <property name="title">       <string>&Help</string>      </property> +    <addaction name="action_Report_Compatibility"/> +    <addaction name="separator"/>      <addaction name="action_About"/>     </widget>     <addaction name="menu_File"/> @@ -239,6 +241,18 @@         <string>Restart</string>       </property>     </action> +   <action name="action_Report_Compatibility"> +     <property name="enabled"> +       <bool>false</bool> +     </property> +     <property name="text"> +       <string>Report Compatibility</string> +     </property> +     <property name="visible"> +       <bool>false</bool> +     </property> +   </action>    </widget>   <resources/> + <connections/>  </ui> diff --git a/src/yuzu/ui_settings.cpp b/src/yuzu/ui_settings.cpp index 120b34990..a314493fc 100644 --- a/src/yuzu/ui_settings.cpp +++ b/src/yuzu/ui_settings.cpp @@ -6,5 +6,11 @@  namespace UISettings { +const Themes themes{{ +    {"Default", "default"}, +    {"Dark", "qdarkstyle"}, +}}; +  Values values = {}; -} + +} // namespace UISettings diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 051494bc5..2e617d52a 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -15,9 +15,8 @@ namespace UISettings {  using ContextualShortcut = std::pair<QString, int>;  using Shortcut = std::pair<QString, ContextualShortcut>; -static const std::array<std::pair<QString, QString>, 2> themes = { -    {std::make_pair(QString("Default"), QString("default")), -     std::make_pair(QString("Dark"), QString("qdarkstyle"))}}; +using Themes = std::array<std::pair<const char*, const char*>, 2>; +extern const Themes themes;  struct Values {      QByteArray geometry; @@ -39,6 +38,9 @@ struct Values {      bool confirm_before_closing;      bool first_start; +    // Discord RPC +    bool enable_discord_presence; +      QString roms_path;      QString symbols_path;      QString gamedir;  | 
