mirror of
https://github.com/cemu-project/Cemu.git
synced 2025-04-29 14:59:26 -04:00
2104 lines
77 KiB
C++
2104 lines
77 KiB
C++
#include "gui/wxgui.h"
|
|
#include "gui/GeneralSettings2.h"
|
|
#include "gui/CemuApp.h"
|
|
#include "gui/helpers/wxControlObject.h"
|
|
|
|
#include "util/helpers/helpers.h"
|
|
|
|
#include "Cafe/OS/libs/snd_core/ax.h"
|
|
|
|
#include <wx/collpane.h>
|
|
#include <wx/clrpicker.h>
|
|
#include <wx/cshelp.h>
|
|
#include <wx/textdlg.h>
|
|
#include <wx/hyperlink.h>
|
|
|
|
#include "config/CemuConfig.h"
|
|
#include "config/NetworkSettings.h"
|
|
|
|
#include "audio/IAudioAPI.h"
|
|
#if BOOST_OS_WINDOWS
|
|
#include "audio/DirectSoundAPI.h"
|
|
#include "audio/XAudio27API.h"
|
|
#endif
|
|
#include "audio/CubebAPI.h"
|
|
|
|
#include "audio/IAudioInputAPI.h"
|
|
|
|
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
|
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
|
#include "Cafe/Account/Account.h"
|
|
|
|
#include <boost/tokenizer.hpp>
|
|
#include "util/helpers/SystemException.h"
|
|
#include "gui/dialogs/CreateAccount/wxCreateAccountDialog.h"
|
|
|
|
#if BOOST_OS_WINDOWS
|
|
#include <VersionHelpers.h>
|
|
#endif
|
|
|
|
#include "config/LaunchSettings.h"
|
|
#include "config/ActiveSettings.h"
|
|
#include "gui/helpers/wxHelpers.h"
|
|
|
|
#include "resource/embedded/resources.h"
|
|
|
|
#include "Cafe/CafeSystem.h"
|
|
#include "Cemu/ncrypto/ncrypto.h"
|
|
#include "Cafe/TitleList/TitleList.h"
|
|
#include "wxHelper.h"
|
|
|
|
#include "util/ScreenSaver/ScreenSaver.h"
|
|
|
|
const wxString kDirectSound("DirectSound");
|
|
const wxString kXAudio27("XAudio2.7");
|
|
const wxString kXAudio2("XAudio2");
|
|
const wxString kCubeb("Cubeb");
|
|
|
|
const wxString kPropertyPersistentId("PersistentId");
|
|
const wxString kPropertyMiiName("MiiName");
|
|
const wxString kPropertyBirthday("Birthday");
|
|
const wxString kPropertyGender("Gender");
|
|
const wxString kPropertyEmail("Email");
|
|
const wxString kPropertyCountry("Country");
|
|
|
|
wxDEFINE_EVENT(wxEVT_ACCOUNTLIST_REFRESH, wxCommandEvent);
|
|
|
|
class wxDeviceDescription : public wxClientData
|
|
{
|
|
public:
|
|
wxDeviceDescription(const IAudioAPI::DeviceDescriptionPtr& description) : m_description(description) {}
|
|
const IAudioAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; }
|
|
private:
|
|
IAudioAPI::DeviceDescriptionPtr m_description;
|
|
};
|
|
|
|
class wxInputDeviceDescription : public wxClientData
|
|
{
|
|
public:
|
|
wxInputDeviceDescription(const IAudioInputAPI::DeviceDescriptionPtr& description) : m_description(description) {}
|
|
const IAudioInputAPI::DeviceDescriptionPtr& GetDescription() const { return m_description; }
|
|
private:
|
|
IAudioInputAPI::DeviceDescriptionPtr m_description;
|
|
};
|
|
|
|
class wxVulkanUUID : public wxClientData
|
|
{
|
|
public:
|
|
wxVulkanUUID(const VulkanRenderer::DeviceInfo& info)
|
|
: m_device_info(info) {}
|
|
const VulkanRenderer::DeviceInfo& GetDeviceInfo() const { return m_device_info; }
|
|
|
|
private:
|
|
VulkanRenderer::DeviceInfo m_device_info;
|
|
};
|
|
|
|
class wxAccountData : public wxClientData
|
|
{
|
|
public:
|
|
wxAccountData(const Account& account)
|
|
: m_account(account) {}
|
|
|
|
Account& GetAccount() { return m_account; }
|
|
const Account& GetAccount() const { return m_account; }
|
|
|
|
private:
|
|
Account m_account;
|
|
};
|
|
|
|
wxPanel* GeneralSettings2::AddGeneralPage(wxNotebook* notebook)
|
|
{
|
|
auto* panel = new wxPanel(notebook);
|
|
auto* general_panel_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
{
|
|
auto* box = new wxStaticBox(panel, wxID_ANY, _("Interface"));
|
|
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
{
|
|
auto* first_row = new wxFlexGridSizer(0, 2, 0, 0);
|
|
first_row->SetFlexibleDirection(wxBOTH);
|
|
first_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
first_row->Add(new wxStaticText(box, wxID_ANY, _("Language"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
|
|
wxString language_choices[] = { _("Default") };
|
|
m_language = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(language_choices), language_choices);
|
|
m_language->SetSelection(0);
|
|
m_language->SetToolTip(_("Changes the interface language of Cemu\nAvailable languages are stored in the translation directory\nA restart will be required after changing the language"));
|
|
for (const auto& language : wxGetApp().GetLanguages())
|
|
{
|
|
m_language->Append(language->DescriptionNative);
|
|
}
|
|
|
|
first_row->Add(m_language, 0, wxALL | wxEXPAND, 5);
|
|
|
|
box_sizer->Add(first_row, 1, wxEXPAND, 5);
|
|
}
|
|
|
|
{
|
|
auto* second_row = new wxFlexGridSizer(0, 3, 0, 0);
|
|
second_row->SetFlexibleDirection(wxBOTH);
|
|
second_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
sint32 checkboxCount = 0;
|
|
auto CountRowElement = [&]()
|
|
{
|
|
checkboxCount++;
|
|
if(checkboxCount != 2)
|
|
return;
|
|
second_row->AddSpacer(10);
|
|
checkboxCount = 0;
|
|
};
|
|
|
|
auto InsertEmptyRow = [&]()
|
|
{
|
|
while(checkboxCount != 0)
|
|
CountRowElement();
|
|
second_row->AddSpacer(10);
|
|
second_row->AddSpacer(10);
|
|
second_row->AddSpacer(10);
|
|
};
|
|
|
|
const int topflag = wxALIGN_CENTER_VERTICAL | wxALL;
|
|
m_save_window_position_size = new wxCheckBox(box, wxID_ANY, _("Remember main window position"));
|
|
m_save_window_position_size->SetToolTip(_("Restores the last known window position and size when starting Cemu"));
|
|
second_row->Add(m_save_window_position_size, 0, topflag, 5);
|
|
CountRowElement();
|
|
//second_row->AddSpacer(10);
|
|
m_save_padwindow_position_size = new wxCheckBox(box, wxID_ANY, _("Remember pad window position"));
|
|
m_save_padwindow_position_size->SetToolTip(_("Restores the last known pad window position and size when opening it"));
|
|
second_row->Add(m_save_padwindow_position_size, 0, topflag, 5);
|
|
CountRowElement();
|
|
|
|
const int botflag = wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT | wxBOTTOM;
|
|
m_discord_presence = new wxCheckBox(box, wxID_ANY, _("Discord Presence"));
|
|
m_discord_presence->SetToolTip(_("Enables the Discord Rich Presence feature\nYou will also need to enable it in the Discord settings itself!"));
|
|
second_row->Add(m_discord_presence, 0, botflag, 5);
|
|
CountRowElement();
|
|
#ifndef ENABLE_DISCORD_RPC
|
|
m_discord_presence->Disable();
|
|
#endif
|
|
//second_row->AddSpacer(10);
|
|
m_fullscreen_menubar = new wxCheckBox(box, wxID_ANY, _("Fullscreen menu bar"));
|
|
m_fullscreen_menubar->SetToolTip(_("Displays the menu bar when Cemu is running in fullscreen mode and the mouse cursor is moved to the top"));
|
|
second_row->Add(m_fullscreen_menubar, 0, botflag, 5);
|
|
CountRowElement();
|
|
|
|
m_save_screenshot = new wxCheckBox(box, wxID_ANY, _("Save screenshot"));
|
|
m_save_screenshot->SetToolTip(_("Pressing the screenshot key (F12) will save a screenshot directly to the screenshots folder"));
|
|
second_row->Add(m_save_screenshot, 0, botflag, 5);
|
|
CountRowElement();
|
|
|
|
m_disable_screensaver = new wxCheckBox(box, wxID_ANY, _("Disable screen saver"));
|
|
m_disable_screensaver->SetToolTip(_("Prevents the system from activating the screen saver or going to sleep while running a game."));
|
|
second_row->Add(m_disable_screensaver, 0, botflag, 5);
|
|
CountRowElement();
|
|
|
|
// Enable/disable feral interactive gamemode
|
|
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
|
m_feral_gamemode = new wxCheckBox(box, wxID_ANY, _("Enable Feral GameMode"));
|
|
m_feral_gamemode->SetToolTip(_("Use FeralInteractive GameMode if installed."));
|
|
second_row->Add(m_feral_gamemode, 0, botflag, 5);
|
|
CountRowElement();
|
|
#endif
|
|
|
|
// temporary workaround because feature crashes on macOS
|
|
#if BOOST_OS_MACOS
|
|
m_disable_screensaver->Enable(false);
|
|
#endif
|
|
m_play_boot_sound = new wxCheckBox(box, wxID_ANY, _("Enable intro sound"));
|
|
m_play_boot_sound->SetToolTip(_("Play bootSound file while compiling shaders/pipelines."));
|
|
second_row->Add(m_play_boot_sound, 0, botflag, 5);
|
|
CountRowElement();
|
|
|
|
m_auto_update = new wxCheckBox(box, wxID_ANY, _("Automatically check for updates"));
|
|
m_auto_update->SetToolTip(_("Automatically checks for new cemu versions on startup"));
|
|
second_row->Add(m_auto_update, 0, botflag, 5);
|
|
CountRowElement();
|
|
|
|
m_receive_untested_releases = new wxCheckBox(box, wxID_ANY, _("Receive untested updates"));
|
|
m_receive_untested_releases->SetToolTip(_("When checking for updates, include brand new and untested releases. These may contain bugs!"));
|
|
second_row->Add(m_receive_untested_releases, 0, botflag, 5);
|
|
#if BOOST_OS_LINUX
|
|
if (!std::getenv("APPIMAGE")) {
|
|
m_auto_update->Disable();
|
|
}
|
|
#endif
|
|
|
|
box_sizer->Add(second_row, 0, wxEXPAND, 5);
|
|
}
|
|
|
|
general_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
{
|
|
auto* outerMlcBox = new wxStaticBox(panel, wxID_ANY, _("Custom MLC path"));
|
|
|
|
auto* box_sizer_mlc = new wxStaticBoxSizer(outerMlcBox, wxVERTICAL);
|
|
box_sizer_mlc->Add(new wxStaticText(box_sizer_mlc->GetStaticBox(), wxID_ANY, _("You can configure a custom path for the emulated internal Wii U storage (MLC).\nThis is where Cemu stores saves, accounts and other Wii U system files."), wxDefaultPosition, wxDefaultSize, 0), 0, wxALL, 5);
|
|
|
|
auto* mlcPathLineSizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
m_mlc_path = new wxTextCtrl(outerMlcBox, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_READONLY);
|
|
m_mlc_path->SetMinSize(wxSize(150, -1));
|
|
m_mlc_path->SetToolTip(_("The mlc directory contains your save games and installed game update/dlc data"));
|
|
|
|
mlcPathLineSizer->Add(m_mlc_path, 1, wxALL | wxEXPAND, 5);
|
|
|
|
auto* changePath = new wxButton(outerMlcBox, wxID_ANY, "Change");
|
|
changePath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathSelect, this);
|
|
mlcPathLineSizer->Add(changePath, 0, wxALL, 5);
|
|
if (LaunchSettings::GetMLCPath().has_value())
|
|
changePath->Disable();
|
|
|
|
auto* clearPath = new wxButton(outerMlcBox, wxID_ANY, "Clear custom path");
|
|
clearPath->Bind(wxEVT_BUTTON, &GeneralSettings2::OnMLCPathClear, this);
|
|
mlcPathLineSizer->Add(clearPath, 0, wxALL, 5);
|
|
if (LaunchSettings::GetMLCPath().has_value() || !ActiveSettings::IsCustomMlcPath())
|
|
clearPath->Disable();
|
|
|
|
box_sizer_mlc->Add(mlcPathLineSizer, 0, wxEXPAND, 5);
|
|
general_panel_sizer->Add(box_sizer_mlc, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
{
|
|
auto* general_gamepath_box = new wxStaticBox(panel, wxID_ANY, _("Game Paths"));
|
|
auto* general_gamepath_sizer = new wxStaticBoxSizer(general_gamepath_box, wxVERTICAL);
|
|
|
|
m_game_paths = new wxListBox(general_gamepath_box, wxID_ANY);
|
|
m_game_paths->SetMinSize(wxSize(150, 100));
|
|
m_game_paths->SetToolTip(_("Add the root directory of your game(s). It will scan all directories in it for games"));
|
|
general_gamepath_sizer->Add(m_game_paths, 1, wxALL | wxEXPAND, 5);
|
|
|
|
auto* general_gamepath_buttons = new wxFlexGridSizer(0, 2, 0, 0);
|
|
general_gamepath_buttons->SetFlexibleDirection(wxBOTH);
|
|
general_gamepath_buttons->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
auto* general_gamepath_add_button = new wxButton(general_gamepath_box, wxID_ANY, _("Add"));
|
|
general_gamepath_add_button->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAddPathClicked, this);
|
|
general_gamepath_add_button->SetToolTip(_("Adds a game path to scan for games displayed in the game list\nIf you have unpacked games, make sure to select the root folder of a game"));
|
|
general_gamepath_buttons->Add(general_gamepath_add_button, 0, wxALL, 5);
|
|
|
|
auto* general_gamepath_remove_button = new wxButton(general_gamepath_box, wxID_ANY, _("Remove"));
|
|
general_gamepath_remove_button->Bind(wxEVT_BUTTON, &GeneralSettings2::OnRemovePathClicked, this);
|
|
general_gamepath_remove_button->SetToolTip(_("Removes the currently selected game path from the game list"));
|
|
general_gamepath_buttons->Add(general_gamepath_remove_button, 0, wxALL, 5);
|
|
|
|
general_gamepath_sizer->Add(general_gamepath_buttons, 0, wxEXPAND, 5);
|
|
general_panel_sizer->Add(general_gamepath_sizer, 1, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
panel->SetSizerAndFit(general_panel_sizer);
|
|
|
|
return panel;
|
|
}
|
|
|
|
wxPanel* GeneralSettings2::AddGraphicsPage(wxNotebook* notebook)
|
|
{
|
|
// Graphics page
|
|
auto graphics_panel = new wxPanel(notebook);
|
|
auto graphics_panel_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
{
|
|
auto box = new wxStaticBox(graphics_panel, wxID_ANY, _("General"));
|
|
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
auto row = new wxFlexGridSizer(0, 2, 0, 0);
|
|
row->SetFlexibleDirection(wxBOTH);
|
|
row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
row->Add(new wxStaticText(box, wxID_ANY, _("Graphics API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
|
|
sint32 api_size = 1;
|
|
wxString choices[2] = { "OpenGL" };
|
|
if (g_vulkan_available)
|
|
{
|
|
choices[1] = "Vulkan";
|
|
api_size = 2;
|
|
}
|
|
|
|
m_graphic_api = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, api_size, choices);
|
|
m_graphic_api->SetSelection(0);
|
|
if (api_size > 1)
|
|
m_graphic_api->SetToolTip(_("Select one of the available graphic back ends"));
|
|
row->Add(m_graphic_api, 0, wxALL, 5);
|
|
m_graphic_api->Bind(wxEVT_CHOICE, &GeneralSettings2::OnGraphicAPISelected, this);
|
|
|
|
row->Add(new wxStaticText(box, wxID_ANY, _("Graphics Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_graphic_device = new wxChoice(box, wxID_ANY, wxDefaultPosition, { 230, -1 }, api_size, choices);
|
|
m_graphic_device->SetSelection(0);
|
|
m_graphic_device->SetToolTip(_("Select the used graphic device"));
|
|
row->Add(m_graphic_device, 0, wxALL, 5);
|
|
|
|
row->Add(new wxStaticText(box, wxID_ANY, _("VSync")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_vsync = new wxChoice(box, wxID_ANY, wxDefaultPosition, { 230, -1 });
|
|
m_vsync->SetToolTip(_("Controls the vsync state"));
|
|
row->Add(m_vsync, 0, wxALL, 5);
|
|
|
|
box_sizer->Add(row, 0, wxEXPAND, 5);
|
|
|
|
auto* graphic_misc_row = new wxFlexGridSizer(0, 2, 0, 0);
|
|
|
|
m_async_compile = new wxCheckBox(box, wxID_ANY, _("Async shader compile"));
|
|
m_async_compile->SetToolTip(_("Enables async shader and pipeline compilation. Reduces stutter at the cost of objects not rendering for a short time.\nVulkan only"));
|
|
graphic_misc_row->Add(m_async_compile, 0, wxALL, 5);
|
|
|
|
m_gx2drawdone_sync = new wxCheckBox(box, wxID_ANY, _("Full sync at GX2DrawDone()"));
|
|
m_gx2drawdone_sync->SetToolTip(_("If synchronization is requested by the game, the emulated CPU will wait for the GPU to finish all operations.\nThis is more accurate behavior, but may cause lower performance"));
|
|
graphic_misc_row->Add(m_gx2drawdone_sync, 0, wxALL, 5);
|
|
|
|
box_sizer->Add(graphic_misc_row, 1, wxEXPAND, 5);
|
|
graphics_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
{
|
|
wxString choices[] = { _("Bilinear"), _("Bicubic"), _("Hermite"), _("Nearest Neighbor") };
|
|
m_upscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Upscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS);
|
|
m_upscale_filter->SetToolTip(_("Upscaling filters are used when the game resolution is smaller than the window size"));
|
|
graphics_panel_sizer->Add(m_upscale_filter, 0, wxALL | wxEXPAND, 5);
|
|
|
|
m_downscale_filter = new wxRadioBox(graphics_panel, wxID_ANY, _("Downscale filter"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS);
|
|
m_downscale_filter->SetToolTip(_("Downscaling filters are used when the game resolution is bigger than the window size"));
|
|
graphics_panel_sizer->Add(m_downscale_filter, 0, wxALL | wxEXPAND, 5);
|
|
}
|
|
|
|
{
|
|
wxString choices[] = { _("Keep aspect ratio"), _("Stretch") };
|
|
m_fullscreen_scaling = new wxRadioBox(graphics_panel, wxID_ANY, _("Fullscreen scaling"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 5, wxRA_SPECIFY_COLS);
|
|
m_fullscreen_scaling->SetToolTip(_("Controls the output aspect ratio when it doesn't match the ratio of the game"));
|
|
graphics_panel_sizer->Add(m_fullscreen_scaling, 0, wxALL | wxEXPAND, 5);
|
|
}
|
|
|
|
graphics_panel->SetSizerAndFit(graphics_panel_sizer);
|
|
return graphics_panel;
|
|
}
|
|
|
|
wxPanel* GeneralSettings2::AddAudioPage(wxNotebook* notebook)
|
|
{
|
|
auto audio_panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
|
auto audio_panel_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
{
|
|
auto box = new wxStaticBox(audio_panel, wxID_ANY, _("General"));
|
|
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
auto audio_general_row = new wxFlexGridSizer(0, 3, 0, 0);
|
|
audio_general_row->SetFlexibleDirection(wxBOTH);
|
|
audio_general_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
audio_general_row->Add(new wxStaticText(box, wxID_ANY, _("API")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
|
|
m_audio_api = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr);
|
|
if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::DirectSound))
|
|
m_audio_api->Append(kDirectSound);
|
|
if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::XAudio27))
|
|
m_audio_api->Append(kXAudio27);
|
|
if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::XAudio2))
|
|
m_audio_api->Append(kXAudio2);
|
|
if (IAudioAPI::IsAudioAPIAvailable(IAudioAPI::Cubeb))
|
|
m_audio_api->Append(kCubeb);
|
|
|
|
m_audio_api->SetSelection(0);
|
|
m_audio_api->SetToolTip(_("Select one of the available audio back ends"));
|
|
audio_general_row->Add(m_audio_api, 0, wxALL, 5);
|
|
|
|
m_audio_api->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioAPISelected, this);
|
|
|
|
audio_general_row->AddSpacer(0);
|
|
|
|
audio_general_row->Add(new wxStaticText(box, wxID_ANY, _("Latency")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_audio_latency = new wxSlider(box, wxID_ANY, 2, 0, IAudioAPI::kBlockCount - 1, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL);
|
|
m_audio_latency->SetToolTip(_("Controls the amount of buffered audio data\nHigher values will create a delay in audio playback, but may avoid audio problems when emulation is too slow"));
|
|
audio_general_row->Add(m_audio_latency, 0, wxEXPAND | wxALL, 5);
|
|
auto latency_text = new wxStaticText(box, wxID_ANY, "24ms");
|
|
audio_general_row->Add(latency_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5);
|
|
m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnLatencySliderChanged, this, wxID_ANY, wxID_ANY, new wxControlObject(latency_text));
|
|
m_audio_latency->Bind(wxEVT_SLIDER, &GeneralSettings2::OnAudioLatencyChanged, this);
|
|
|
|
box_sizer->Add(audio_general_row, 1, wxEXPAND, 5);
|
|
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
const wxString audio_channel_choices[] = { _("Mono"), _("Stereo") , _("Surround") };
|
|
{
|
|
auto box = new wxStaticBox(audio_panel, wxID_ANY, _("TV"));
|
|
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
auto audio_tv_row = new wxFlexGridSizer(0, 3, 0, 0);
|
|
audio_tv_row->SetFlexibleDirection(wxBOTH);
|
|
audio_tv_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_tv_device = new wxChoice(box, wxID_ANY);
|
|
m_tv_device->SetMinSize(wxSize(300, -1));
|
|
m_tv_device->SetToolTip(_("Select the active audio output device for Wii U TV"));
|
|
audio_tv_row->Add(m_tv_device, 0, wxEXPAND | wxALL, 5);
|
|
audio_tv_row->AddSpacer(0);
|
|
|
|
m_tv_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this);
|
|
|
|
audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
|
|
m_tv_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_choices), audio_channel_choices);
|
|
|
|
m_tv_channels->SetSelection(1); // set default to stereo
|
|
m_tv_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this);
|
|
audio_tv_row->Add(m_tv_channels, 0, wxEXPAND | wxALL, 5);
|
|
audio_tv_row->AddSpacer(0);
|
|
|
|
audio_tv_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_tv_volume = new wxSlider(box, wxID_ANY, 100, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL);
|
|
audio_tv_row->Add(m_tv_volume, 0, wxEXPAND | wxALL, 5);
|
|
auto audio_tv_volume_text = new wxStaticText(box, wxID_ANY, "100%");
|
|
audio_tv_row->Add(audio_tv_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5);
|
|
|
|
m_tv_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_tv_volume_text));
|
|
m_tv_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this);
|
|
|
|
box_sizer->Add(audio_tv_row, 1, wxEXPAND, 5);
|
|
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
{
|
|
auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Gamepad"));
|
|
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
auto audio_pad_row = new wxFlexGridSizer(0, 3, 0, 0);
|
|
audio_pad_row->SetFlexibleDirection(wxBOTH);
|
|
audio_pad_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_pad_device = new wxChoice(box, wxID_ANY, wxDefaultPosition);
|
|
m_pad_device->SetMinSize(wxSize(300, -1));
|
|
m_pad_device->SetToolTip(_("Select the active audio output device for Wii U GamePad"));
|
|
audio_pad_row->Add(m_pad_device, 0, wxEXPAND | wxALL, 5);
|
|
audio_pad_row->AddSpacer(0);
|
|
|
|
m_pad_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this);
|
|
|
|
const wxString audio_channel_drc_choices[] = { _("Stereo") }; // stereo for now only
|
|
|
|
audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_pad_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices);
|
|
|
|
m_pad_channels->SetSelection(0); // set default to stereo
|
|
|
|
m_pad_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this);
|
|
audio_pad_row->Add(m_pad_channels, 0, wxEXPAND | wxALL, 5);
|
|
audio_pad_row->AddSpacer(0);
|
|
|
|
audio_pad_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_pad_volume = new wxSlider(box, wxID_ANY, 100, 0, 100);
|
|
audio_pad_row->Add(m_pad_volume, 0, wxEXPAND | wxALL, 5);
|
|
auto audio_pad_volume_text = new wxStaticText(box, wxID_ANY, "100%");
|
|
audio_pad_row->Add(audio_pad_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5);
|
|
|
|
m_pad_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_pad_volume_text));
|
|
m_pad_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this);
|
|
|
|
box_sizer->Add(audio_pad_row, 1, wxEXPAND, 5);
|
|
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
{
|
|
auto box = new wxStaticBox(audio_panel, wxID_ANY, _("Microphone (Experimental)"));
|
|
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
auto audio_input_row = new wxFlexGridSizer(0, 3, 0, 0);
|
|
audio_input_row->SetFlexibleDirection(wxBOTH);
|
|
audio_input_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Device")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_input_device = new wxChoice(box, wxID_ANY, wxDefaultPosition);
|
|
m_input_device->SetMinSize(wxSize(300, -1));
|
|
m_input_device->SetToolTip(_("Select the active audio input device for Wii U GamePad"));
|
|
audio_input_row->Add(m_input_device, 0, wxEXPAND | wxALL, 5);
|
|
audio_input_row->AddSpacer(0);
|
|
|
|
m_input_device->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioDeviceSelected, this);
|
|
|
|
const wxString audio_channel_drc_choices[] = { _("Mono") }; // mono for now only
|
|
|
|
audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Channels")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_input_channels = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(audio_channel_drc_choices), audio_channel_drc_choices);
|
|
|
|
m_input_channels->SetSelection(0); // set default to stereo
|
|
|
|
m_input_channels->Bind(wxEVT_CHOICE, &GeneralSettings2::OnAudioChannelsSelected, this);
|
|
audio_input_row->Add(m_input_channels, 0, wxEXPAND | wxALL, 5);
|
|
audio_input_row->AddSpacer(0);
|
|
|
|
audio_input_row->Add(new wxStaticText(box, wxID_ANY, _("Volume")), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
m_input_volume = new wxSlider(box, wxID_ANY, 100, 0, 100);
|
|
audio_input_row->Add(m_input_volume, 0, wxEXPAND | wxALL, 5);
|
|
auto audio_input_volume_text = new wxStaticText(box, wxID_ANY, "100%");
|
|
audio_input_row->Add(audio_input_volume_text, 0, wxALIGN_CENTER_VERTICAL | wxALL | wxALIGN_RIGHT, 5);
|
|
|
|
m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnSliderChangedPercent, this, wxID_ANY, wxID_ANY, new wxControlObject(audio_input_volume_text));
|
|
m_input_volume->Bind(wxEVT_SLIDER, &GeneralSettings2::OnVolumeChanged, this);
|
|
|
|
box_sizer->Add(audio_input_row, 1, wxEXPAND, 5);
|
|
audio_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
audio_panel->SetSizerAndFit(audio_panel_sizer);
|
|
return audio_panel;
|
|
}
|
|
|
|
wxPanel* GeneralSettings2::AddOverlayPage(wxNotebook* notebook)
|
|
{
|
|
auto* panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
|
auto* panel_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
const wxString positions[]{ _("Disabled"), _("Top left"), _("Top center"), _("Top right"), _("Bottom left"), _("Bottom center"), _("Bottom right") };
|
|
const wxString text_scale[]{ "50%", "75%", "100%", "125%", "150%", "175%", "200%", "225%", "250%", "275%", "300%" };
|
|
{
|
|
auto box = new wxStaticBox(panel, wxID_ANY, _("Overlay"));
|
|
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
auto position_row = new wxFlexGridSizer(1, 0, 0, 0);
|
|
position_row->SetFlexibleDirection(wxBOTH);
|
|
position_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
{
|
|
position_row->Add(new wxStaticText(box, wxID_ANY, _("Position")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
|
m_overlay_position = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(positions), positions);
|
|
m_overlay_position->SetSelection(0);
|
|
m_overlay_position->SetToolTip(_("Controls the overlay which displays technical information while playing"));
|
|
position_row->Add(m_overlay_position, 0, wxALL, 5);
|
|
|
|
position_row->AddSpacer(25);
|
|
|
|
position_row->Add(new wxStaticText(box, wxID_ANY, _("Text Color")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5);
|
|
m_overlay_font_color = new wxColourPickerCtrl(box, wxID_ANY, *wxWHITE, wxDefaultPosition, wxDefaultSize, wxCLRP_SHOW_ALPHA);
|
|
m_overlay_font_color->SetToolTip(_("Sets the text color of the overlay"));
|
|
position_row->Add(m_overlay_font_color, 0, wxEXPAND | wxALL, 5);
|
|
|
|
position_row->AddSpacer(25);
|
|
|
|
position_row->Add(new wxStaticText(box, wxID_ANY, _("Scale")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5);
|
|
m_overlay_scale = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(text_scale), text_scale);
|
|
m_overlay_scale->SetToolTip(_("Sets the scale of the overlay text"));
|
|
position_row->Add(m_overlay_scale, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
box_sizer->Add(position_row, 0, wxEXPAND, 5);
|
|
|
|
auto settings2_row = new wxFlexGridSizer(0, 4, 2, 0);
|
|
{
|
|
m_overlay_fps = new wxCheckBox(box, wxID_ANY, _("FPS"));
|
|
m_overlay_fps->SetToolTip(_("The number of frames per second. Average over last 5 seconds"));
|
|
settings2_row->Add(m_overlay_fps, 0, wxALL, 5);
|
|
|
|
m_overlay_drawcalls = new wxCheckBox(box, wxID_ANY, _("Draw calls per frame"));
|
|
m_overlay_drawcalls->SetToolTip(_("The number of draw calls per frame. Average over last 5 seconds"));
|
|
settings2_row->Add(m_overlay_drawcalls, 0, wxALL, 5);
|
|
|
|
m_overlay_cpu = new wxCheckBox(box, wxID_ANY, _("CPU usage"));
|
|
m_overlay_cpu->SetToolTip(_("CPU usage of Cemu in percent"));
|
|
settings2_row->Add(m_overlay_cpu, 0, wxALL, 5);
|
|
|
|
m_overlay_cpu_per_core = new wxCheckBox(box, wxID_ANY, _("CPU per core usage"));
|
|
m_overlay_cpu_per_core->SetToolTip(_("Total cpu usage in percent for each core"));
|
|
settings2_row->Add(m_overlay_cpu_per_core, 0, wxALL, 5);
|
|
|
|
m_overlay_ram = new wxCheckBox(box, wxID_ANY, _("RAM usage"));
|
|
m_overlay_ram->SetToolTip(_("Cemu RAM usage in MB"));
|
|
settings2_row->Add(m_overlay_ram, 0, wxALL, 5);
|
|
|
|
m_overlay_vram = new wxCheckBox(box, wxID_ANY, _("VRAM usage"));
|
|
#if BOOST_OS_WINDOWS
|
|
using RtlGetVersion_t = LONG(WINAPI*)(PRTL_OSVERSIONINFOW lpVersionInformation);
|
|
const auto pRtlGetVersion = (RtlGetVersion_t)GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion");
|
|
//if(IsWindows8Point1OrGreater()) requires manifest
|
|
RTL_OSVERSIONINFOW info{};
|
|
// Windows 8.1 6.3*
|
|
if (pRtlGetVersion && pRtlGetVersion(&info) == 0 && ((info.dwMajorVersion == 6 && info.dwMinorVersion >= 3) || info.dwMajorVersion > 6))
|
|
m_overlay_vram->SetToolTip(_("The VRAM usage of Cemu in MB"));
|
|
else
|
|
{
|
|
m_overlay_vram->SetToolTip(_("This option requires Win8.1+"));
|
|
m_overlay_vram->Disable();
|
|
}
|
|
#else
|
|
m_overlay_vram->SetToolTip(_("The VRAM usage of Cemu in MB"));
|
|
#endif
|
|
|
|
settings2_row->Add(m_overlay_vram, 0, wxALL, 5);
|
|
|
|
m_overlay_debug = new wxCheckBox(box, wxID_ANY, _("Debug"));
|
|
m_overlay_debug->SetToolTip(_("Displays internal debug information (Vulkan only)"));
|
|
settings2_row->Add(m_overlay_debug, 0, wxALL, 5);
|
|
}
|
|
box_sizer->Add(settings2_row, 0, wxEXPAND, 5);
|
|
|
|
panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
{
|
|
auto box = new wxStaticBox(panel, wxID_ANY, _("Notifications"));
|
|
auto box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
auto position_row = new wxFlexGridSizer(1, 0, 0, 0);
|
|
position_row->SetFlexibleDirection(wxBOTH);
|
|
position_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
{
|
|
position_row->Add(new wxStaticText(box, wxID_ANY, _("Position")), 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
|
|
|
|
m_notification_position = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(positions), positions);
|
|
m_notification_position->SetSelection(0);
|
|
m_notification_position->SetToolTip(_("Controls the notification position while playing"));
|
|
position_row->Add(m_notification_position, 0, wxALL, 5);
|
|
|
|
position_row->AddSpacer(25);
|
|
|
|
position_row->Add(new wxStaticText(box, wxID_ANY, _("Text Color")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5);
|
|
m_notification_font_color = new wxColourPickerCtrl(box, wxID_ANY, *wxWHITE, wxDefaultPosition, wxDefaultSize, wxCLRP_SHOW_ALPHA);
|
|
m_notification_font_color->SetToolTip(_("Sets the text color of notifications"));
|
|
position_row->Add(m_notification_font_color, 0, wxEXPAND | wxALL, 5);
|
|
|
|
position_row->Add(new wxStaticText(box, wxID_ANY, _("Scale")), 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5);
|
|
m_notification_scale = new wxChoice(box, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(text_scale), text_scale);
|
|
m_notification_scale->SetToolTip(_("Sets the scale of the notification text"));
|
|
position_row->Add(m_notification_scale, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
box_sizer->Add(position_row, 0, wxEXPAND, 5);
|
|
|
|
auto settings1_row = new wxFlexGridSizer(1, 0, 2, 0);
|
|
{
|
|
m_controller_profile_name = new wxCheckBox(box, wxID_ANY, _("Controller profiles"));
|
|
m_controller_profile_name->SetToolTip(_("Displays the active controller profile when starting a game"));
|
|
settings1_row->Add(m_controller_profile_name, 0, wxALL, 5);
|
|
|
|
m_controller_low_battery = new wxCheckBox(box, wxID_ANY, _("Low battery"));
|
|
m_controller_low_battery->SetToolTip(_("Shows a notification when a low controller battery has been detected"));
|
|
settings1_row->Add(m_controller_low_battery, 0, wxALL, 5);
|
|
|
|
m_shader_compiling = new wxCheckBox(box, wxID_ANY, _("Shader compiler"));
|
|
m_shader_compiling->SetToolTip(_("Shows a notification after shaders have been compiled"));
|
|
settings1_row->Add(m_shader_compiling, 0, wxALL, 5);
|
|
|
|
m_friends_data = new wxCheckBox(box, wxID_ANY, _("Friend list"));
|
|
m_friends_data->SetToolTip(_("Shows friend list related data if online"));
|
|
settings1_row->Add(m_friends_data, 0, wxALL, 5);
|
|
}
|
|
box_sizer->Add(settings1_row, 0, wxEXPAND, 5);
|
|
|
|
|
|
panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
panel->SetSizerAndFit(panel_sizer);
|
|
|
|
return panel;
|
|
}
|
|
|
|
wxPanel* GeneralSettings2::AddAccountPage(wxNotebook* notebook)
|
|
{
|
|
auto* online_panel = new wxPanel(notebook);
|
|
auto* online_panel_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
{
|
|
auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Account settings"));
|
|
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
auto* content = new wxFlexGridSizer(0, 4, 0, 0);
|
|
content->SetFlexibleDirection(wxBOTH);
|
|
content->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
content->AddGrowableCol(1, 1);
|
|
content->AddGrowableCol(2, 0);
|
|
content->AddGrowableCol(3, 0);
|
|
|
|
content->Add(new wxStaticText(box, wxID_ANY, _("Active account")), 1, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
|
|
m_active_account = new wxChoice(box, wxID_ANY);
|
|
m_active_account->SetMinSize({ 250, -1 });
|
|
content->Add(m_active_account, 0, wxEXPAND | wxALL, 5);
|
|
m_active_account->Bind(wxEVT_CHOICE, &GeneralSettings2::OnActiveAccountChanged, this);
|
|
|
|
m_create_account = new wxButton(box, wxID_ANY, _("Create"));
|
|
content->Add(m_create_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5);
|
|
m_create_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountCreate, this);
|
|
|
|
m_delete_account = new wxButton(box, wxID_ANY, _("Delete"));
|
|
content->Add(m_delete_account, 0, wxEXPAND | wxALL | wxALIGN_RIGHT, 5);
|
|
m_delete_account->Bind(wxEVT_BUTTON, &GeneralSettings2::OnAccountDelete, this);
|
|
|
|
box_sizer->Add(content, 1, wxEXPAND, 5);
|
|
|
|
online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
|
|
if (CafeSystem::IsTitleRunning())
|
|
{
|
|
m_active_account->Enable(false);
|
|
m_create_account->Enable(false);
|
|
m_delete_account->Enable(false);
|
|
}
|
|
}
|
|
|
|
|
|
{
|
|
wxString choices[] = { _("Offline"), _("Nintendo"), _("Pretendo"), _("Custom") };
|
|
m_active_service = new wxRadioBox(online_panel, wxID_ANY, _("Network Service"), wxDefaultPosition, wxDefaultSize, std::size(choices), choices, 4, wxRA_SPECIFY_COLS);
|
|
if (!NetworkConfig::XMLExists())
|
|
m_active_service->Enable(3, false);
|
|
|
|
m_active_service->SetItemToolTip(0, _("Online functionality disabled for this account"));
|
|
m_active_service->SetItemToolTip(1, _("Connect to the official Nintendo Network Service"));
|
|
m_active_service->SetItemToolTip(2, _("Connect to the Pretendo Network Service"));
|
|
m_active_service->SetItemToolTip(3, _("Connect to a custom Network Service (configured via network_services.xml)"));
|
|
|
|
m_active_service->Bind(wxEVT_RADIOBOX, &GeneralSettings2::OnAccountServiceChanged,this);
|
|
online_panel_sizer->Add(m_active_service, 0, wxEXPAND | wxALL, 5);
|
|
|
|
if (CafeSystem::IsTitleRunning())
|
|
{
|
|
m_active_service->Enable(false);
|
|
}
|
|
}
|
|
|
|
{
|
|
auto* box = new wxStaticBox(online_panel, wxID_ANY, _("Online play requirements"));
|
|
auto* box_sizer = new wxStaticBoxSizer(box, wxVERTICAL);
|
|
|
|
auto* row = new wxFlexGridSizer(0, 2, 0, 0);
|
|
row->SetFlexibleDirection(wxBOTH);
|
|
row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
const wxImage tmp = wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage();
|
|
m_validate_online = new wxBitmapButton(box, wxID_ANY, tmp.Scale(16, 16));
|
|
m_validate_online->Bind(wxEVT_BUTTON, &GeneralSettings2::OnShowOnlineValidator, this);
|
|
row->Add(m_validate_online, 0, wxEXPAND | wxALL, 5);
|
|
|
|
m_online_status = new wxStaticText(box, wxID_ANY, _("No account selected"));
|
|
row->Add(m_online_status, 1, wxALL | wxALIGN_CENTRE_VERTICAL, 5);
|
|
|
|
box_sizer->Add(row, 1, wxEXPAND, 5);
|
|
|
|
auto* tutorial_link = new wxHyperlinkCtrl(box, wxID_ANY, _("Online play tutorial"), "https://cemu.info/online-guide");
|
|
box_sizer->Add(tutorial_link, 0, wxALL, 5);
|
|
|
|
|
|
online_panel_sizer->Add(box_sizer, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
{
|
|
m_account_information = new wxCollapsiblePane(online_panel, wxID_ANY, _("Account information"));
|
|
#if BOOST_OS_WINDOWS
|
|
m_account_information->GetControlWidget()->SetBackgroundColour(*wxWHITE);
|
|
#endif
|
|
auto win = m_account_information->GetPane();
|
|
|
|
auto content = new wxBoxSizer(wxVERTICAL);
|
|
|
|
m_account_grid = new wxPropertyGrid(win, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_HIDE_MARGIN | wxPG_STATIC_SPLITTER);
|
|
m_account_grid->SetExtraStyle(wxPG_EX_HELP_AS_TOOLTIPS);
|
|
m_account_grid->SetMinSize({ 300, -1 });
|
|
//m_account_grid->Append(new wxPropertyCategory("Main"));
|
|
|
|
auto* persistent_id_gprop = m_account_grid->Append(new wxStringProperty("PersistentId", kPropertyPersistentId));
|
|
persistent_id_gprop->SetHelpString(_("The persistent id is the internal folder name used for your saves"));
|
|
m_account_grid->SetPropertyReadOnly(persistent_id_gprop);
|
|
|
|
m_account_grid->Append(new wxStringProperty(_("Mii name"), kPropertyMiiName))->SetHelpString(_("The mii name is the profile name"));
|
|
m_account_grid->Append(new wxStringProperty(_("Birthday"), kPropertyBirthday));
|
|
|
|
wxPGChoices gender;
|
|
gender.Add(_("Female"), 0);
|
|
gender.Add(_("Male"), 1);
|
|
m_account_grid->Append(new wxEnumProperty(_("Gender"), kPropertyGender, gender));
|
|
|
|
m_account_grid->Append(new wxStringProperty(_("Email"), kPropertyEmail));
|
|
|
|
wxPGChoices countries;
|
|
for (int i = 0; i < NCrypto::GetCountryCount(); ++i)
|
|
{
|
|
const auto country = NCrypto::GetCountryAsString(i);
|
|
if (country && (i == 0 || !boost::equals(country, "NN")))
|
|
{
|
|
countries.Add(country, i);
|
|
}
|
|
}
|
|
m_account_grid->Append(new wxEnumProperty(_("Country"), kPropertyCountry, countries));
|
|
|
|
m_account_grid->Bind(wxEVT_PG_CHANGED, &GeneralSettings2::OnAccountSettingsChanged, this);
|
|
|
|
content->Add(m_account_grid, 1, wxEXPAND | wxALL, 5);
|
|
|
|
win->SetSizer(content);
|
|
content->SetSizeHints(win);
|
|
|
|
online_panel_sizer->Add(m_account_information, 0, wxEXPAND | wxALL, 5);
|
|
}
|
|
|
|
online_panel->SetSizerAndFit(online_panel_sizer);
|
|
return online_panel;
|
|
}
|
|
|
|
wxPanel* GeneralSettings2::AddDebugPage(wxNotebook* notebook)
|
|
{
|
|
auto* panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
|
|
auto* debug_panel_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
{
|
|
auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0);
|
|
debug_row->SetFlexibleDirection(wxBOTH);
|
|
debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
debug_row->Add(new wxStaticText(panel, wxID_ANY, _("Crash dump"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
|
|
#if BOOST_OS_WINDOWS
|
|
wxString dump_choices[] = {_("Disabled"), _("Lite"), _("Full")};
|
|
#elif BOOST_OS_UNIX
|
|
wxString dump_choices[] = {_("Disabled"), _("Enabled")};
|
|
#endif
|
|
m_crash_dump = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(dump_choices), dump_choices);
|
|
m_crash_dump->SetSelection(0);
|
|
#if BOOST_OS_WINDOWS
|
|
m_crash_dump->SetToolTip(_("Creates a dump when Cemu crashes\nOnly enable when requested by a developer!\nThe Full option will create a very large dump file (includes a full RAM dump of the Cemu process)"));
|
|
#elif BOOST_OS_UNIX
|
|
m_crash_dump->SetToolTip(_("Creates a core dump when Cemu crashes\nOnly enable when requested by a developer!"));
|
|
#endif
|
|
debug_row->Add(m_crash_dump, 0, wxALL | wxEXPAND, 5);
|
|
debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5);
|
|
}
|
|
|
|
{
|
|
auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0);
|
|
debug_row->SetFlexibleDirection(wxBOTH);
|
|
debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
|
|
|
|
debug_row->Add(new wxStaticText(panel, wxID_ANY, _("GDB Stub port"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
|
|
|
|
m_gdb_port = new wxSpinCtrl(panel, wxID_ANY, "1337", wxDefaultPosition, wxDefaultSize, 0, 1000, 65535);
|
|
m_gdb_port->SetToolTip(_("Changes the port that the GDB stub will use, which you can use by either starting Cemu with the --enable-gdbstub option or by enabling it the Debug tab."));
|
|
|
|
debug_row->Add(m_gdb_port, 0, wxALL | wxEXPAND, 5);
|
|
debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5);
|
|
}
|
|
|
|
panel->SetSizerAndFit(debug_panel_sizer);
|
|
|
|
return panel;
|
|
}
|
|
|
|
GeneralSettings2::GeneralSettings2(wxWindow* parent, bool game_launched)
|
|
: wxDialog(parent, wxID_ANY, _("General settings"), wxDefaultPosition, wxDefaultSize, wxCLOSE_BOX | wxCLIP_CHILDREN | wxCAPTION | wxRESIZE_BORDER), m_game_launched(game_launched)
|
|
{
|
|
SetIcon(wxICON(X_SETTINGS));
|
|
|
|
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
|
auto* notebook = new wxNotebook(this, wxID_ANY);
|
|
|
|
notebook->AddPage(AddGeneralPage(notebook), _("General"));
|
|
notebook->AddPage(AddGraphicsPage(notebook), _("Graphics"));
|
|
notebook->AddPage(AddAudioPage(notebook), _("Audio"));
|
|
notebook->AddPage(AddOverlayPage(notebook), _("Overlay"));
|
|
notebook->AddPage(AddAccountPage(notebook), _("Account"));
|
|
notebook->AddPage(AddDebugPage(notebook), _("Debug"));
|
|
|
|
Bind(wxEVT_CLOSE_WINDOW, &GeneralSettings2::OnClose, this);
|
|
|
|
//
|
|
|
|
sizer->Add(notebook, 1, wxEXPAND | wxALL, 5);
|
|
|
|
SetSizerAndFit(sizer);
|
|
Layout();
|
|
Centre(wxBOTH);
|
|
|
|
//
|
|
|
|
UpdateOnlineAccounts();
|
|
UpdateAudioDeviceList();
|
|
|
|
ApplyConfig();
|
|
HandleGraphicsApiSelection();
|
|
|
|
DisableSettings(game_launched);
|
|
}
|
|
|
|
uint32 GeneralSettings2::GetSelectedAccountPersistentId()
|
|
{
|
|
const auto active_account = m_active_account->GetSelection();
|
|
if (active_account == wxNOT_FOUND)
|
|
return GetConfig().account.m_persistent_id.GetInitValue();
|
|
return dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(active_account))->GetAccount().GetPersistentId();
|
|
}
|
|
|
|
void GeneralSettings2::StoreConfig()
|
|
{
|
|
auto* app = (CemuApp*)wxTheApp;
|
|
auto& config = GetConfig();
|
|
|
|
config.use_discord_presence = m_discord_presence->IsChecked();
|
|
config.fullscreen_menubar = m_fullscreen_menubar->IsChecked();
|
|
config.check_update = m_auto_update->IsChecked();
|
|
config.save_screenshot = m_save_screenshot->IsChecked();
|
|
config.receive_untested_updates = m_receive_untested_releases->IsChecked();
|
|
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
|
config.feral_gamemode = m_feral_gamemode->IsChecked();
|
|
#endif
|
|
config.play_boot_sound = m_play_boot_sound->IsChecked();
|
|
config.disable_screensaver = m_disable_screensaver->IsChecked();
|
|
// Toggle while a game is running
|
|
if (CafeSystem::IsTitleRunning())
|
|
{
|
|
ScreenSaver::SetInhibit(config.disable_screensaver);
|
|
}
|
|
|
|
|
|
// -1 is default wx widget value -> set to dummy 0 so mainwindow and padwindow will update it
|
|
config.window_position = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1};
|
|
config.window_size = m_save_window_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1};
|
|
config.pad_position = m_save_padwindow_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1};
|
|
config.pad_size = m_save_padwindow_position_size->IsChecked() ? Vector2i{ 0,0 } : Vector2i{-1,-1};
|
|
|
|
config.game_paths.clear();
|
|
for (auto& path : m_game_paths->GetStrings())
|
|
config.game_paths.emplace_back(path.utf8_string());
|
|
|
|
auto selection = m_language->GetSelection();
|
|
if (selection == 0)
|
|
GetConfig().language = wxLANGUAGE_DEFAULT;
|
|
else
|
|
{
|
|
const auto language = m_language->GetStringSelection();
|
|
for (const auto& lang : app->GetLanguages())
|
|
{
|
|
if (lang->DescriptionNative == language)
|
|
{
|
|
GetConfig().language = lang->Language;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// audio
|
|
if (m_audio_api->GetStringSelection() == kDirectSound)
|
|
config.audio_api = IAudioAPI::DirectSound;
|
|
else if (m_audio_api->GetStringSelection() == kXAudio27)
|
|
config.audio_api = IAudioAPI::XAudio27;
|
|
else if (m_audio_api->GetStringSelection() == kXAudio2)
|
|
config.audio_api = IAudioAPI::XAudio2;
|
|
else if (m_audio_api->GetStringSelection() == kCubeb)
|
|
config.audio_api = IAudioAPI::Cubeb;
|
|
|
|
config.audio_delay = m_audio_latency->GetValue();
|
|
config.tv_channels = (AudioChannels)m_tv_channels->GetSelection();
|
|
//config.pad_channels = (AudioChannels)m_pad_channels->GetSelection();
|
|
config.pad_channels = kStereo; // (AudioChannels)m_pad_channels->GetSelection();
|
|
//config.input_channels = (AudioChannels)m_input_channels->GetSelection();
|
|
config.input_channels = kMono; // (AudioChannels)m_input_channels->GetSelection();
|
|
|
|
config.tv_volume = m_tv_volume->GetValue();
|
|
config.pad_volume = m_pad_volume->GetValue();
|
|
config.input_volume = m_input_volume->GetValue();
|
|
|
|
config.tv_device.clear();
|
|
const auto tv_device = m_tv_device->GetSelection();
|
|
if (tv_device != wxNOT_FOUND && tv_device != 0 && m_tv_device->HasClientObjectData())
|
|
{
|
|
const auto* device_description = (wxDeviceDescription*)m_tv_device->GetClientObject(tv_device);
|
|
if(device_description)
|
|
config.tv_device = device_description->GetDescription()->GetIdentifier();
|
|
}
|
|
|
|
config.pad_device.clear();
|
|
const auto pad_device = m_pad_device->GetSelection();
|
|
if (pad_device != wxNOT_FOUND && pad_device != 0 && m_pad_device->HasClientObjectData())
|
|
{
|
|
const auto* device_description = (wxDeviceDescription*)m_pad_device->GetClientObject(pad_device);
|
|
if (device_description)
|
|
config.pad_device = device_description->GetDescription()->GetIdentifier();
|
|
}
|
|
|
|
config.input_device = L"";
|
|
const auto input_device = m_input_device->GetSelection();
|
|
if (input_device != wxNOT_FOUND && input_device != 0 && m_input_device->HasClientObjectData())
|
|
{
|
|
const auto* device_description = (wxDeviceDescription*)m_input_device->GetClientObject(input_device);
|
|
if (device_description)
|
|
config.input_device = device_description->GetDescription()->GetIdentifier();
|
|
}
|
|
|
|
// graphics
|
|
config.graphic_api = (GraphicAPI)m_graphic_api->GetSelection();
|
|
|
|
selection = m_graphic_device->GetSelection();
|
|
if(selection != wxNOT_FOUND)
|
|
{
|
|
const auto* info = (wxVulkanUUID*)m_graphic_device->GetClientObject(selection);
|
|
if(info)
|
|
config.graphic_device_uuid = info->GetDeviceInfo().uuid;
|
|
else
|
|
config.graphic_device_uuid = {};
|
|
}
|
|
else
|
|
config.graphic_device_uuid = {};
|
|
|
|
|
|
config.vsync = m_vsync->GetSelection();
|
|
config.gx2drawdone_sync = m_gx2drawdone_sync->IsChecked();
|
|
config.async_compile = m_async_compile->IsChecked();
|
|
|
|
config.upscale_filter = m_upscale_filter->GetSelection();
|
|
config.downscale_filter = m_downscale_filter->GetSelection();
|
|
config.fullscreen_scaling = m_fullscreen_scaling->GetSelection();
|
|
|
|
config.overlay.position = (ScreenPosition)m_overlay_position->GetSelection(); wxASSERT((int)config.overlay.position <= (int)ScreenPosition::kBottomRight);
|
|
config.overlay.text_color = m_overlay_font_color->GetColour().GetRGBA();
|
|
config.overlay.text_scale = m_overlay_scale->GetSelection() * 25 + 50;
|
|
|
|
config.overlay.fps = m_overlay_fps->GetValue();
|
|
config.overlay.drawcalls = m_overlay_drawcalls->GetValue();
|
|
config.overlay.cpu_usage = m_overlay_cpu->GetValue();
|
|
config.overlay.cpu_per_core_usage = m_overlay_cpu_per_core->GetValue();
|
|
config.overlay.ram_usage = m_overlay_ram->GetValue();
|
|
config.overlay.vram_usage = m_overlay_vram->GetValue();
|
|
config.overlay.debug = m_overlay_debug->GetValue();
|
|
|
|
config.notification.position = (ScreenPosition)m_notification_position->GetSelection(); wxASSERT((int)config.notification.position <= (int)ScreenPosition::kBottomRight);
|
|
config.notification.text_color = m_notification_font_color->GetColour().GetRGBA();
|
|
config.notification.text_scale = m_notification_scale->GetSelection() * 25 + 50;
|
|
config.notification.controller_profiles = m_controller_profile_name->GetValue();
|
|
config.notification.controller_battery = m_controller_low_battery->GetValue();
|
|
config.notification.shader_compiling = m_shader_compiling->GetValue();
|
|
config.notification.friends = m_friends_data->GetValue();
|
|
|
|
// account
|
|
config.account.m_persistent_id = GetSelectedAccountPersistentId();
|
|
|
|
// debug
|
|
config.crash_dump = (CrashDump)m_crash_dump->GetSelection();
|
|
config.gdb_port = m_gdb_port->GetValue();
|
|
|
|
g_config.Save();
|
|
}
|
|
|
|
GeneralSettings2::~GeneralSettings2()
|
|
{
|
|
Unbind(wxEVT_CLOSE_WINDOW, &GeneralSettings2::OnClose, this);
|
|
}
|
|
|
|
void GeneralSettings2::OnClose(wxCloseEvent& event)
|
|
{
|
|
StoreConfig();
|
|
if (m_has_account_change)
|
|
{
|
|
wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH);
|
|
GetParent()->ProcessWindowEvent(refresh_event);
|
|
}
|
|
event.Skip();
|
|
}
|
|
|
|
void GeneralSettings2::ValidateConfig()
|
|
{
|
|
g_config.Load();
|
|
|
|
auto& data = g_config.data();
|
|
// todo
|
|
//data.fullscreen_scaling = min(max(data.fullscreen_scaling,))
|
|
}
|
|
|
|
void GeneralSettings2::DisableSettings(bool game_launched)
|
|
{
|
|
|
|
}
|
|
|
|
void GeneralSettings2::OnAudioLatencyChanged(wxCommandEvent& event)
|
|
{
|
|
IAudioAPI::s_audioDelay = event.GetInt();
|
|
event.Skip();
|
|
}
|
|
|
|
void GeneralSettings2::OnVolumeChanged(wxCommandEvent& event)
|
|
{
|
|
|
|
if(event.GetEventObject() == m_input_volume)
|
|
{
|
|
std::shared_lock lock(g_audioInputMutex);
|
|
if (g_inputAudio)
|
|
g_inputAudio->SetVolume(event.GetInt());
|
|
}
|
|
else
|
|
{
|
|
std::shared_lock lock(g_audioMutex);
|
|
if(event.GetEventObject() == m_pad_volume)
|
|
{
|
|
if (g_padAudio)
|
|
{
|
|
g_padAudio->SetVolume(event.GetInt());
|
|
g_padVolume = event.GetInt();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (g_tvAudio)
|
|
g_tvAudio->SetVolume(event.GetInt());
|
|
}
|
|
}
|
|
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void GeneralSettings2::OnInputVolumeChanged(wxCommandEvent& event)
|
|
{
|
|
std::shared_lock lock(g_audioMutex);
|
|
if (g_padAudio)
|
|
{
|
|
g_padAudio->SetInputVolume(event.GetInt());
|
|
g_padVolume = event.GetInt();
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void GeneralSettings2::OnSliderChangedPercent(wxCommandEvent& event)
|
|
{
|
|
const auto slider = dynamic_cast<wxSlider*>(event.GetEventObject());
|
|
wxASSERT(slider);
|
|
|
|
const auto control = dynamic_cast<wxControlObject*>(event.GetEventUserData());
|
|
wxASSERT(control);
|
|
|
|
auto slider_text = control->GetControl<wxStaticText>();
|
|
wxASSERT(slider_text);
|
|
|
|
const auto value = event.GetInt();
|
|
slider->SetValue(value);
|
|
slider_text->SetLabel(wxString::Format("%d%%", value));
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void GeneralSettings2::OnLatencySliderChanged(wxCommandEvent& event)
|
|
{
|
|
const auto slider = dynamic_cast<wxSlider*>(event.GetEventObject());
|
|
wxASSERT(slider);
|
|
|
|
const auto control = dynamic_cast<wxControlObject*>(event.GetEventUserData());
|
|
wxASSERT(control);
|
|
|
|
auto slider_text = control->GetControl<wxStaticText>();
|
|
wxASSERT(slider_text);
|
|
|
|
const auto value = event.GetInt();
|
|
slider->SetValue(value);
|
|
slider_text->SetLabel(wxString::Format("%dms", value * 12));
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void GeneralSettings2::UpdateAudioDeviceList()
|
|
{
|
|
m_tv_device->Clear();
|
|
m_pad_device->Clear();
|
|
m_input_device->Clear();
|
|
|
|
m_tv_device->Append(_("Disabled"));
|
|
m_pad_device->Append(_("Disabled"));
|
|
m_input_device->Append(_("Disabled"));
|
|
|
|
const auto audio_api = (IAudioAPI::AudioAPI)GetConfig().audio_api;
|
|
const auto devices = IAudioAPI::GetDevices(audio_api);
|
|
for (auto& device : devices)
|
|
{
|
|
m_tv_device->Append(device->GetName(), new wxDeviceDescription(device));
|
|
m_pad_device->Append(device->GetName(), new wxDeviceDescription(device));
|
|
}
|
|
|
|
const auto input_audio_api = IAudioInputAPI::Cubeb; //(IAudioAPI::AudioAPI)GetConfig().input_audio_api;
|
|
const auto input_devices = IAudioInputAPI::GetDevices(input_audio_api);
|
|
|
|
for (auto& device : input_devices)
|
|
{
|
|
m_input_device->Append(device->GetName(), new wxInputDeviceDescription(device));
|
|
}
|
|
|
|
if(m_tv_device->GetCount() > 1)
|
|
m_tv_device->SetSelection(1);
|
|
else
|
|
m_tv_device->SetSelection(0);
|
|
|
|
m_pad_device->SetSelection(0);
|
|
|
|
m_input_device->SetSelection(0);
|
|
|
|
// todo reset global instance of audio device
|
|
}
|
|
|
|
void GeneralSettings2::ResetAccountInformation()
|
|
{
|
|
m_account_grid->SetSplitterPosition(100);
|
|
m_active_account->SetSelection(0);
|
|
|
|
for(auto it = m_account_grid->GetIterator(); !it.AtEnd(); ++it)
|
|
{
|
|
(*it)->SetValueToUnspecified();
|
|
}
|
|
|
|
// refresh pane size
|
|
m_account_information->InvalidateBestSize();
|
|
#if BOOST_OS_WINDOWS
|
|
m_account_information->OnStateChange(GetBestSize());
|
|
#endif
|
|
}
|
|
|
|
void GeneralSettings2::OnAccountCreate(wxCommandEvent& event)
|
|
{
|
|
wxASSERT(Account::HasFreeAccountSlots());
|
|
|
|
wxCreateAccountDialog dialog(this);
|
|
if (dialog.ShowModal() == wxID_CANCEL)
|
|
return;
|
|
|
|
Account account(dialog.GetPersistentId(), dialog.GetMiiName().ToStdWstring());
|
|
account.Save();
|
|
Account::RefreshAccounts();
|
|
|
|
const int index = m_active_account->Append(account.ToString(), new wxAccountData(account));
|
|
|
|
// update ui
|
|
m_active_account->SetSelection(index);
|
|
UpdateAccountInformation();
|
|
|
|
m_create_account->Enable(m_active_account->GetCount() < 0xC);
|
|
m_delete_account->Enable(m_active_account->GetCount() > 1);
|
|
|
|
// send main window event
|
|
wxASSERT(GetParent());
|
|
wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH);
|
|
GetParent()->ProcessWindowEvent(refresh_event);
|
|
}
|
|
|
|
void GeneralSettings2::OnAccountDelete(wxCommandEvent& event)
|
|
{
|
|
if(m_active_account->GetCount() == 1)
|
|
{
|
|
wxMessageBox(_("Can't delete the only account!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
|
return;
|
|
}
|
|
|
|
const auto selection = m_active_account->GetSelection();
|
|
wxASSERT(selection != wxNOT_FOUND);
|
|
auto* obj = dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(selection));
|
|
wxASSERT(obj);
|
|
auto& account = obj->GetAccount();
|
|
|
|
const std::wstring format_str = _("Are you sure you want to delete the account {} with id {:x}?").ToStdWstring();
|
|
const std::wstring msg = fmt::format(fmt::runtime(format_str),
|
|
std::wstring{ account.GetMiiName() }, account.GetPersistentId());
|
|
|
|
const int answer = wxMessageBox(msg, _("Confirmation"), wxYES_NO | wxCENTRE | wxICON_QUESTION, this);
|
|
if (answer == wxNO)
|
|
return;
|
|
|
|
// todo: ask if saves should be deleted too?
|
|
|
|
const fs::path path = account.GetFileName();
|
|
try
|
|
{
|
|
fs::remove_all(path.parent_path());
|
|
m_active_account->Delete(selection);
|
|
m_active_account->SetSelection(0);
|
|
Account::RefreshAccounts();
|
|
UpdateAccountInformation();
|
|
|
|
m_create_account->Enable(m_active_account->GetCount() < 0xC);
|
|
m_delete_account->Enable(m_active_account->GetCount() > 1);
|
|
}
|
|
catch(const std::exception& ex)
|
|
{
|
|
SystemException sys(ex);
|
|
cemuLog_log(LogType::Force, sys.what());
|
|
}
|
|
|
|
}
|
|
|
|
void GeneralSettings2::OnAccountSettingsChanged(wxPropertyGridEvent& event)
|
|
{
|
|
wxPGProperty* property = event.GetProperty();
|
|
if (!property)
|
|
return;
|
|
|
|
const wxAny value = property->GetValue();
|
|
if (value.IsNull())
|
|
return;
|
|
|
|
const auto selection = m_active_account->GetSelection();
|
|
wxASSERT(selection != wxNOT_FOUND);
|
|
auto* obj = dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(selection));
|
|
wxASSERT(obj);
|
|
auto& account = obj->GetAccount();
|
|
|
|
// TODO make id changeable to free ids + current it?
|
|
bool refresh_accounts = false;
|
|
if (property->GetName() == kPropertyMiiName)
|
|
{
|
|
std::wstring new_name = value.As<wxString>().ToStdWstring();
|
|
if (new_name.empty())
|
|
new_name = L"default";
|
|
|
|
account.SetMiiName(new_name);
|
|
refresh_accounts = true;
|
|
}
|
|
else if (property->GetName() == kPropertyBirthday)
|
|
{
|
|
const std::string birthday = value.As<wxString>().ToStdString();
|
|
const boost::char_separator<char> sep{ "-" };
|
|
|
|
std::vector<std::string> tokens;
|
|
for (const auto& token : boost::tokenizer(birthday, sep))
|
|
{
|
|
tokens.emplace_back(token);
|
|
}
|
|
|
|
if (tokens.size() == 3)
|
|
{
|
|
account.SetBirthYear(ConvertString<uint16>(tokens[0]));
|
|
account.SetBirthMonth(ConvertString<uint8>(tokens[1]));
|
|
account.SetBirthDay(ConvertString<uint8>(tokens[2]));
|
|
}
|
|
}
|
|
else if (property->GetName() == kPropertyGender)
|
|
{
|
|
account.SetGender(value.As<int>());
|
|
}
|
|
else if (property->GetName() == kPropertyEmail)
|
|
{
|
|
account.SetEmail(value.As<wxString>().ToStdString());
|
|
|
|
}
|
|
else if (property->GetName() == kPropertyCountry)
|
|
{
|
|
account.SetCountry(value.As<int>());
|
|
}
|
|
else
|
|
cemu_assert_debug(false);
|
|
|
|
account.Save();
|
|
Account::RefreshAccounts(); // refresh internal account list
|
|
UpdateAccountInformation(); // refresh on invalid values
|
|
|
|
if(refresh_accounts)
|
|
{
|
|
wxCommandEvent refresh_event(wxEVT_ACCOUNTLIST_REFRESH);
|
|
GetParent()->ProcessWindowEvent(refresh_event);
|
|
}
|
|
}
|
|
|
|
void GeneralSettings2::UpdateAccountInformation()
|
|
{
|
|
m_account_grid->SetSplitterPosition(100);
|
|
|
|
const auto selection = m_active_account->GetSelection();
|
|
if(selection == wxNOT_FOUND)
|
|
{
|
|
m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16));
|
|
m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE);
|
|
ResetAccountInformation();
|
|
m_online_status->SetLabel(_("No account selected"));
|
|
return;
|
|
}
|
|
|
|
const auto* obj = dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(selection));
|
|
wxASSERT(obj);
|
|
const auto& account = obj->GetAccount();
|
|
|
|
m_active_account->SetString(selection, account.ToString());
|
|
|
|
m_account_grid->GetProperty(kPropertyPersistentId)->SetValueFromString(fmt::format("{:x}", account.GetPersistentId()));
|
|
m_account_grid->GetProperty(kPropertyMiiName)->SetValueFromString(std::wstring{ account.GetMiiName() });
|
|
m_account_grid->GetProperty(kPropertyBirthday)->SetValueFromString(fmt::format("{:04d}-{:02d}-{:02d}", account.GetBirthYear(), account.GetBirthMonth(), account.GetBirthDay()));
|
|
|
|
const auto gender_property = m_account_grid->GetProperty(kPropertyGender); // gender 2 can be also female?
|
|
gender_property->SetChoiceSelection(std::min(gender_property->GetChoices().GetCount() - 1, (uint32)account.GetGender()));
|
|
|
|
m_account_grid->GetProperty(kPropertyEmail)->SetValueFromString(std::string{ account.GetEmail() });
|
|
|
|
auto* country_property = dynamic_cast<wxEnumProperty*>(m_account_grid->GetProperty(kPropertyCountry));
|
|
wxASSERT(country_property);
|
|
int index = (country_property)->GetIndexForValue(account.GetCountry());
|
|
if (index == wxNOT_FOUND)
|
|
index = 0;
|
|
country_property->SetChoiceSelection(index);
|
|
|
|
const bool online_fully_valid = account.IsValidOnlineAccount() && ActiveSettings::HasRequiredOnlineFiles();
|
|
if (ActiveSettings::HasRequiredOnlineFiles())
|
|
{
|
|
if(account.IsValidOnlineAccount())
|
|
m_online_status->SetLabel(_("Selected account is a valid online account"));
|
|
else
|
|
m_online_status->SetLabel(_("Selected account is not linked to a NNID or PNID"));
|
|
}
|
|
else
|
|
{
|
|
if(NCrypto::OTP_IsPresent() != NCrypto::SEEPROM_IsPresent())
|
|
m_online_status->SetLabel(_("OTP.bin or SEEPROM.bin is missing"));
|
|
else if(NCrypto::OTP_IsPresent() && NCrypto::SEEPROM_IsPresent())
|
|
m_online_status->SetLabel(_("OTP and SEEPROM present but no certificate files were found"));
|
|
else
|
|
m_online_status->SetLabel(_("Online play is not set up. Follow the guide below to get started"));
|
|
}
|
|
|
|
if(online_fully_valid)
|
|
{
|
|
m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_CHECK_YES).ConvertToImage().Scale(16, 16));
|
|
m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() | wxBORDER_NONE);
|
|
}
|
|
else
|
|
{
|
|
m_validate_online->SetBitmap(wxBITMAP_PNG_FROM_DATA(PNG_ERROR).ConvertToImage().Scale(16, 16));
|
|
m_validate_online->SetWindowStyleFlag(m_validate_online->GetWindowStyleFlag() & ~wxBORDER_NONE);
|
|
}
|
|
|
|
// enable/disable network service field depending on online requirements
|
|
m_active_service->Enable(online_fully_valid && !CafeSystem::IsTitleRunning());
|
|
if(online_fully_valid)
|
|
{
|
|
NetworkService service = GetConfig().GetAccountNetworkService(account.GetPersistentId());
|
|
m_active_service->SetSelection(static_cast<int>(service));
|
|
// set the config option here for the selected service
|
|
// this will guarantee that it's actually written to settings.xml
|
|
// allowing us to eventually get rid of the legacy option in the (far) future
|
|
GetConfig().SetAccountSelectedService(account.GetPersistentId(), service);
|
|
}
|
|
else
|
|
{
|
|
m_active_service->SetSelection(0); // force offline
|
|
}
|
|
wxString tmp = _("Network service");
|
|
tmp.append(" (");
|
|
tmp.append(wxString::FromUTF8(boost::nowide::narrow(account.GetMiiName())));
|
|
tmp.append(")");
|
|
m_active_service->SetLabel(tmp);
|
|
|
|
// refresh pane size
|
|
m_account_grid->InvalidateBestSize();
|
|
//m_account_grid->GetParent()->FitInside();
|
|
//m_account_information->OnStateChange(GetBestSize()); idk..
|
|
}
|
|
|
|
void GeneralSettings2::UpdateOnlineAccounts()
|
|
{
|
|
m_active_account->Clear();
|
|
for(const auto& account : Account::GetAccounts())
|
|
{
|
|
m_active_account->Append(fmt::format(L"{} ({:x})", std::wstring{ account.GetMiiName() }, account.GetPersistentId()),
|
|
new wxAccountData(account));
|
|
}
|
|
|
|
m_active_account->SetSelection(0);
|
|
m_create_account->Enable(m_active_account->GetCount() < 0xC);
|
|
m_delete_account->Enable(m_active_account->GetCount() > 1);
|
|
UpdateAccountInformation();
|
|
}
|
|
|
|
void GeneralSettings2::HandleGraphicsApiSelection()
|
|
{
|
|
int selection = m_vsync->GetSelection();
|
|
if(selection == wxNOT_FOUND)
|
|
selection = GetConfig().vsync;
|
|
|
|
m_vsync->Clear();
|
|
if(m_graphic_api->GetSelection() == 0)
|
|
{
|
|
// OpenGL
|
|
m_vsync->AppendString(_("Off"));
|
|
m_vsync->AppendString(_("On"));
|
|
if (selection == 0)
|
|
m_vsync->Select(0);
|
|
else
|
|
m_vsync->Select(1);
|
|
|
|
m_graphic_device->Clear();
|
|
m_graphic_device->Disable();
|
|
|
|
m_gx2drawdone_sync->Enable();
|
|
m_async_compile->Disable();
|
|
}
|
|
else
|
|
{
|
|
// Vulkan
|
|
m_gx2drawdone_sync->Disable();
|
|
m_async_compile->Enable();
|
|
|
|
m_vsync->AppendString(_("Off"));
|
|
m_vsync->AppendString(_("Double buffering"));
|
|
m_vsync->AppendString(_("Triple buffering"));
|
|
#if BOOST_OS_WINDOWS
|
|
m_vsync->AppendString(_("Match emulated display (Experimental)"));
|
|
#endif
|
|
|
|
m_vsync->Select(selection);
|
|
|
|
m_graphic_device->Enable();
|
|
auto devices = VulkanRenderer::GetDevices();
|
|
m_graphic_device->Clear();
|
|
if(!devices.empty())
|
|
{
|
|
for(const auto& device : devices)
|
|
{
|
|
m_graphic_device->Append(device.name, new wxVulkanUUID(device));
|
|
}
|
|
m_graphic_device->SetSelection(0);
|
|
|
|
const auto& config = GetConfig();
|
|
for(size_t i = 0; i < devices.size(); ++i)
|
|
{
|
|
if(config.graphic_device_uuid == devices[i].uuid)
|
|
{
|
|
m_graphic_device->SetSelection(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GeneralSettings2::ApplyConfig()
|
|
{
|
|
ValidateConfig();
|
|
auto& config = GetConfig();
|
|
|
|
if (LaunchSettings::GetMLCPath().has_value())
|
|
m_mlc_path->SetValue(wxHelper::FromPath(LaunchSettings::GetMLCPath().value()));
|
|
else
|
|
m_mlc_path->SetValue(wxHelper::FromUtf8(config.mlc_path.GetValue()));
|
|
|
|
m_save_window_position_size->SetValue(config.window_position != Vector2i{-1,-1});
|
|
m_save_padwindow_position_size->SetValue(config.pad_position != Vector2i{-1,-1});
|
|
|
|
m_discord_presence->SetValue(config.use_discord_presence);
|
|
m_fullscreen_menubar->SetValue(config.fullscreen_menubar);
|
|
|
|
m_auto_update->SetValue(config.check_update);
|
|
m_receive_untested_releases->SetValue(config.receive_untested_updates);
|
|
m_save_screenshot->SetValue(config.save_screenshot);
|
|
|
|
m_disable_screensaver->SetValue(config.disable_screensaver);
|
|
m_play_boot_sound->SetValue(config.play_boot_sound);
|
|
#if BOOST_OS_LINUX && defined(ENABLE_FERAL_GAMEMODE)
|
|
m_feral_gamemode->SetValue(config.feral_gamemode);
|
|
#endif
|
|
// temporary workaround because feature crashes on macOS
|
|
#if BOOST_OS_MACOS
|
|
m_disable_screensaver->SetValue(false);
|
|
#endif
|
|
|
|
m_game_paths->Clear();
|
|
for (auto& path : config.game_paths)
|
|
{
|
|
m_game_paths->Append(to_wxString(path));
|
|
}
|
|
|
|
const auto app = (CemuApp*)wxTheApp;
|
|
for (const auto& language : app->GetLanguages())
|
|
{
|
|
if (config.language == language->Language)
|
|
{
|
|
m_language->SetStringSelection(language->DescriptionNative);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// graphics
|
|
m_graphic_api->SetSelection(config.graphic_api);
|
|
m_vsync->SetSelection(config.vsync);
|
|
m_async_compile->SetValue(config.async_compile);
|
|
m_gx2drawdone_sync->SetValue(config.gx2drawdone_sync);
|
|
m_upscale_filter->SetSelection(config.upscale_filter);
|
|
m_downscale_filter->SetSelection(config.downscale_filter);
|
|
m_fullscreen_scaling->SetSelection(config.fullscreen_scaling);
|
|
|
|
wxASSERT((uint32)config.overlay.position < m_overlay_position->GetCount());
|
|
m_overlay_position->SetSelection((int)config.overlay.position);
|
|
m_overlay_font_color->SetColour(wxColour((unsigned long)config.overlay.text_color));
|
|
|
|
uint32 selection = (config.overlay.text_scale - 50) / 25;
|
|
wxASSERT(selection < m_overlay_scale->GetCount());
|
|
m_overlay_scale->SetSelection(selection);
|
|
|
|
m_overlay_fps->SetValue(config.overlay.fps);
|
|
m_overlay_drawcalls->SetValue(config.overlay.drawcalls);
|
|
m_overlay_cpu->SetValue(config.overlay.cpu_usage);
|
|
m_overlay_cpu_per_core->SetValue(config.overlay.cpu_per_core_usage);
|
|
m_overlay_ram->SetValue(config.overlay.ram_usage);
|
|
m_overlay_vram->SetValue(config.overlay.vram_usage);
|
|
m_overlay_debug->SetValue(config.overlay.debug);
|
|
|
|
wxASSERT((uint32)config.notification.position < m_notification_position->GetCount());
|
|
m_notification_position->SetSelection((int)config.notification.position);
|
|
m_notification_font_color->SetColour(wxColour((unsigned long)config.notification.text_color));
|
|
|
|
selection = (config.notification.text_scale - 50) / 25;
|
|
wxASSERT(selection < m_notification_scale->GetCount());
|
|
m_notification_scale->SetSelection(selection);
|
|
|
|
m_controller_profile_name->SetValue(config.notification.controller_profiles);
|
|
m_controller_low_battery->SetValue(config.notification.controller_battery);
|
|
m_shader_compiling->SetValue(config.notification.shader_compiling);
|
|
m_friends_data->SetValue(config.notification.friends);
|
|
|
|
// audio
|
|
if(config.audio_api == IAudioAPI::DirectSound)
|
|
m_audio_api->SetStringSelection(kDirectSound);
|
|
else if(config.audio_api == IAudioAPI::XAudio27)
|
|
m_audio_api->SetStringSelection(kXAudio27);
|
|
else if(config.audio_api == IAudioAPI::XAudio2)
|
|
m_audio_api->SetStringSelection(kXAudio2);
|
|
else if(config.audio_api == IAudioAPI::Cubeb)
|
|
m_audio_api->SetStringSelection(kCubeb);
|
|
|
|
SendSliderEvent(m_audio_latency, config.audio_delay);
|
|
|
|
m_tv_channels->SetSelection(config.tv_channels);
|
|
//m_pad_channels->SetSelection(config.pad_channels);
|
|
m_pad_channels->SetSelection(0);
|
|
//m_input_channels->SetSelection(config.pad_channels);
|
|
m_input_channels->SetSelection(0);
|
|
|
|
SendSliderEvent(m_tv_volume, config.tv_volume);
|
|
|
|
if (!config.tv_device.empty() && m_tv_device->HasClientObjectData())
|
|
{
|
|
for(uint32 i = 0; i < m_tv_device->GetCount(); ++i)
|
|
{
|
|
const auto device_description = (wxDeviceDescription*)m_tv_device->GetClientObject(i);
|
|
if (device_description && config.tv_device == device_description->GetDescription()->GetIdentifier())
|
|
{
|
|
m_tv_device->SetSelection(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
m_tv_device->SetSelection(0);
|
|
|
|
SendSliderEvent(m_pad_volume, config.pad_volume);
|
|
if (!config.pad_device.empty() && m_pad_device->HasClientObjectData())
|
|
{
|
|
for (uint32 i = 0; i < m_pad_device->GetCount(); ++i)
|
|
{
|
|
const auto device_description = (wxDeviceDescription*)m_pad_device->GetClientObject(i);
|
|
if (device_description && config.pad_device == device_description->GetDescription()->GetIdentifier())
|
|
{
|
|
m_pad_device->SetSelection(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
m_pad_device->SetSelection(0);
|
|
|
|
SendSliderEvent(m_input_volume, config.input_volume);
|
|
if (!config.input_device.empty() && m_input_device->HasClientObjectData())
|
|
{
|
|
for (uint32 i = 0; i < m_input_device->GetCount(); ++i)
|
|
{
|
|
const auto device_description = (wxInputDeviceDescription*)m_input_device->GetClientObject(i);
|
|
if (device_description && config.input_device == device_description->GetDescription()->GetIdentifier())
|
|
{
|
|
m_input_device->SetSelection(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
m_input_device->SetSelection(0);
|
|
|
|
// account
|
|
UpdateOnlineAccounts();
|
|
m_active_account->SetSelection(0);
|
|
for(uint32 i = 0; i < m_active_account->GetCount(); ++i)
|
|
{
|
|
const auto* obj = dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(i));
|
|
wxASSERT(obj);
|
|
if(obj->GetAccount().GetPersistentId() == ActiveSettings::GetPersistentId())
|
|
{
|
|
m_active_account->SetSelection(i);
|
|
break;
|
|
}
|
|
}
|
|
m_active_service->SetSelection((int)config.GetAccountNetworkService(ActiveSettings::GetPersistentId()));
|
|
|
|
UpdateAccountInformation();
|
|
|
|
// debug
|
|
m_crash_dump->SetSelection((int)config.crash_dump.GetValue());
|
|
m_gdb_port->SetValue(config.gdb_port.GetValue());
|
|
}
|
|
|
|
void GeneralSettings2::OnAudioAPISelected(wxCommandEvent& event)
|
|
{
|
|
IAudioAPI::AudioAPI api;
|
|
if (m_audio_api->GetStringSelection() == kDirectSound)
|
|
api = IAudioAPI::DirectSound;
|
|
else if (m_audio_api->GetStringSelection() == kXAudio27)
|
|
api = IAudioAPI::XAudio27;
|
|
else if (m_audio_api->GetStringSelection() == kXAudio2)
|
|
api = IAudioAPI::XAudio2;
|
|
else if (m_audio_api->GetStringSelection() == kCubeb)
|
|
api = IAudioAPI::Cubeb;
|
|
else
|
|
{
|
|
wxFAIL_MSG("invalid audio api selected!");
|
|
return;
|
|
}
|
|
|
|
GetConfig().audio_api = api;
|
|
UpdateAudioDeviceList();
|
|
OnAudioDeviceSelected(event);
|
|
}
|
|
|
|
#define AX_FRAMES_PER_GROUP 4
|
|
|
|
void GeneralSettings2::UpdateAudioDevice()
|
|
{
|
|
auto& config = GetConfig();
|
|
|
|
std::unique_lock lock(g_audioMutex);
|
|
std::unique_lock inputLock(g_audioInputMutex);
|
|
|
|
// tv audio device
|
|
{
|
|
const auto selection = m_tv_device->GetSelection();
|
|
if (selection == wxNOT_FOUND)
|
|
{
|
|
cemu_assert_debug(false);
|
|
return;
|
|
}
|
|
|
|
g_tvAudio.reset();
|
|
|
|
if (m_tv_device->HasClientObjectData())
|
|
{
|
|
const auto description = (wxDeviceDescription*)m_tv_device->GetClientObject(selection);
|
|
if (description)
|
|
{
|
|
sint32 channels;
|
|
if (m_game_launched && g_tvAudio)
|
|
channels = g_tvAudio->GetChannels();
|
|
else
|
|
channels = CemuConfig::AudioChannelsToNChannels(config.tv_channels);
|
|
|
|
try
|
|
{
|
|
g_tvAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
|
g_tvAudio->SetVolume(m_tv_volume->GetValue());
|
|
}
|
|
catch (std::runtime_error& ex)
|
|
{
|
|
cemuLog_log(LogType::Force, "can't initialize tv audio: {}", ex.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// pad audio device
|
|
{
|
|
const auto selection = m_pad_device->GetSelection();
|
|
if (selection == wxNOT_FOUND)
|
|
{
|
|
cemu_assert_debug(false);
|
|
return;
|
|
}
|
|
|
|
g_padAudio.reset();
|
|
|
|
if (m_pad_device->HasClientObjectData())
|
|
{
|
|
const auto description = (wxDeviceDescription*)m_pad_device->GetClientObject(selection);
|
|
if (description)
|
|
{
|
|
sint32 channels;
|
|
if (m_game_launched && g_padAudio)
|
|
channels = g_padAudio->GetChannels();
|
|
else
|
|
channels = CemuConfig::AudioChannelsToNChannels(config.pad_channels);
|
|
|
|
try
|
|
{
|
|
g_padAudio = IAudioAPI::CreateDevice((IAudioAPI::AudioAPI)config.audio_api, description->GetDescription(), 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
|
|
g_padAudio->SetVolume(m_pad_volume->GetValue());
|
|
g_padVolume = m_pad_volume->GetValue();
|
|
}
|
|
catch (std::runtime_error& ex)
|
|
{
|
|
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// input audio device
|
|
{
|
|
const auto selection = m_input_device->GetSelection();
|
|
if (selection == wxNOT_FOUND)
|
|
{
|
|
cemu_assert_debug(false);
|
|
return;
|
|
}
|
|
|
|
g_inputAudio.reset();
|
|
|
|
if (m_input_device->HasClientObjectData())
|
|
{
|
|
const auto description = (wxInputDeviceDescription*)m_input_device->GetClientObject(selection);
|
|
if (description)
|
|
{
|
|
sint32 channels;
|
|
if (m_game_launched && g_inputAudio)
|
|
channels = g_inputAudio->GetChannels();
|
|
else
|
|
channels = CemuConfig::AudioChannelsToNChannels(config.input_channels);
|
|
|
|
try
|
|
{
|
|
g_inputAudio = IAudioInputAPI::CreateDevice(IAudioInputAPI::AudioInputAPI::Cubeb, description->GetDescription(), 32000, channels, snd_core::AX_SAMPLES_PER_3MS_32KHZ, 16);
|
|
g_inputAudio->SetVolume(m_input_volume->GetValue());
|
|
}
|
|
catch (std::runtime_error& ex)
|
|
{
|
|
cemuLog_log(LogType::Force, "can't initialize pad audio: {}", ex.what());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GeneralSettings2::OnAudioDeviceSelected(wxCommandEvent& event)
|
|
{
|
|
UpdateAudioDevice();
|
|
}
|
|
|
|
void GeneralSettings2::OnAudioChannelsSelected(wxCommandEvent& event)
|
|
{
|
|
const auto obj = wxDynamicCast(event.GetEventObject(), wxChoice);
|
|
wxASSERT(obj);
|
|
if (obj->GetSelection() == wxNOT_FOUND)
|
|
return;
|
|
|
|
auto& config = GetConfig();
|
|
if (obj == m_tv_channels)
|
|
{
|
|
if (config.tv_channels == (AudioChannels)obj->GetSelection())
|
|
return;
|
|
|
|
config.tv_channels = (AudioChannels)obj->GetSelection();
|
|
}
|
|
else if (obj == m_pad_channels)
|
|
{
|
|
if (config.pad_channels == (AudioChannels)obj->GetSelection())
|
|
return;
|
|
|
|
config.pad_channels = (AudioChannels)obj->GetSelection();
|
|
}
|
|
else
|
|
cemu_assert_debug(false);
|
|
|
|
if(m_game_launched)
|
|
wxMessageBox(_("You have to restart the game in order to apply the new settings."), _("Information"), wxOK | wxCENTRE, this);
|
|
else
|
|
UpdateAudioDevice();
|
|
}
|
|
|
|
void GeneralSettings2::OnGraphicAPISelected(wxCommandEvent& event)
|
|
{
|
|
HandleGraphicsApiSelection();
|
|
}
|
|
|
|
void GeneralSettings2::OnAddPathClicked(wxCommandEvent& event)
|
|
{
|
|
wxDirDialog path_dialog(this, _("Select a directory containing games."), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
|
|
if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty())
|
|
return;
|
|
|
|
const auto path = path_dialog.GetPath();
|
|
// test if already included
|
|
for (auto& s : m_game_paths->GetStrings())
|
|
{
|
|
if (s == path)
|
|
return;
|
|
}
|
|
|
|
m_game_paths->Append(path);
|
|
m_reload_gamelist = true;
|
|
|
|
// trigger title list rescan with new path configuration
|
|
CafeTitleList::ClearScanPaths();
|
|
for (auto& it : m_game_paths->GetStrings())
|
|
CafeTitleList::AddScanPath(wxHelper::MakeFSPath(it));
|
|
CafeTitleList::Refresh();
|
|
}
|
|
|
|
void GeneralSettings2::OnRemovePathClicked(wxCommandEvent& event)
|
|
{
|
|
const auto selection = m_game_paths->GetSelection();
|
|
if (selection == wxNOT_FOUND)
|
|
return;
|
|
|
|
m_game_paths->Delete(selection);
|
|
m_reload_gamelist = true;
|
|
// trigger title list rescan with new path configuration
|
|
CafeTitleList::ClearScanPaths();
|
|
for (auto& it : m_game_paths->GetStrings())
|
|
CafeTitleList::AddScanPath(wxHelper::MakeFSPath(it));
|
|
CafeTitleList::Refresh();
|
|
}
|
|
|
|
void GeneralSettings2::OnActiveAccountChanged(wxCommandEvent& event)
|
|
{
|
|
UpdateAccountInformation();
|
|
m_has_account_change = true;
|
|
}
|
|
|
|
void GeneralSettings2::OnAccountServiceChanged(wxCommandEvent& event)
|
|
{
|
|
auto& config = GetConfig();
|
|
uint32 peristentId = GetSelectedAccountPersistentId();
|
|
config.SetAccountSelectedService(peristentId, static_cast<NetworkService>(m_active_service->GetSelection()));
|
|
UpdateAccountInformation();
|
|
}
|
|
|
|
void GeneralSettings2::OnMLCPathSelect(wxCommandEvent& event)
|
|
{
|
|
if(CafeSystem::IsTitleRunning())
|
|
{
|
|
wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
|
return;
|
|
}
|
|
// show directory dialog
|
|
wxDirDialog path_dialog(this, _("Select MLC directory"), wxEmptyString, wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
|
|
if (path_dialog.ShowModal() != wxID_OK || path_dialog.GetPath().empty())
|
|
return;
|
|
// check if the choosen MLC path is an already initialized MLC location
|
|
fs::path newMlc = wxHelper::MakeFSPath(path_dialog.GetPath());
|
|
if(CemuApp::CheckMLCPath(newMlc))
|
|
{
|
|
// ask user if they are sure they want to use this folder and let them know that accounts and saves wont transfer
|
|
wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?");
|
|
wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING);
|
|
if(dialog.ShowModal() == wxID_NO)
|
|
return;
|
|
if( !CemuApp::CreateDefaultMLCFiles(newMlc) ) // creating also acts as a check for read+write access
|
|
{
|
|
wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// ask user if they want to create a new mlc structure at the choosen location
|
|
wxString message = _("The selected directory does not contain the expected MLC structure. Do you want to create a new MLC structure in this directory?\nNote that changing the MLC location will not transfer any accounts or save files.");
|
|
wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING);
|
|
if( !CemuApp::CreateDefaultMLCFiles(newMlc) )
|
|
{
|
|
wxMessageBox(_("Failed to create default MLC files in the selected directory. The MLC path has not been changed"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
|
return;
|
|
}
|
|
}
|
|
// update MLC path and store any other modified settings
|
|
GetConfig().SetMLCPath(newMlc);
|
|
StoreConfig();
|
|
wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this);
|
|
// close settings and then cemu
|
|
wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW);
|
|
wxPostEvent(this, closeEvent);
|
|
wxPostEvent(GetParent(), closeEvent);
|
|
}
|
|
|
|
void GeneralSettings2::OnMLCPathClear(wxCommandEvent& event)
|
|
{
|
|
if(CafeSystem::IsTitleRunning())
|
|
{
|
|
wxMessageBox(_("Can't change MLC path while a game is running!"), _("Error"), wxOK | wxCENTRE | wxICON_ERROR, this);
|
|
return;
|
|
}
|
|
wxString message = _("Note that changing the MLC location will not transfer any accounts or save files. Are you sure you want to change the path?");
|
|
wxMessageDialog dialog(this, message, _("Warning"), wxYES_NO | wxCENTRE | wxICON_WARNING);
|
|
if(dialog.ShowModal() == wxID_NO)
|
|
return;
|
|
GetConfig().SetMLCPath("");
|
|
StoreConfig();
|
|
g_config.Save();
|
|
wxMessageBox(_("Cemu needs to be restarted for the changes to take effect."), _("Information"), wxOK | wxCENTRE | wxICON_INFORMATION, this);
|
|
// close settings and then cemu
|
|
wxCloseEvent closeEvent(wxEVT_CLOSE_WINDOW);
|
|
wxPostEvent(this, closeEvent);
|
|
wxPostEvent(GetParent(), closeEvent);
|
|
}
|
|
|
|
void GeneralSettings2::OnShowOnlineValidator(wxCommandEvent& event)
|
|
{
|
|
const auto selection = m_active_account->GetSelection();
|
|
if (selection == wxNOT_FOUND)
|
|
return;
|
|
|
|
const auto* obj = dynamic_cast<wxAccountData*>(m_active_account->GetClientObject(selection));
|
|
wxASSERT(obj);
|
|
const auto& account = obj->GetAccount();
|
|
|
|
const auto validator = account.ValidateOnlineFiles();
|
|
if (validator) // everything valid? shouldn't happen
|
|
return;
|
|
|
|
wxString err;
|
|
err << _("The following error(s) have been found:") << '\n';
|
|
|
|
if (validator.otp == OnlineValidator::FileState::Missing)
|
|
err << _("otp.bin missing in Cemu directory") << '\n';
|
|
else if(validator.otp == OnlineValidator::FileState::Corrupted)
|
|
err << _("otp.bin is invalid") << '\n';
|
|
|
|
if (validator.seeprom == OnlineValidator::FileState::Missing)
|
|
err << _("seeprom.bin missing in Cemu directory") << '\n';
|
|
else if(validator.seeprom == OnlineValidator::FileState::Corrupted)
|
|
err << _("seeprom.bin is invalid") << '\n';
|
|
|
|
if(!validator.missing_files.empty())
|
|
{
|
|
err << _("Missing certificate and key files:") << '\n';
|
|
|
|
int counter = 0;
|
|
for (const auto& f : validator.missing_files)
|
|
{
|
|
err << f << '\n';
|
|
|
|
++counter;
|
|
if(counter > 10)
|
|
{
|
|
err << "..." << '\n';
|
|
break;
|
|
}
|
|
}
|
|
|
|
err << '\n';
|
|
}
|
|
|
|
if (!validator.valid_account)
|
|
{
|
|
err << _("The currently selected account is not a valid or dumped online account:") << '\n';
|
|
err << GetOnlineAccountErrorMessage(validator.account_error);
|
|
}
|
|
|
|
wxMessageBox(err, _("Online Status"), wxOK | wxCENTRE | wxICON_INFORMATION);
|
|
}
|
|
|
|
wxString GeneralSettings2::GetOnlineAccountErrorMessage(OnlineAccountError error)
|
|
{
|
|
switch (error)
|
|
{
|
|
case OnlineAccountError::kNoAccountId:
|
|
return _("AccountId missing (The account is not connected to a NNID/PNID)");
|
|
case OnlineAccountError::kNoPasswordCached:
|
|
return _("IsPasswordCacheEnabled is set to false (The remember password option on your Wii U must be enabled for this account before dumping it)");
|
|
case OnlineAccountError::kPasswordCacheEmpty:
|
|
return _("AccountPasswordCache is empty (The remember password option on your Wii U must be enabled for this account before dumping it)");
|
|
case OnlineAccountError::kNoPrincipalId:
|
|
return _("PrincipalId missing");
|
|
default:
|
|
return "no error";
|
|
}
|
|
}
|