diff --git a/CMakeLists.txt b/CMakeLists.txt index ea2bf5d66..59c550b4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,7 @@ else() endif() option(ENABLE_SDL2 "Enable using SDL2" ON) +CMAKE_DEPENDENT_OPTION(ENABLE_SDL2_FRONTEND "Enable the SDL2 frontend" ON "ENABLE_SDL2;NOT ANDROID" OFF) option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OFF) # Set bundled qt as dependent options. @@ -87,6 +88,17 @@ option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO}) option(LIME3DS_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON) option(LIME3DS_WARNINGS_AS_ERRORS "Enable warnings as errors" ON) +# Pass the following values to C++ land +if (ENABLE_QT) + add_definitions(-DENABLE_QT) +endif() +if (ENABLE_QT_TRANSLATION) + add_definitions(-DENABLE_QT_TRANSLATION) +endif() +if (ENABLE_SDL2_FRONTEND) + add_definitions(-DENABLE_SDL2_FRONTEND) +endif() + include(Lime3DSHandleSystemLibs) if (LIME3DS_USE_PRECOMPILED_HEADERS) @@ -420,19 +432,18 @@ endif() add_subdirectory(src) - -# Set lime-qt project or lime project as default StartUp Project in Visual Studio depending on whether QT is enabled or not -if(ENABLE_QT) - set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT lime-qt) -endif() +set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT lime) # Create target for outputting distributable bundles. # Not supported for mobile platforms as distributables are built differently. if (NOT ANDROID) include(BundleTarget) if (ENABLE_QT) - bundle_target(lime-qt) + qt_bundle_target(lime) + elseif (ENABLE_SDL2_FRONTEND) + bundle_target(lime) endif() + if (ENABLE_DEDICATED_ROOM) bundle_target(lime-room) endif() diff --git a/CMakeModules/BundleTarget.cmake b/CMakeModules/BundleTarget.cmake index 11809e77a..81ea93f7f 100644 --- a/CMakeModules/BundleTarget.cmake +++ b/CMakeModules/BundleTarget.cmake @@ -308,21 +308,16 @@ else() # Adds a target to the bundle target, packing in required libraries. # If in_place is true, the bundling will be done in-place as part of the specified target. - function(bundle_target_internal target_name in_place) + function(bundle_target_internal target_name in_place bundle_qt) # Create base bundle target if it does not exist. if (NOT in_place AND NOT TARGET bundle) create_base_bundle_target() endif() set(bundle_executable_path "$") - if (target_name MATCHES ".*qt") - set(bundle_qt ON) - if (APPLE) - # For Qt targets on Apple, expect an app bundle. - set(bundle_executable_path "$") - endif() - else() - set(bundle_qt OFF) + if (bundle_qt AND APPLE) + # For Qt targets on Apple, expect an app bundle. + set(bundle_executable_path "$") endif() # Build a list of library search paths from prefix paths. @@ -364,11 +359,21 @@ else() # Adds a target to the bundle target, packing in required libraries. function(bundle_target target_name) - bundle_target_internal("${target_name}" OFF) + bundle_target_internal("${target_name}" OFF OFF) + endfunction() + + # Same as bundle_target, but also bundles Qt libraries + function(qt_bundle_target target_name) + bundle_target_internal("${target_name}" OFF ON) endfunction() # Bundles the target in-place, packing in required libraries. function(bundle_target_in_place target_name) - bundle_target_internal("${target_name}" ON) + bundle_target_internal("${target_name}" ON OFF) + endfunction() + + # Same as bundle_target_in_place, but also bundles Qt libraries + function(qt_bundle_target_in_place target_name) + bundle_target_internal("${target_name}" ON ON) endfunction() endif() diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/theme_colorful.qrc similarity index 100% rename from dist/qt_themes/colorful/style.qrc rename to dist/qt_themes/colorful/theme_colorful.qrc diff --git a/dist/qt_themes/colorful_dark/style.qrc b/dist/qt_themes/colorful_dark/theme_colorful_dark.qrc similarity index 100% rename from dist/qt_themes/colorful_dark/style.qrc rename to dist/qt_themes/colorful_dark/theme_colorful_dark.qrc diff --git a/dist/qt_themes/colorful_midnight_blue/style.qrc b/dist/qt_themes/colorful_midnight_blue/theme_colorful_midnight_blue.qrc similarity index 100% rename from dist/qt_themes/colorful_midnight_blue/style.qrc rename to dist/qt_themes/colorful_midnight_blue/theme_colorful_midnight_blue.qrc diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/theme_default.qrc similarity index 100% rename from dist/qt_themes/default/default.qrc rename to dist/qt_themes/default/theme_default.qrc diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/theme_qdarkstyle.qrc similarity index 100% rename from dist/qt_themes/qdarkstyle/style.qrc rename to dist/qt_themes/qdarkstyle/theme_qdarkstyle.qrc diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/style.qrc b/dist/qt_themes/qdarkstyle_midnight_blue/theme_qdarkstyle_midnight_blue.qrc similarity index 100% rename from dist/qt_themes/qdarkstyle_midnight_blue/style.qrc rename to dist/qt_themes/qdarkstyle_midnight_blue/theme_qdarkstyle_midnight_blue.qrc diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cb148ae22..4f6ac5063 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -180,10 +180,18 @@ if (ENABLE_TESTS) add_subdirectory(tests) endif() +if (ENABLE_SDL2_FRONTEND) + add_subdirectory(lime_sdl) +endif() + if (ENABLE_QT) add_subdirectory(lime_qt) endif() +if (ENABLE_QT OR ENABLE_SDL2_FRONTEND) + add_subdirectory(lime) +endif() + if (ENABLE_DEDICATED_ROOM) add_subdirectory(lime_room) endif() diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 960797168..7406cc1f1 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -38,7 +38,7 @@ enum class Class : u8 { Core_ARM11, ///< ARM11 CPU core Core_Timing, ///< CoreTiming functions Core_Cheats, ///< Cheat functions - Config, ///< Emulator configuration (including commandline) + Config, ///< Emulator configuration Debug, ///< Debugging tools Debug_Emulated, ///< Debug messages from the emulated programs Debug_GPU, ///< GPU debugging tools diff --git a/src/lime/CMakeLists.txt b/src/lime/CMakeLists.txt new file mode 100644 index 000000000..afe308ca4 --- /dev/null +++ b/src/lime/CMakeLists.txt @@ -0,0 +1,68 @@ +add_executable(lime + lime.rc + main.cpp +) + +set_target_properties(lime PROPERTIES OUTPUT_NAME "lime3ds") + +if (APPLE) + set(DIST_DIR "../../dist/apple") + set(APPLE_RESOURCES + "${DIST_DIR}/lime.icns" + "${DIST_DIR}/LaunchScreen.storyboard" + "${DIST_DIR}/launch_logo.png" + ) + target_sources(lime PRIVATE ${APPLE_RESOURCES}) + + # Define app bundle metadata. + include(GenerateBuildInfo) + set_target_properties(lime PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST "${DIST_DIR}/Info.plist.in" + MACOSX_BUNDLE_BUNDLE_NAME "Lime3DS" + MACOSX_BUNDLE_GUI_IDENTIFIER "io.github.lime3ds.Lime3DS" + MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_VERSION}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${BUILD_FULLNAME}" + MACOSX_BUNDLE_LONG_VERSION_STRING "${BUILD_FULLNAME}" + MACOSX_BUNDLE_ICON_FILE "lime.icns" + RESOURCE "${APPLE_RESOURCES}" + ) +endif() + +target_link_libraries(lime PRIVATE fmt) + +if (ENABLE_SDL2_FRONTEND) + target_link_libraries(lime PRIVATE lime_sdl) +endif() + +if (ENABLE_QT) + target_link_libraries(lime PRIVATE lime_qt) + target_link_libraries(lime PRIVATE Boost::boost Qt6::Widgets) +endif() + +if (ENABLE_QT AND UNIX AND NOT APPLE) + target_link_libraries(lime PRIVATE Qt6::DBus gamemode) +endif() + +if (ENABLE_QT AND USE_DISCORD_PRESENCE) + target_link_libraries(lime PRIVATE discord-rpc) +endif() + +if(WIN32) + # compile as a win32 gui application instead of a console application + if(MSVC) + set_target_properties(lime PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") + elseif(MINGW) + set_target_properties(lime PROPERTIES LINK_FLAGS_RELEASE "-mwindows") + endif() +endif() + +# Bundle in-place on MSVC so dependencies can be resolved by builds. +if (ENABLE_QT AND MSVC) + include(BundleTarget) + qt_bundle_target_in_place(lime) +endif() + +if(UNIX AND NOT APPLE) + install(TARGETS lime RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") +endif() diff --git a/src/lime/common_strings.h b/src/lime/common_strings.h new file mode 100644 index 000000000..9fa12200a --- /dev/null +++ b/src/lime/common_strings.h @@ -0,0 +1,29 @@ +// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +namespace Common { + +constexpr char help_string[] = + "Usage: {} [options] \n" + "-d, --dump-video [path] Dump video recording of emulator playback to the given file path\n" + "-f, --fullscreen Start in fullscreen mode\n" + "-g, --gdbport [port] Enable gdb stub on the given port\n" + "-h, --help Display this help and exit\n" + "-i, --install [path] Install a CIA file at the given path\n" + "-p, --movie-play [path] Play a TAS movie located at the given path\n" + "-r, --movie-record [path] Record a TAS movie to the given file path\n" + "-a, --movie-record-author [author] Set the author for the recorded TAS movie (to be used " + "alongside --movie-record)\n" + "-n, --no-gui Use the lightweight SDL frontend instead of the usual Qt " + "frontend\n" + "-m, --multiplayer [nick:password@address:port] Nickname, password, address and port for " + "multiplayer\n" + "-v, --version Output version information and exit\n" + "-w, --windowed Start in windowed mode"; + +} diff --git a/src/lime_qt/lime-qt.rc b/src/lime/lime.rc similarity index 100% rename from src/lime_qt/lime-qt.rc rename to src/lime/lime.rc diff --git a/src/lime/main.cpp b/src/lime/main.cpp new file mode 100644 index 000000000..6ceebf7a1 --- /dev/null +++ b/src/lime/main.cpp @@ -0,0 +1,45 @@ +// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#ifdef ENABLE_QT +#include "lime_qt/lime_qt.h" +#endif +#ifdef ENABLE_SDL2_FRONTEND +#include "lime_sdl/lime_sdl.h" +#endif + +#ifdef _WIN32 +extern "C" { +// tells Nvidia drivers to use the dedicated GPU by default on laptops with switchable graphics +__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; +} +#endif + +int main(int argc, char* argv[]) { +#if ENABLE_QT + bool no_gui = false; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--no-gui") == 0 || strcmp(argv[i], "-n") == 0) { + no_gui = true; + } + } + + if (!no_gui) { + LaunchQtFrontend(argc, argv); + return 0; + } +#endif + +#if ENABLE_SDL2_FRONTEND + LaunchSdlFrontend(argc, argv); +#else + std::cout << "Cannot use SDL frontend as it was excluded at compile time. Exiting." + << std::endl; + return -1; +#endif + + return 0; +} \ No newline at end of file diff --git a/src/lime_qt/CMakeLists.txt b/src/lime_qt/CMakeLists.txt index 7d61acc2a..cffab1a3f 100644 --- a/src/lime_qt/CMakeLists.txt +++ b/src/lime_qt/CMakeLists.txt @@ -7,7 +7,7 @@ if (POLICY CMP0071) cmake_policy(SET CMP0071 NEW) endif() -add_executable(lime-qt +add_library(lime_qt STATIC EXCLUDE_FROM_ALL aboutdialog.cpp aboutdialog.h aboutdialog.ui @@ -27,7 +27,6 @@ add_executable(lime-qt camera/qt_camera_base.h camera/qt_multimedia_camera.cpp camera/qt_multimedia_camera.h - lime-qt.rc configuration/config.cpp configuration/config.h configuration/configure.ui @@ -136,11 +135,11 @@ add_executable(lime-qt game_list_worker.h hotkeys.cpp hotkeys.h + lime_qt.cpp + lime_qt.h loading_screen.cpp loading_screen.h loading_screen.ui - main.cpp - main.h main.ui movie/movie_play_dialog.cpp movie/movie_play_dialog.h @@ -191,8 +190,6 @@ add_executable(lime-qt util/util.h ) -set_target_properties(lime-qt PROPERTIES OUTPUT_NAME "lime3ds") - file(GLOB COMPAT_LIST ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) @@ -200,12 +197,12 @@ file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*) file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*) if (ENABLE_QT_UPDATER) - target_sources(lime-qt PRIVATE + target_sources(lime_qt PRIVATE updater/updater.cpp updater/updater.h updater/updater_p.h ) - target_compile_definitions(lime-qt PUBLIC ENABLE_QT_UPDATER) + target_compile_definitions(lime_qt PUBLIC ENABLE_QT_UPDATER) endif() if (ENABLE_QT_TRANSLATION) @@ -214,13 +211,13 @@ if (ENABLE_QT_TRANSLATION) # Update source TS file if enabled if (GENERATE_QT_TRANSLATION) - get_target_property(QT_SRCS lime-qt SOURCES) - get_target_property(QT_INCLUDES lime-qt INCLUDE_DIRECTORIES) - qt_add_lupdate(lime-qt TS_FILES ${LIME3DS_QT_LANGUAGES}/en.ts + get_target_property(QT_SRCS lime_qt SOURCES) + get_target_property(QT_INCLUDES lime_qt INCLUDE_DIRECTORIES) + qt_add_lupdate(lime_qt TS_FILES ${LIME3DS_QT_LANGUAGES}/en.ts SOURCES ${QT_SRCS} ${UIS} INCLUDE_DIRECTORIES ${QT_INCLUDES} NO_GLOBAL_TARGET) - add_custom_target(translation ALL DEPENDS lime-qt_lupdate) + add_custom_target(translation ALL DEPENDS lime_qt_lupdate) endif() # Find all TS files except en.ts @@ -228,7 +225,7 @@ if (ENABLE_QT_TRANSLATION) list(REMOVE_ITEM LANGUAGES_TS ${LIME3DS_QT_LANGUAGES}/en.ts) # Compile TS files to QM files - qt_add_lrelease(lime-qt TS_FILES ${LANGUAGES_TS} NO_GLOBAL_TARGET QM_FILES_OUTPUT_VARIABLE LANGUAGES_QM) + qt_add_lrelease(lime_qt TS_FILES ${LANGUAGES_TS} NO_GLOBAL_TARGET QM_FILES_OUTPUT_VARIABLE LANGUAGES_QM) # Build a QRC file from the QM file list set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc) @@ -245,7 +242,7 @@ else() set(LANGUAGES) endif() -target_sources(lime-qt +target_sources(lime_qt PRIVATE ${COMPAT_LIST} ${ICONS} @@ -253,66 +250,38 @@ target_sources(lime-qt ${THEMES} ) -if (APPLE) - set(DIST_DIR "../../dist/apple") - set(APPLE_RESOURCES - "${DIST_DIR}/lime.icns" - "${DIST_DIR}/LaunchScreen.storyboard" - "${DIST_DIR}/launch_logo.png" - ) - target_sources(lime-qt PRIVATE ${APPLE_RESOURCES}) - - # Define app bundle metadata. - include(GenerateBuildInfo) - set_target_properties(lime-qt PROPERTIES - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_INFO_PLIST "${DIST_DIR}/Info.plist.in" - MACOSX_BUNDLE_BUNDLE_NAME "Lime3DS" - MACOSX_BUNDLE_GUI_IDENTIFIER "io.github.lime3ds.Lime3DS" - MACOSX_BUNDLE_BUNDLE_VERSION "${BUILD_VERSION}" - MACOSX_BUNDLE_SHORT_VERSION_STRING "${BUILD_FULLNAME}" - MACOSX_BUNDLE_LONG_VERSION_STRING "${BUILD_FULLNAME}" - MACOSX_BUNDLE_ICON_FILE "lime.icns" - RESOURCE "${APPLE_RESOURCES}" - ) -elseif(WIN32) - # compile as a win32 gui application instead of a console application - target_link_libraries(lime-qt PRIVATE Qt6::EntryPointImplementation) - if(MSVC) - set_target_properties(lime-qt PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") - elseif(MINGW) - set_target_properties(lime-qt PROPERTIES LINK_FLAGS_RELEASE "-mwindows") - endif() +if (WIN32) + target_link_libraries(lime_qt PRIVATE Qt6::EntryPointImplementation) endif() if(ENABLE_SDL2) - target_link_libraries(lime-qt PRIVATE SDL2::SDL2) - target_compile_definitions(lime-qt PRIVATE HAVE_SDL2) + target_link_libraries(lime_qt PRIVATE SDL2::SDL2) + target_compile_definitions(lime_qt PRIVATE HAVE_SDL2) endif() -create_target_directory_groups(lime-qt) +create_target_directory_groups(lime_qt) -target_link_libraries(lime-qt PRIVATE audio_core lime_common lime_core input_common network video_core) -target_link_libraries(lime-qt PRIVATE Boost::boost nihstro-headers Qt6::Widgets Qt6::Multimedia Qt6::Concurrent) -target_link_libraries(lime-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) +target_link_libraries(lime_qt PRIVATE audio_core lime_common lime_core input_common network video_core) +target_link_libraries(lime_qt PRIVATE Boost::boost nihstro-headers Qt6::Widgets Qt6::Multimedia Qt6::Concurrent) +target_link_libraries(lime_qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) if (ENABLE_OPENGL) - target_link_libraries(lime-qt PRIVATE glad) + target_link_libraries(lime_qt PRIVATE glad) endif() if (ENABLE_VULKAN) - target_link_libraries(lime-qt PRIVATE vulkan-headers) + target_link_libraries(lime_qt PRIVATE vulkan-headers) endif() if (NOT WIN32) - target_include_directories(lime-qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) + target_include_directories(lime_qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) endif() if (UNIX AND NOT APPLE) - target_link_libraries(lime-qt PRIVATE Qt6::DBus gamemode) + target_link_libraries(lime_qt PRIVATE Qt6::DBus gamemode) endif() -target_compile_definitions(lime-qt PRIVATE +target_compile_definitions(lime_qt PRIVATE # Use QStringBuilder for string concatenation to reduce # the overall number of temporary strings created. -DQT_USE_QSTRINGBUILDER @@ -332,28 +301,18 @@ target_compile_definitions(lime-qt PRIVATE ) if (USE_DISCORD_PRESENCE) - target_sources(lime-qt PUBLIC + target_sources(lime_qt PUBLIC discord_impl.cpp discord_impl.h ) - target_link_libraries(lime-qt PRIVATE discord-rpc) - target_compile_definitions(lime-qt PRIVATE -DUSE_DISCORD_PRESENCE) + target_link_libraries(lime_qt PRIVATE discord-rpc) + target_compile_definitions(lime_qt PRIVATE -DUSE_DISCORD_PRESENCE) endif() if (ENABLE_WEB_SERVICE) - target_link_libraries(lime-qt PRIVATE web_service) -endif() - -if(UNIX AND NOT APPLE) - install(TARGETS lime-qt RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") + target_link_libraries(lime_qt PRIVATE web_service) endif() if (LIME3DS_USE_PRECOMPILED_HEADERS) - target_precompile_headers(lime-qt PRIVATE precompiled_headers.h) -endif() - -# Bundle in-place on MSVC so dependencies can be resolved by builds. -if (MSVC) - include(BundleTarget) - bundle_target_in_place(lime-qt) + target_precompile_headers(lime_qt PRIVATE precompiled_headers.h) endif() diff --git a/src/lime_qt/bootmanager.cpp b/src/lime_qt/bootmanager.cpp index 9e9926f33..90cb8a132 100644 --- a/src/lime_qt/bootmanager.cpp +++ b/src/lime_qt/bootmanager.cpp @@ -20,7 +20,7 @@ #include "input_common/main.h" #include "input_common/motion_emu.h" #include "lime_qt/bootmanager.h" -#include "lime_qt/main.h" +#include "lime_qt/lime_qt.h" #include "video_core/custom_textures/custom_tex_manager.h" #include "video_core/gpu.h" #include "video_core/renderer_base.h" diff --git a/src/lime_qt/camera/qt_multimedia_camera.cpp b/src/lime_qt/camera/qt_multimedia_camera.cpp index 9725089b4..98f73d2f4 100644 --- a/src/lime_qt/camera/qt_multimedia_camera.cpp +++ b/src/lime_qt/camera/qt_multimedia_camera.cpp @@ -7,7 +7,7 @@ #include #include #include "lime_qt/camera/qt_multimedia_camera.h" -#include "lime_qt/main.h" +#include "lime_qt/lime_qt.h" #if defined(__APPLE__) #include "common/apple_authorization.h" diff --git a/src/lime_qt/configuration/config.cpp b/src/lime_qt/configuration/config.cpp index 9342ce436..21abee0e1 100644 --- a/src/lime_qt/configuration/config.cpp +++ b/src/lime_qt/configuration/config.cpp @@ -15,24 +15,24 @@ #include "network/network.h" #include "network/network_settings.h" -Config::Config(const std::string& config_name, ConfigType config_type) : type{config_type} { +QtConfig::QtConfig(const std::string& config_name, ConfigType config_type) : type{config_type} { global = config_type == ConfigType::GlobalConfig; Initialize(config_name); } -Config::~Config() { +QtConfig::~QtConfig() { if (global) { Save(); } } -const std::array Config::default_buttons = { +const std::array QtConfig::default_buttons = { Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, Qt::Key_Q, Qt::Key_W, Qt::Key_M, Qt::Key_N, Qt::Key_O, Qt::Key_P, Qt::Key_1, Qt::Key_2, Qt::Key_B, Qt::Key_V, }; -const std::array, Settings::NativeAnalog::NumAnalogs> Config::default_analogs{{ +const std::array, Settings::NativeAnalog::NumAnalogs> QtConfig::default_analogs{{ { Qt::Key_Up, Qt::Key_Down, @@ -54,7 +54,7 @@ const std::array, Settings::NativeAnalog::NumAnalogs> Config: // This must be in alphabetical order according to action name as it must have the same order as // UISetting::values.shortcuts, which is alphabetically ordered. // clang-format off -const std::array Config::default_hotkeys {{ +const std::array QtConfig::default_hotkeys {{ {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}}, @@ -93,7 +93,7 @@ const std::array Config::default_hotkeys {{ }}; // clang-format on -void Config::Initialize(const std::string& config_name) { +void QtConfig::Initialize(const std::string& config_name) { const std::string fs_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir); const std::string config_file = fmt::format("{}.ini", config_name); @@ -120,7 +120,7 @@ void Config::Initialize(const std::string& config_name) { // Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant, nor // can it implicitly convert a QVariant back to a {std::,Q}string template <> -void Config::ReadBasicSetting(Settings::Setting& setting) { +void QtConfig::ReadBasicSetting(Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const auto default_value = QString::fromStdString(setting.GetDefault()); if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) { @@ -131,7 +131,7 @@ void Config::ReadBasicSetting(Settings::Setting& setting) { } template -void Config::ReadBasicSetting(Settings::Setting& setting) { +void QtConfig::ReadBasicSetting(Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type default_value = setting.GetDefault(); if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) { @@ -150,7 +150,7 @@ void Config::ReadBasicSetting(Settings::Setting& setting) { } template -void Config::ReadGlobalSetting(Settings::SwitchableSetting& setting) { +void QtConfig::ReadGlobalSetting(Settings::SwitchableSetting& setting) { QString name = QString::fromStdString(setting.GetLabel()); const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); setting.SetGlobal(use_global); @@ -168,7 +168,7 @@ void Config::ReadGlobalSetting(Settings::SwitchableSetting& settin } template <> -void Config::ReadGlobalSetting(Settings::SwitchableSetting& setting) { +void QtConfig::ReadGlobalSetting(Settings::SwitchableSetting& setting) { QString name = QString::fromStdString(setting.GetLabel()); const bool use_global = qt_config->value(name + QStringLiteral("/use_global"), true).toBool(); setting.SetGlobal(use_global); @@ -181,7 +181,7 @@ void Config::ReadGlobalSetting(Settings::SwitchableSetting& setting // Explicit std::string definition: Qt can't implicitly convert a std::string to a QVariant template <> -void Config::WriteBasicSetting(const Settings::Setting& setting) { +void QtConfig::WriteBasicSetting(const Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const std::string& value = setting.GetValue(); qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); @@ -190,7 +190,7 @@ void Config::WriteBasicSetting(const Settings::Setting& setting) { // Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable template <> -void Config::WriteBasicSetting(const Settings::Setting& setting) { +void QtConfig::WriteBasicSetting(const Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const u16& value = setting.GetValue(); qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); @@ -198,7 +198,7 @@ void Config::WriteBasicSetting(const Settings::Setting& setting) { } template -void Config::WriteBasicSetting(const Settings::Setting& setting) { +void QtConfig::WriteBasicSetting(const Settings::Setting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type value = setting.GetValue(); qt_config->setValue(name + QStringLiteral("/default"), value == setting.GetDefault()); @@ -210,7 +210,7 @@ void Config::WriteBasicSetting(const Settings::Setting& setting) { } template -void Config::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { +void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const Type& value = setting.GetValue(global); if (!global) { @@ -227,7 +227,7 @@ void Config::WriteGlobalSetting(const Settings::SwitchableSetting& } template <> -void Config::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { +void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const std::string& value = setting.GetValue(global); if (!global) { @@ -241,7 +241,7 @@ void Config::WriteGlobalSetting(const Settings::SwitchableSetting& // Explicit u16 definition: Qt would store it as QMetaType otherwise, which is not human-readable template <> -void Config::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { +void QtConfig::WriteGlobalSetting(const Settings::SwitchableSetting& setting) { const QString name = QString::fromStdString(setting.GetLabel()); const u16& value = setting.GetValue(global); if (!global) { @@ -253,7 +253,7 @@ void Config::WriteGlobalSetting(const Settings::SwitchableSetting& se } } -void Config::ReadValues() { +void QtConfig::ReadValues() { if (global) { ReadControlValues(); ReadCameraValues(); @@ -273,7 +273,7 @@ void Config::ReadValues() { ReadUtilityValues(); } -void Config::ReadAudioValues() { +void QtConfig::ReadAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); ReadGlobalSetting(Settings::values.audio_emulation); @@ -291,7 +291,7 @@ void Config::ReadAudioValues() { qt_config->endGroup(); } -void Config::ReadCameraValues() { +void QtConfig::ReadCameraValues() { using namespace Service::CAM; qt_config->beginGroup(QStringLiteral("Camera")); @@ -325,7 +325,7 @@ void Config::ReadCameraValues() { qt_config->endGroup(); } -void Config::ReadControlValues() { +void QtConfig::ReadControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); ReadBasicSetting(Settings::values.use_artic_base_controller); @@ -442,7 +442,7 @@ void Config::ReadControlValues() { qt_config->endGroup(); } -void Config::ReadUtilityValues() { +void QtConfig::ReadUtilityValues() { qt_config->beginGroup(QStringLiteral("Utility")); ReadGlobalSetting(Settings::values.dump_textures); @@ -453,7 +453,7 @@ void Config::ReadUtilityValues() { qt_config->endGroup(); } -void Config::ReadCoreValues() { +void QtConfig::ReadCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); ReadGlobalSetting(Settings::values.cpu_clock_percentage); @@ -466,7 +466,7 @@ void Config::ReadCoreValues() { qt_config->endGroup(); } -void Config::ReadDataStorageValues() { +void QtConfig::ReadDataStorageValues() { qt_config->beginGroup(QStringLiteral("Data Storage")); ReadBasicSetting(Settings::values.use_virtual_sd); @@ -485,7 +485,7 @@ void Config::ReadDataStorageValues() { qt_config->endGroup(); } -void Config::ReadDebuggingValues() { +void QtConfig::ReadDebuggingValues() { qt_config->beginGroup(QStringLiteral("Debugging")); // Intentionally not using the QT default setting as this is intended to be changed in the ini @@ -505,7 +505,7 @@ void Config::ReadDebuggingValues() { qt_config->endGroup(); } -void Config::ReadLayoutValues() { +void QtConfig::ReadLayoutValues() { qt_config->beginGroup(QStringLiteral("Layout")); ReadGlobalSetting(Settings::values.render_3d); @@ -550,7 +550,7 @@ void Config::ReadLayoutValues() { qt_config->endGroup(); } -void Config::ReadMiscellaneousValues() { +void QtConfig::ReadMiscellaneousValues() { qt_config->beginGroup(QStringLiteral("Miscellaneous")); ReadBasicSetting(Settings::values.log_filter); @@ -559,7 +559,7 @@ void Config::ReadMiscellaneousValues() { qt_config->endGroup(); } -void Config::ReadMultiplayerValues() { +void QtConfig::ReadMultiplayerValues() { qt_config->beginGroup(QStringLiteral("Multiplayer")); UISettings::values.nickname = ReadSetting(QStringLiteral("nickname"), QString{}).toString(); @@ -610,7 +610,7 @@ void Config::ReadMultiplayerValues() { qt_config->endGroup(); } -void Config::ReadPathValues() { +void QtConfig::ReadPathValues() { qt_config->beginGroup(QStringLiteral("Paths")); ReadGlobalSetting(UISettings::values.screenshot_path); @@ -662,7 +662,7 @@ void Config::ReadPathValues() { qt_config->endGroup(); } -void Config::ReadRendererValues() { +void QtConfig::ReadRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); ReadGlobalSetting(Settings::values.graphics_api); @@ -691,7 +691,7 @@ void Config::ReadRendererValues() { qt_config->endGroup(); } -void Config::ReadShortcutValues() { +void QtConfig::ReadShortcutValues() { qt_config->beginGroup(QStringLiteral("Shortcuts")); for (const auto& [name, group, shortcut] : default_hotkeys) { @@ -712,7 +712,7 @@ void Config::ReadShortcutValues() { qt_config->endGroup(); } -void Config::ReadSystemValues() { +void QtConfig::ReadSystemValues() { qt_config->beginGroup(QStringLiteral("System")); ReadGlobalSetting(Settings::values.is_new_3ds); @@ -738,7 +738,7 @@ const QString DEFAULT_VIDEO_ENCODER_OPTIONS = QStringLiteral("quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1"); const QString DEFAULT_AUDIO_ENCODER_OPTIONS = QStringLiteral(""); -void Config::ReadVideoDumpingValues() { +void QtConfig::ReadVideoDumpingValues() { qt_config->beginGroup(QStringLiteral("VideoDumping")); Settings::values.output_format = @@ -775,7 +775,7 @@ void Config::ReadVideoDumpingValues() { qt_config->endGroup(); } -void Config::ReadUIValues() { +void QtConfig::ReadUIValues() { qt_config->beginGroup(QStringLiteral("UI")); ReadPathValues(); @@ -811,7 +811,7 @@ void Config::ReadUIValues() { qt_config->endGroup(); } -void Config::ReadUIGameListValues() { +void QtConfig::ReadUIGameListValues() { qt_config->beginGroup(QStringLiteral("GameList")); ReadBasicSetting(UISettings::values.game_list_icon_size); @@ -837,7 +837,7 @@ void Config::ReadUIGameListValues() { qt_config->endGroup(); } -void Config::ReadUILayoutValues() { +void QtConfig::ReadUILayoutValues() { qt_config->beginGroup(QStringLiteral("UILayout")); UISettings::values.geometry = ReadSetting(QStringLiteral("geometry")).toByteArray(); @@ -853,7 +853,7 @@ void Config::ReadUILayoutValues() { qt_config->endGroup(); } -void Config::ReadUpdaterValues() { +void QtConfig::ReadUpdaterValues() { qt_config->beginGroup(QStringLiteral("Updater")); ReadBasicSetting(UISettings::values.check_for_update_on_start); @@ -862,7 +862,7 @@ void Config::ReadUpdaterValues() { qt_config->endGroup(); } -void Config::ReadWebServiceValues() { +void QtConfig::ReadWebServiceValues() { qt_config->beginGroup(QStringLiteral("WebService")); NetSettings::values.web_api_url = @@ -877,7 +877,7 @@ void Config::ReadWebServiceValues() { qt_config->endGroup(); } -void Config::SaveValues() { +void QtConfig::SaveValues() { if (global) { SaveControlValues(); SaveCameraValues(); @@ -898,7 +898,7 @@ void Config::SaveValues() { qt_config->sync(); } -void Config::SaveAudioValues() { +void QtConfig::SaveAudioValues() { qt_config->beginGroup(QStringLiteral("Audio")); WriteGlobalSetting(Settings::values.audio_emulation); @@ -916,7 +916,7 @@ void Config::SaveAudioValues() { qt_config->endGroup(); } -void Config::SaveCameraValues() { +void QtConfig::SaveCameraValues() { using namespace Service::CAM; qt_config->beginGroup(QStringLiteral("Camera")); @@ -946,7 +946,7 @@ void Config::SaveCameraValues() { qt_config->endGroup(); } -void Config::SaveControlValues() { +void QtConfig::SaveControlValues() { qt_config->beginGroup(QStringLiteral("Controls")); WriteBasicSetting(Settings::values.use_artic_base_controller); @@ -1007,7 +1007,7 @@ void Config::SaveControlValues() { qt_config->endGroup(); } -void Config::SaveUtilityValues() { +void QtConfig::SaveUtilityValues() { qt_config->beginGroup(QStringLiteral("Utility")); WriteGlobalSetting(Settings::values.dump_textures); @@ -1018,7 +1018,7 @@ void Config::SaveUtilityValues() { qt_config->endGroup(); } -void Config::SaveCoreValues() { +void QtConfig::SaveCoreValues() { qt_config->beginGroup(QStringLiteral("Core")); WriteGlobalSetting(Settings::values.cpu_clock_percentage); @@ -1031,7 +1031,7 @@ void Config::SaveCoreValues() { qt_config->endGroup(); } -void Config::SaveDataStorageValues() { +void QtConfig::SaveDataStorageValues() { qt_config->beginGroup(QStringLiteral("Data Storage")); WriteBasicSetting(Settings::values.use_virtual_sd); @@ -1046,7 +1046,7 @@ void Config::SaveDataStorageValues() { qt_config->endGroup(); } -void Config::SaveDebuggingValues() { +void QtConfig::SaveDebuggingValues() { qt_config->beginGroup(QStringLiteral("Debugging")); // Intentionally not using the QT default setting as this is intended to be changed in the ini @@ -1064,7 +1064,7 @@ void Config::SaveDebuggingValues() { qt_config->endGroup(); } -void Config::SaveLayoutValues() { +void QtConfig::SaveLayoutValues() { qt_config->beginGroup(QStringLiteral("Layout")); WriteGlobalSetting(Settings::values.render_3d); @@ -1108,7 +1108,7 @@ void Config::SaveLayoutValues() { qt_config->endGroup(); } -void Config::SaveMiscellaneousValues() { +void QtConfig::SaveMiscellaneousValues() { qt_config->beginGroup(QStringLiteral("Miscellaneous")); WriteBasicSetting(Settings::values.log_filter); @@ -1117,7 +1117,7 @@ void Config::SaveMiscellaneousValues() { qt_config->endGroup(); } -void Config::SaveMultiplayerValues() { +void QtConfig::SaveMultiplayerValues() { qt_config->beginGroup(QStringLiteral("Multiplayer")); WriteSetting(QStringLiteral("nickname"), UISettings::values.nickname, QString{}); @@ -1160,7 +1160,7 @@ void Config::SaveMultiplayerValues() { qt_config->endGroup(); } -void Config::SavePathValues() { +void QtConfig::SavePathValues() { qt_config->beginGroup(QStringLiteral("Paths")); WriteGlobalSetting(UISettings::values.screenshot_path); @@ -1188,7 +1188,7 @@ void Config::SavePathValues() { qt_config->endGroup(); } -void Config::SaveRendererValues() { +void QtConfig::SaveRendererValues() { qt_config->beginGroup(QStringLiteral("Renderer")); WriteGlobalSetting(Settings::values.graphics_api); @@ -1218,7 +1218,7 @@ void Config::SaveRendererValues() { qt_config->endGroup(); } -void Config::SaveShortcutValues() { +void QtConfig::SaveShortcutValues() { qt_config->beginGroup(QStringLiteral("Shortcuts")); // Lengths of UISettings::values.shortcuts & default_hotkeys are same. @@ -1238,7 +1238,7 @@ void Config::SaveShortcutValues() { qt_config->endGroup(); } -void Config::SaveSystemValues() { +void QtConfig::SaveSystemValues() { qt_config->beginGroup(QStringLiteral("System")); WriteGlobalSetting(Settings::values.is_new_3ds); @@ -1258,7 +1258,7 @@ void Config::SaveSystemValues() { qt_config->endGroup(); } -void Config::SaveVideoDumpingValues() { +void QtConfig::SaveVideoDumpingValues() { qt_config->beginGroup(QStringLiteral("VideoDumping")); WriteSetting(QStringLiteral("output_format"), @@ -1285,7 +1285,7 @@ void Config::SaveVideoDumpingValues() { qt_config->endGroup(); } -void Config::SaveUIValues() { +void QtConfig::SaveUIValues() { qt_config->beginGroup(QStringLiteral("UI")); SavePathValues(); @@ -1320,7 +1320,7 @@ void Config::SaveUIValues() { qt_config->endGroup(); } -void Config::SaveUIGameListValues() { +void QtConfig::SaveUIGameListValues() { qt_config->beginGroup(QStringLiteral("GameList")); WriteBasicSetting(UISettings::values.game_list_icon_size); @@ -1346,7 +1346,7 @@ void Config::SaveUIGameListValues() { qt_config->endGroup(); } -void Config::SaveUILayoutValues() { +void QtConfig::SaveUILayoutValues() { qt_config->beginGroup(QStringLiteral("UILayout")); WriteSetting(QStringLiteral("geometry"), UISettings::values.geometry); @@ -1360,7 +1360,7 @@ void Config::SaveUILayoutValues() { qt_config->endGroup(); } -void Config::SaveUpdaterValues() { +void QtConfig::SaveUpdaterValues() { qt_config->beginGroup(QStringLiteral("Updater")); WriteBasicSetting(UISettings::values.check_for_update_on_start); @@ -1369,7 +1369,7 @@ void Config::SaveUpdaterValues() { qt_config->endGroup(); } -void Config::SaveWebServiceValues() { +void QtConfig::SaveWebServiceValues() { qt_config->beginGroup(QStringLiteral("WebService")); WriteSetting(QStringLiteral("web_api_url"), @@ -1383,11 +1383,11 @@ void Config::SaveWebServiceValues() { qt_config->endGroup(); } -QVariant Config::ReadSetting(const QString& name) const { +QVariant QtConfig::ReadSetting(const QString& name) const { return qt_config->value(name); } -QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const { +QVariant QtConfig::ReadSetting(const QString& name, const QVariant& default_value) const { QVariant result; if (qt_config->value(name + QStringLiteral("/default"), false).toBool()) { result = default_value; @@ -1397,22 +1397,22 @@ QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) return result; } -void Config::WriteSetting(const QString& name, const QVariant& value) { +void QtConfig::WriteSetting(const QString& name, const QVariant& value) { qt_config->setValue(name, value); } -void Config::WriteSetting(const QString& name, const QVariant& value, - const QVariant& default_value) { +void QtConfig::WriteSetting(const QString& name, const QVariant& value, + const QVariant& default_value) { qt_config->setValue(name + QStringLiteral("/default"), value == default_value); qt_config->setValue(name, value); } -void Config::Reload() { +void QtConfig::Reload() { ReadValues(); // To apply default value changes SaveValues(); } -void Config::Save() { +void QtConfig::Save() { SaveValues(); } diff --git a/src/lime_qt/configuration/config.h b/src/lime_qt/configuration/config.h index 5bb8ef3d9..b82c716d2 100644 --- a/src/lime_qt/configuration/config.h +++ b/src/lime_qt/configuration/config.h @@ -13,13 +13,13 @@ class QSettings; -class Config { +class QtConfig { public: enum class ConfigType : u32 { GlobalConfig, PerGameConfig }; - explicit Config(const std::string& config_name = "qt-config", - ConfigType config_type = ConfigType::GlobalConfig); - ~Config(); + explicit QtConfig(const std::string& config_name = "qt-config", + ConfigType config_type = ConfigType::GlobalConfig); + ~QtConfig(); void Reload(); void Save(); diff --git a/src/lime_qt/configuration/configure_hotkeys.cpp b/src/lime_qt/configuration/configure_hotkeys.cpp index a65969c21..e9ddb1f30 100644 --- a/src/lime_qt/configuration/configure_hotkeys.cpp +++ b/src/lime_qt/configuration/configure_hotkeys.cpp @@ -162,7 +162,7 @@ void ConfigureHotkeys::RestoreDefaults() { for (int r2 = 0; r2 < parent->rowCount(); ++r2) { model->item(r, 0) ->child(r2, hotkey_column) - ->setText(Config::default_hotkeys[r2].shortcut.keyseq); + ->setText(QtConfig::default_hotkeys[r2].shortcut.keyseq); } } } @@ -198,7 +198,7 @@ void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) { void ConfigureHotkeys::RestoreHotkey(QModelIndex index) { const QKeySequence& default_key_sequence = QKeySequence::fromString( - Config::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText); + QtConfig::default_hotkeys[index.row()].shortcut.keyseq, QKeySequence::NativeText); const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence); if (key_sequence_used && default_key_sequence != QKeySequence(model->data(index).toString())) { diff --git a/src/lime_qt/configuration/configure_input.cpp b/src/lime_qt/configuration/configure_input.cpp index 39b461996..be06280a6 100644 --- a/src/lime_qt/configuration/configure_input.cpp +++ b/src/lime_qt/configuration/configure_input.cpp @@ -228,8 +228,9 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) Settings::SaveProfile(ui->profile->currentIndex()); }); context_menu.addAction(tr("Restore Default"), this, [&] { - buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + buttons_param[button_id] = + Common::ParamPackage{InputCommon::GenerateKeyboardParam( + QtConfig::default_buttons[button_id])}; button_map[button_id]->setText(ButtonToText(buttons_param[button_id])); ApplyConfiguration(); Settings::SaveProfile(ui->profile->currentIndex()); @@ -268,7 +269,7 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) }); context_menu.addAction(tr("Restore Default"), this, [&] { Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; + QtConfig::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( @@ -347,8 +348,8 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) { Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id] - [static_cast(AnalogSubButtons::modifier)])}; + QtConfig::default_analogs[analog_id][static_cast( + AnalogSubButtons::modifier)])}; SetAnalogButton(params, analogs_param[analog_id], "modifier"); ui->buttonCircleMod->setText( AnalogToText(analogs_param[analog_id], "modifier")); @@ -464,13 +465,13 @@ void ConfigureInput::LoadConfiguration() { void ConfigureInput::RestoreDefaults() { for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { buttons_param[button_id] = Common::ParamPackage{ - InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])}; + InputCommon::GenerateKeyboardParam(QtConfig::default_buttons[button_id])}; } 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++) { Common::ParamPackage params{InputCommon::GenerateKeyboardParam( - Config::default_analogs[analog_id][sub_button_id])}; + QtConfig::default_analogs[analog_id][sub_button_id])}; SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]); } analogs_param[analog_id].Set("modifier_scale", 0.5f); diff --git a/src/lime_qt/configuration/configure_per_game.cpp b/src/lime_qt/configuration/configure_per_game.cpp index 3cd982378..c889eb11d 100644 --- a/src/lime_qt/configuration/configure_per_game.cpp +++ b/src/lime_qt/configuration/configure_per_game.cpp @@ -31,7 +31,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString filename{file_name.toStdString()}, title_id{title_id_}, system{system_} { const auto config_file_name = title_id == 0 ? std::string(FileUtil::GetFilename(filename)) : fmt::format("{:016X}", title_id); - game_config = std::make_unique(config_file_name, Config::ConfigType::PerGameConfig); + game_config = std::make_unique(config_file_name, QtConfig::ConfigType::PerGameConfig); const bool is_powered_on = system.IsPoweredOn(); audio_tab = std::make_unique(is_powered_on, this); diff --git a/src/lime_qt/configuration/configure_per_game.h b/src/lime_qt/configuration/configure_per_game.h index 571c6d567..648a89f13 100644 --- a/src/lime_qt/configuration/configure_per_game.h +++ b/src/lime_qt/configuration/configure_per_game.h @@ -65,7 +65,7 @@ private: QGraphicsScene* scene; - std::unique_ptr game_config; + std::unique_ptr game_config; Core::System& system; diff --git a/src/lime_qt/game_list.cpp b/src/lime_qt/game_list.cpp index 10d03ec58..81b65fc0b 100644 --- a/src/lime_qt/game_list.cpp +++ b/src/lime_qt/game_list.cpp @@ -35,7 +35,7 @@ #include "lime_qt/game_list.h" #include "lime_qt/game_list_p.h" #include "lime_qt/game_list_worker.h" -#include "lime_qt/main.h" +#include "lime_qt/lime_qt.h" #include "lime_qt/uisettings.h" #include "qcursor.h" diff --git a/src/lime_qt/main.cpp b/src/lime_qt/lime_qt.cpp similarity index 98% rename from src/lime_qt/main.cpp rename to src/lime_qt/lime_qt.cpp index 73752e680..abf40cafc 100644 --- a/src/lime_qt/main.cpp +++ b/src/lime_qt/lime_qt.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include #include @@ -24,7 +25,6 @@ #include #include #else -#include #include #endif #ifdef __unix__ @@ -44,6 +44,7 @@ #include "common/memory_detect.h" #include "common/scm_rev.h" #include "common/scope_exit.h" +#include "lime/common_strings.h" #include "lime_qt/aboutdialog.h" #include "lime_qt/applets/mii_selector.h" #include "lime_qt/applets/swkbd.h" @@ -70,8 +71,8 @@ #include "lime_qt/dumping/dumping_dialog.h" #include "lime_qt/game_list.h" #include "lime_qt/hotkeys.h" +#include "lime_qt/lime_qt.h" #include "lime_qt/loading_screen.h" -#include "lime_qt/main.h" #include "lime_qt/movie/movie_play_dialog.h" #include "lime_qt/movie/movie_record_dialog.h" #include "lime_qt/multiplayer/state.h" @@ -116,13 +117,6 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); #endif -#ifdef _WIN32 -extern "C" { -// tells Nvidia drivers to use the dedicated GPU by default on laptops with switchable graphics -__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001; -} -#endif - #ifdef HAVE_SDL2 #include #endif @@ -175,7 +169,7 @@ GMainWindow::GMainWindow(Core::System& system_) Debugger::ToggleConsole(); - this->config = std::make_unique(); + this->config = std::make_unique(); QStringList args = QApplication::arguments(); QString game_path; @@ -188,7 +182,7 @@ GMainWindow::GMainWindow(Core::System& system_) } // Dump video - if (args[i] == QStringLiteral("-d")) { + if (args[i] == QStringLiteral("--dump-video") || args[i] == QStringLiteral("-d")) { if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) { continue; } @@ -202,13 +196,13 @@ GMainWindow::GMainWindow(Core::System& system_) } // Launch game in fullscreen mode - if (args[i] == QStringLiteral("-f")) { + if (args[i] == QStringLiteral("--fullscreen") || args[i] == QStringLiteral("-f")) { fullscreen_override = true; continue; } // Enable GDB stub - if (args[i] == QStringLiteral("-g")) { + if (args[i] == QStringLiteral("--gdbport") || args[i] == QStringLiteral("-g")) { if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) { continue; } @@ -217,24 +211,12 @@ GMainWindow::GMainWindow(Core::System& system_) continue; } - if (args[i] == QStringLiteral("-h")) { - const std::string help_string = - std::string("Usage: ") + args[0].toStdString() + - " [options] \n" - "-d [path] Dump video recording of emulator playback to the given file path\n" - "-g [port] Enable gdb stub on the given port\n" - "-f Start in fullscreen mode\n" - "-h Display this help and exit\n" - "-i [path] Install a CIA file at the given path\n" - "-p [path] Play a TAS movie located at the given path\n" - "-r [path] Record a TAS movie to the given file path\n" - "-v Output version information and exit"; - - ShowCommandOutput("Help", help_string); + if (args[i] == QStringLiteral("--help") || args[i] == QStringLiteral("-h")) { + ShowCommandOutput("Help", fmt::format(Common::help_string, args[0].toStdString())); exit(0); } - if (args[i] == QStringLiteral("-i")) { + if (args[i] == QStringLiteral("--install") || args[i] == QStringLiteral("-i")) { if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) { continue; } @@ -267,7 +249,7 @@ GMainWindow::GMainWindow(Core::System& system_) exit(0); } - if (args[i] == QStringLiteral("-p")) { + if (args[i] == QStringLiteral("--movie-play") || args[i] == QStringLiteral("-p")) { if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) { continue; } @@ -276,7 +258,7 @@ GMainWindow::GMainWindow(Core::System& system_) continue; } - if (args[i] == QStringLiteral("-r")) { + if (args[i] == QStringLiteral("--movie-record") || args[i] == QStringLiteral("-r")) { if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) { continue; } @@ -285,7 +267,25 @@ GMainWindow::GMainWindow(Core::System& system_) continue; } - if (args[i] == QStringLiteral("-v")) { + if (args[i] == QStringLiteral("--movie-record-author") || args[i] == QStringLiteral("-a")) { + if (i >= args.size() - 1 || args[i + 1].startsWith(QChar::fromLatin1('-'))) { + continue; + } + movie_record_author = args[++i]; + continue; + } + + if (args[i] == QStringLiteral("--multiplayer") || args[i] == QStringLiteral("-m")) { + std::cout << "Warning: The --multiplayer option is not yet implemented for the Qt " + "frontend; Ignoring." + << std::endl; + if (i < args.size() - 1 && !args[i + 1].startsWith(QChar::fromLatin1('-'))) { + i++; + } + continue; + } + + if (args[i] == QStringLiteral("--version") || args[i] == QStringLiteral("-v")) { const std::string version_string = std::string("Lime3DS ") + Common::g_scm_branch + " " + Common::g_scm_desc; ShowCommandOutput("Version", version_string); @@ -293,7 +293,7 @@ GMainWindow::GMainWindow(Core::System& system_) } // Launch game in windowed mode - if (args[i] == QStringLiteral("-w")) { + if (args[i] == QStringLiteral("--windowed") || args[i] == QStringLiteral("-w")) { fullscreen_override = false; continue; } @@ -1468,7 +1468,7 @@ void GMainWindow::BootGame(const QString& filename) { const std::string config_file_name = title_id == 0 ? name : fmt::format("{:016X}", title_id); LOG_INFO(Frontend, "Loading per game config file for title {}", config_file_name); - Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); + QtConfig per_game_config(config_file_name, QtConfig::ConfigType::PerGameConfig); } // Artic Base Server cannot accept a client multiple times, so multiple loaders are not @@ -3722,7 +3722,7 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() { #endif } -int main(int argc, char* argv[]) { +void LaunchQtFrontend(int argc, char* argv[]) { Common::DetachedTasks detached_tasks; MicroProfileOnThreadCreate("Frontend"); SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -3748,6 +3748,19 @@ int main(int argc, char* argv[]) { QApplication app(argc, argv); + // Required when using .qrc resources from within a static library. + // See https://doc.qt.io/qt-5/resources.html#using-resources-in-a-library + Q_INIT_RESOURCE(compatibility_list); + Q_INIT_RESOURCE(theme_colorful); + Q_INIT_RESOURCE(theme_colorful_dark); + Q_INIT_RESOURCE(theme_colorful_midnight_blue); + Q_INIT_RESOURCE(theme_default); + Q_INIT_RESOURCE(theme_qdarkstyle); + Q_INIT_RESOURCE(theme_qdarkstyle_midnight_blue); +#ifdef ENABLE_QT_TRANSLATION + Q_INIT_RESOURCE(languages); +#endif + // Qt changes the locale and causes issues in float conversion using std::to_string() when // generating shaders setlocale(LC_ALL, "C"); @@ -3777,5 +3790,5 @@ int main(int argc, char* argv[]) { int result = app.exec(); detached_tasks.WaitForAllTasks(); - return result; + exit(result); } diff --git a/src/lime_qt/main.h b/src/lime_qt/lime_qt.h similarity index 99% rename from src/lime_qt/main.h rename to src/lime_qt/lime_qt.h index f8aae8195..7a174b35d 100644 --- a/src/lime_qt/main.h +++ b/src/lime_qt/lime_qt.h @@ -25,7 +25,7 @@ #include class AboutDialog; -class Config; +class QtConfig; class ClickableLabel; class EmuThread; class GameList; @@ -86,6 +86,8 @@ namespace Service::FS { enum class MediaType : u32; } +void LaunchQtFrontend(int argc, char* argv[]); + class GMainWindow : public QMainWindow { Q_OBJECT @@ -348,7 +350,7 @@ private: bool message_label_used_for_movie = false; MultiplayerState* multiplayer_state = nullptr; - std::unique_ptr config; + std::unique_ptr config; // Whether emulation is currently running in Lime3DS. bool emulation_running = false; diff --git a/src/lime_qt/multiplayer/direct_connect.cpp b/src/lime_qt/multiplayer/direct_connect.cpp index 77fdf2edf..1717eca3e 100644 --- a/src/lime_qt/multiplayer/direct_connect.cpp +++ b/src/lime_qt/multiplayer/direct_connect.cpp @@ -9,7 +9,7 @@ #include #include #include "core/hle/service/cfg/cfg.h" -#include "lime_qt/main.h" +#include "lime_qt/lime_qt.h" #include "lime_qt/multiplayer/direct_connect.h" #include "lime_qt/multiplayer/message.h" #include "lime_qt/multiplayer/validation.h" diff --git a/src/lime_qt/multiplayer/lobby.cpp b/src/lime_qt/multiplayer/lobby.cpp index c4b017a9f..6eaaf2829 100644 --- a/src/lime_qt/multiplayer/lobby.cpp +++ b/src/lime_qt/multiplayer/lobby.cpp @@ -8,7 +8,7 @@ #include "common/logging/log.h" #include "core/hle/service/cfg/cfg.h" #include "lime_qt/game_list_p.h" -#include "lime_qt/main.h" +#include "lime_qt/lime_qt.h" #include "lime_qt/multiplayer/lobby.h" #include "lime_qt/multiplayer/lobby_p.h" #include "lime_qt/multiplayer/message.h" diff --git a/src/lime_sdl/CMakeLists.txt b/src/lime_sdl/CMakeLists.txt new file mode 100644 index 000000000..3516f9f1a --- /dev/null +++ b/src/lime_sdl/CMakeLists.txt @@ -0,0 +1,48 @@ +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) + +add_library(lime_sdl STATIC EXCLUDE_FROM_ALL + config.cpp + config.h + default_ini.h + emu_window/emu_window_sdl2.cpp + emu_window/emu_window_sdl2.h + lime_sdl.cpp + precompiled_headers.h + resource.h +) + +if (ENABLE_SOFTWARE_RENDERER) + target_sources(lime_sdl PRIVATE + emu_window/emu_window_sdl2_sw.cpp + emu_window/emu_window_sdl2_sw.h + ) +endif() +if (ENABLE_OPENGL) + target_sources(lime_sdl PRIVATE + emu_window/emu_window_sdl2_gl.cpp + emu_window/emu_window_sdl2_gl.h + ) +endif() +if (ENABLE_VULKAN) + target_sources(lime_sdl PRIVATE + emu_window/emu_window_sdl2_vk.cpp + emu_window/emu_window_sdl2_vk.h + ) +endif() + +create_target_directory_groups(lime_sdl) + +target_link_libraries(lime_sdl PRIVATE lime_common lime_core input_common network) +target_link_libraries(lime_sdl PRIVATE inih) +if (MSVC) + target_link_libraries(lime_sdl PRIVATE getopt) +endif() +target_link_libraries(lime_sdl PRIVATE ${PLATFORM_LIBRARIES} SDL2::SDL2 Threads::Threads) + +if (ENABLE_OPENGL) + target_link_libraries(lime_sdl PRIVATE glad) +endif() + +if (LIME3DS_USE_PRECOMPILED_HEADERS) + target_precompile_headers(lime_sdl PRIVATE precompiled_headers.h) +endif() diff --git a/src/lime_sdl/config.cpp b/src/lime_sdl/config.cpp new file mode 100644 index 000000000..e376869a1 --- /dev/null +++ b/src/lime_sdl/config.cpp @@ -0,0 +1,383 @@ +// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include "common/file_util.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "core/hle/service/service.h" +#include "input_common/main.h" +#include "input_common/udp/client.h" +#include "lime_sdl/config.h" +#include "lime_sdl/default_ini.h" +#include "network/network_settings.h" + +SdlConfig::SdlConfig() { + // TODO: Don't hardcode the path; let the frontend decide where to put the config files. + sdl2_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "sdl2-config.ini"; + sdl2_config = std::make_unique(sdl2_config_loc); + + Reload(); +} + +SdlConfig::~SdlConfig() = default; + +bool SdlConfig::LoadINI(const std::string& default_contents, bool retry) { + const std::string& location = this->sdl2_config_loc; + if (sdl2_config->ParseError() < 0) { + if (retry) { + LOG_WARNING(Config, "Failed to load {}. Creating file from defaults...", location); + FileUtil::CreateFullPath(location); + FileUtil::WriteStringToFile(true, location, default_contents); + sdl2_config = std::make_unique(location); // Reopen file + + return LoadINI(default_contents, false); + } + LOG_ERROR(Config, "Failed."); + return false; + } + LOG_INFO(Config, "Successfully loaded {}", location); + return true; +} + +static const std::array default_buttons = { + SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T, SDL_SCANCODE_G, + SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_M, SDL_SCANCODE_N, + SDL_SCANCODE_O, SDL_SCANCODE_P, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B, +}; + +static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs{{ + { + SDL_SCANCODE_UP, + SDL_SCANCODE_DOWN, + SDL_SCANCODE_LEFT, + SDL_SCANCODE_RIGHT, + SDL_SCANCODE_D, + }, + { + SDL_SCANCODE_I, + SDL_SCANCODE_K, + SDL_SCANCODE_J, + SDL_SCANCODE_L, + SDL_SCANCODE_D, + }, +}}; + +template <> +void SdlConfig::ReadSetting(const std::string& group, Settings::Setting& setting) { + std::string setting_value = sdl2_config->Get(group, setting.GetLabel(), setting.GetDefault()); + if (setting_value.empty()) { + setting_value = setting.GetDefault(); + } + setting = std::move(setting_value); +} + +template <> +void SdlConfig::ReadSetting(const std::string& group, Settings::Setting& setting) { + setting = sdl2_config->GetBoolean(group, setting.GetLabel(), setting.GetDefault()); +} + +template +void SdlConfig::ReadSetting(const std::string& group, Settings::Setting& setting) { + if constexpr (std::is_floating_point_v) { + setting = static_cast( + sdl2_config->GetReal(group, setting.GetLabel(), setting.GetDefault())); + } else { + setting = static_cast(sdl2_config->GetInteger( + group, setting.GetLabel(), static_cast(setting.GetDefault()))); + } +} + +void SdlConfig::ReadValues() { + // Controls + // TODO: add multiple input profile support + for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) { + std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]); + Settings::values.current_input_profile.buttons[i] = + sdl2_config->GetString("Controls", Settings::NativeButton::mapping[i], default_param); + if (Settings::values.current_input_profile.buttons[i].empty()) + Settings::values.current_input_profile.buttons[i] = default_param; + } + + for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) { + std::string default_param = InputCommon::GenerateAnalogParamFromKeys( + default_analogs[i][0], default_analogs[i][1], default_analogs[i][2], + default_analogs[i][3], default_analogs[i][4], 0.5f); + Settings::values.current_input_profile.analogs[i] = + sdl2_config->GetString("Controls", Settings::NativeAnalog::mapping[i], default_param); + if (Settings::values.current_input_profile.analogs[i].empty()) + Settings::values.current_input_profile.analogs[i] = default_param; + } + + Settings::values.current_input_profile.motion_device = sdl2_config->GetString( + "Controls", "motion_device", + "engine:motion_emu,update_period:100,sensitivity:0.01,tilt_clamp:90.0"); + Settings::values.current_input_profile.touch_device = + sdl2_config->GetString("Controls", "touch_device", "engine:emu_window"); + Settings::values.current_input_profile.udp_input_address = sdl2_config->GetString( + "Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR); + Settings::values.current_input_profile.udp_input_port = + static_cast(sdl2_config->GetInteger("Controls", "udp_input_port", + InputCommon::CemuhookUDP::DEFAULT_PORT)); + + // Core + ReadSetting("Core", Settings::values.use_cpu_jit); + ReadSetting("Core", Settings::values.cpu_clock_percentage); + + // Renderer + ReadSetting("Renderer", Settings::values.graphics_api); + ReadSetting("Renderer", Settings::values.physical_device); + ReadSetting("Renderer", Settings::values.spirv_shader_gen); + ReadSetting("Renderer", Settings::values.async_shader_compilation); + ReadSetting("Renderer", Settings::values.async_presentation); + ReadSetting("Renderer", Settings::values.use_gles); + ReadSetting("Renderer", Settings::values.use_hw_shader); + ReadSetting("Renderer", Settings::values.shaders_accurate_mul); + ReadSetting("Renderer", Settings::values.use_shader_jit); + ReadSetting("Renderer", Settings::values.resolution_factor); + ReadSetting("Renderer", Settings::values.use_disk_shader_cache); + ReadSetting("Renderer", Settings::values.frame_limit); + ReadSetting("Renderer", Settings::values.use_vsync_new); + ReadSetting("Renderer", Settings::values.texture_filter); + ReadSetting("Renderer", Settings::values.texture_sampling); + + ReadSetting("Renderer", Settings::values.mono_render_option); + ReadSetting("Renderer", Settings::values.render_3d); + ReadSetting("Renderer", Settings::values.factor_3d); + ReadSetting("Renderer", Settings::values.pp_shader_name); + ReadSetting("Renderer", Settings::values.anaglyph_shader_name); + ReadSetting("Renderer", Settings::values.filter_mode); + + ReadSetting("Renderer", Settings::values.bg_red); + ReadSetting("Renderer", Settings::values.bg_green); + ReadSetting("Renderer", Settings::values.bg_blue); + + // Layout + ReadSetting("Layout", Settings::values.layout_option); + ReadSetting("Layout", Settings::values.swap_screen); + ReadSetting("Layout", Settings::values.upright_screen); + ReadSetting("Layout", Settings::values.large_screen_proportion); + ReadSetting("Layout", Settings::values.custom_top_x); + ReadSetting("Layout", Settings::values.custom_top_y); + ReadSetting("Layout", Settings::values.custom_top_width); + ReadSetting("Layout", Settings::values.custom_top_height); + ReadSetting("Layout", Settings::values.custom_bottom_x); + ReadSetting("Layout", Settings::values.custom_bottom_y); + ReadSetting("Layout", Settings::values.custom_bottom_width); + ReadSetting("Layout", Settings::values.custom_bottom_height); + ReadSetting("Layout", Settings::values.custom_second_layer_opacity); + + ReadSetting("Layout", Settings::values.screen_top_stretch); + ReadSetting("Layout", Settings::values.screen_top_leftright_padding); + ReadSetting("Layout", Settings::values.screen_top_topbottom_padding); + ReadSetting("Layout", Settings::values.screen_bottom_stretch); + ReadSetting("Layout", Settings::values.screen_bottom_leftright_padding); + ReadSetting("Layout", Settings::values.screen_bottom_topbottom_padding); + + ReadSetting("Layout", Settings::values.portrait_layout_option); + ReadSetting("Layout", Settings::values.custom_portrait_top_x); + ReadSetting("Layout", Settings::values.custom_portrait_top_y); + ReadSetting("Layout", Settings::values.custom_portrait_top_width); + ReadSetting("Layout", Settings::values.custom_portrait_top_height); + ReadSetting("Layout", Settings::values.custom_portrait_bottom_x); + ReadSetting("Layout", Settings::values.custom_portrait_bottom_y); + ReadSetting("Layout", Settings::values.custom_portrait_bottom_width); + ReadSetting("Layout", Settings::values.custom_portrait_bottom_height); + + // Utility + ReadSetting("Utility", Settings::values.dump_textures); + ReadSetting("Utility", Settings::values.custom_textures); + ReadSetting("Utility", Settings::values.preload_textures); + ReadSetting("Utility", Settings::values.async_custom_loading); + + // Audio + ReadSetting("Audio", Settings::values.audio_emulation); + ReadSetting("Audio", Settings::values.enable_audio_stretching); + ReadSetting("Audio", Settings::values.enable_realtime_audio); + ReadSetting("Audio", Settings::values.volume); + ReadSetting("Audio", Settings::values.output_type); + ReadSetting("Audio", Settings::values.output_device); + ReadSetting("Audio", Settings::values.input_type); + ReadSetting("Audio", Settings::values.input_device); + + // Data Storage + ReadSetting("Data Storage", Settings::values.use_virtual_sd); + ReadSetting("Data Storage", Settings::values.use_custom_storage); + + if (Settings::values.use_custom_storage) { + FileUtil::UpdateUserPath(FileUtil::UserPath::NANDDir, + sdl2_config->GetString("Data Storage", "nand_directory", "")); + FileUtil::UpdateUserPath(FileUtil::UserPath::SDMCDir, + sdl2_config->GetString("Data Storage", "sdmc_directory", "")); + } + + // System + ReadSetting("System", Settings::values.is_new_3ds); + ReadSetting("System", Settings::values.lle_applets); + ReadSetting("System", Settings::values.region_value); + ReadSetting("System", Settings::values.init_clock); + { + std::tm t; + t.tm_sec = 1; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = 1; + t.tm_mon = 0; + t.tm_year = 100; + t.tm_isdst = 0; + std::istringstream string_stream( + sdl2_config->GetString("System", "init_time", "2000-01-01 00:00:01")); + string_stream >> std::get_time(&t, "%Y-%m-%d %H:%M:%S"); + if (string_stream.fail()) { + LOG_ERROR(Config, "Failed To parse init_time. Using 2000-01-01 00:00:01"); + } + Settings::values.init_time = + std::chrono::duration_cast( + std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch()) + .count(); + } + ReadSetting("System", Settings::values.init_ticks_type); + ReadSetting("System", Settings::values.init_ticks_override); + ReadSetting("System", Settings::values.plugin_loader_enabled); + ReadSetting("System", Settings::values.allow_plugin_loader); + + { + constexpr const char* default_init_time_offset = "0 00:00:00"; + + std::string offset_string = + sdl2_config->GetString("System", "init_time_offset", default_init_time_offset); + + std::size_t sep_index = offset_string.find(' '); + + if (sep_index == std::string::npos) { + LOG_ERROR(Config, "Failed to parse init_time_offset. Using 0 00:00:00"); + offset_string = default_init_time_offset; + + sep_index = offset_string.find(' '); + } + + std::string day_string = offset_string.substr(0, sep_index); + long long days = 0; + + try { + days = std::stoll(day_string); + } catch (std::logic_error&) { + LOG_ERROR(Config, "Failed to parse days in init_time_offset. Using 0"); + days = 0; + } + + long long days_in_seconds = days * 86400; + + std::tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = 1; + t.tm_mon = 0; + t.tm_year = 100; + t.tm_isdst = 0; + + std::istringstream string_stream(offset_string.substr(sep_index + 1)); + string_stream >> std::get_time(&t, "%H:%M:%S"); + + if (string_stream.fail()) { + LOG_ERROR(Config, + "Failed to parse hours, minutes and seconds in init_time_offset. 00:00:00"); + } + + auto time_offset = + std::chrono::system_clock::from_time_t(std::mktime(&t)).time_since_epoch(); + + auto secs = std::chrono::duration_cast(time_offset).count(); + + Settings::values.init_time_offset = static_cast(secs) + days_in_seconds; + } + + // Camera + using namespace Service::CAM; + Settings::values.camera_name[OuterRightCamera] = + sdl2_config->GetString("Camera", "camera_outer_right_name", "blank"); + Settings::values.camera_config[OuterRightCamera] = + sdl2_config->GetString("Camera", "camera_outer_right_config", ""); + Settings::values.camera_flip[OuterRightCamera] = + sdl2_config->GetInteger("Camera", "camera_outer_right_flip", 0); + Settings::values.camera_name[InnerCamera] = + sdl2_config->GetString("Camera", "camera_inner_name", "blank"); + Settings::values.camera_config[InnerCamera] = + sdl2_config->GetString("Camera", "camera_inner_config", ""); + Settings::values.camera_flip[InnerCamera] = + sdl2_config->GetInteger("Camera", "camera_inner_flip", 0); + Settings::values.camera_name[OuterLeftCamera] = + sdl2_config->GetString("Camera", "camera_outer_left_name", "blank"); + Settings::values.camera_config[OuterLeftCamera] = + sdl2_config->GetString("Camera", "camera_outer_left_config", ""); + Settings::values.camera_flip[OuterLeftCamera] = + sdl2_config->GetInteger("Camera", "camera_outer_left_flip", 0); + + // Miscellaneous + ReadSetting("Miscellaneous", Settings::values.log_filter); + + // Apply the log_filter setting as the logger has already been initialized + // and doesn't pick up the filter on its own. + Common::Log::Filter filter; + filter.ParseFilterString(Settings::values.log_filter.GetValue()); + Common::Log::SetGlobalFilter(filter); + + // Debugging + Settings::values.record_frame_times = + sdl2_config->GetBoolean("Debugging", "record_frame_times", false); + ReadSetting("Debugging", Settings::values.renderer_debug); + ReadSetting("Debugging", Settings::values.use_gdbstub); + ReadSetting("Debugging", Settings::values.gdbstub_port); + + for (const auto& service_module : Service::service_module_map) { + bool use_lle = sdl2_config->GetBoolean("Debugging", "LLE\\" + service_module.name, false); + Settings::values.lle_modules.emplace(service_module.name, use_lle); + } + + // Web Service + NetSettings::values.web_api_url = + sdl2_config->GetString("WebService", "web_api_url", "https://api.citra-emu.org"); + NetSettings::values.lime3ds_username = + sdl2_config->GetString("WebService", "lime3ds_username", ""); + NetSettings::values.lime3ds_token = sdl2_config->GetString("WebService", "lime3ds_token", ""); + + // Video Dumping + Settings::values.output_format = + sdl2_config->GetString("Video Dumping", "output_format", "webm"); + Settings::values.format_options = sdl2_config->GetString("Video Dumping", "format_options", ""); + + Settings::values.video_encoder = + sdl2_config->GetString("Video Dumping", "video_encoder", "libvpx-vp9"); + + // Options for variable bit rate live streaming taken from here: + // https://developers.google.com/media/vp9/live-encoding + std::string default_video_options; + if (Settings::values.video_encoder == "libvpx-vp9") { + default_video_options = + "quality:realtime,speed:6,tile-columns:4,frame-parallel:1,threads:8,row-mt:1"; + } + Settings::values.video_encoder_options = + sdl2_config->GetString("Video Dumping", "video_encoder_options", default_video_options); + Settings::values.video_bitrate = + sdl2_config->GetInteger("Video Dumping", "video_bitrate", 2500000); + + Settings::values.audio_encoder = + sdl2_config->GetString("Video Dumping", "audio_encoder", "libvorbis"); + Settings::values.audio_encoder_options = + sdl2_config->GetString("Video Dumping", "audio_encoder_options", ""); + Settings::values.audio_bitrate = + sdl2_config->GetInteger("Video Dumping", "audio_bitrate", 64000); +} + +void SdlConfig::Reload() { + LoadINI(DefaultINI::sdl2_config_file); + ReadValues(); +} diff --git a/src/lime_sdl/config.h b/src/lime_sdl/config.h new file mode 100644 index 000000000..d7c3e6c80 --- /dev/null +++ b/src/lime_sdl/config.h @@ -0,0 +1,35 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/settings.h" + +class INIReader; + +class SdlConfig { + std::unique_ptr sdl2_config; + std::string sdl2_config_loc; + + bool LoadINI(const std::string& default_contents = "", bool retry = true); + void ReadValues(); + +public: + SdlConfig(); + ~SdlConfig(); + + void Reload(); + +private: + /** + * Applies a value read from the sdl2_config to a Setting. + * + * @param group The name of the INI group + * @param setting The yuzu setting to modify + */ + template + void ReadSetting(const std::string& group, Settings::Setting& setting); +}; diff --git a/src/lime_sdl/default_ini.h b/src/lime_sdl/default_ini.h new file mode 100644 index 000000000..2a1b74ab5 --- /dev/null +++ b/src/lime_sdl/default_ini.h @@ -0,0 +1,398 @@ +// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace DefaultINI { + +const char* sdl2_config_file = R"( +[Controls] +# The input devices and parameters for each 3DS native input +# It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." +# Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values + +# for button input, the following devices are available: +# - "keyboard" (default) for keyboard input. Required parameters: +# - "code": the code of the key to bind +# - "sdl" for joystick input using SDL. Required parameters: +# - "joystick": the index of the joystick to bind +# - "button"(optional): the index of the button to bind +# - "hat"(optional): the index of the hat to bind as direction buttons +# - "axis"(optional): the index of the axis to bind +# - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", "down", "left" or "right" +# - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is +# triggered if the axis value crosses +# - "direction"(only used for axis): "+" means the button is triggered when the axis value +# is greater than the threshold; "-" means the button is triggered when the axis value +# is smaller than the threshold +button_a= +button_b= +button_x= +button_y= +button_up= +button_down= +button_left= +button_right= +button_l= +button_r= +button_start= +button_select= +button_debug= +button_gpio14= +button_zl= +button_zr= +button_home= + +# for analog input, the following devices are available: +# - "analog_from_button" (default) for emulating analog input from direction buttons. Required parameters: +# - "up", "down", "left", "right": sub-devices for each direction. +# Should be in the format as a button input devices using escape characters, for example, "engine$0keyboard$1code$00" +# - "modifier": sub-devices as a modifier. +# - "modifier_scale": a float number representing the applied modifier scale to the analog input. +# Must be in range of 0.0-1.0. Defaults to 0.5 +# - "sdl" for joystick input using SDL. Required parameters: +# - "joystick": the index of the joystick to bind +# - "axis_x": the index of the axis to bind as x-axis (default to 0) +# - "axis_y": the index of the axis to bind as y-axis (default to 1) +circle_pad= +c_stick= + +# for motion input, the following devices are available: +# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: +# - "update_period": update period in milliseconds (default to 100) +# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) +# - "tilt_clamp": the max value of the tilt angle in degrees (default to 90) +# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol +motion_device= + +# for touch input, the following devices are available: +# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required +# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol +# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system +touch_device= + +# Most desktop operating systems do not expose a way to poll the motion state of the controllers +# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly +# from a controller device to the client program. Lime3DS has a client that can connect and read +# from any cemuhook compatible motion program. + +# IPv4 address of the udp input server (Default "127.0.0.1") +udp_input_address= + +# Port of the udp input server. (Default 26760) +udp_input_port= + +# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0) +udp_pad_index= + +[Core] +# Whether to use the Just-In-Time (JIT) compiler for CPU emulation +# 0: Interpreter (slow), 1 (default): JIT (fast) +use_cpu_jit = + +# Change the Clock Frequency of the emulated 3DS CPU. +# Underclocking can increase the performance of the game at the risk of freezing. +# Overclocking may fix lag that happens on console, but also comes with the risk of freezing. +# Range is any positive integer (but we suspect 25 - 400 is a good idea) Default is 100 +cpu_clock_percentage = + +[Renderer] +# Whether to render using OpenGL or Software +# 0: Software, 1: OpenGL (default), 2: Vulkan +graphics_api = + +# Whether to render using GLES or OpenGL +# 0 (default): OpenGL, 1: GLES +use_gles = + +# Whether to use hardware shaders to emulate 3DS shaders +# 0: Software, 1 (default): Hardware +use_hw_shader = + +# Whether to use accurate multiplication in hardware shaders +# 0: Off (Faster, but causes issues in some games) 1: On (Default. Slower, but correct) +shaders_accurate_mul = + +# Whether to use the Just-In-Time (JIT) compiler for shader emulation +# 0: Interpreter (slow), 1 (default): JIT (fast) +use_shader_jit = + +# Forces VSync on the display thread. Usually doesn't impact performance, but on some drivers it can +# so only turn this off if you notice a speed difference. +# 0: Off, 1 (default): On +use_vsync_new = + +# Reduce stuttering by storing and loading generated shaders to disk +# 0: Off, 1 (default. On) +use_disk_shader_cache = + +# Resolution scale factor +# 0: Auto (scales resolution to window size), 1: Native 3DS screen resolution, Otherwise a scale +# factor for the 3DS resolution +resolution_factor = + +# Texture filter +# 0: None, 1: Anime4K, 2: Bicubic, 3: Nearest Neighbor, 4: ScaleForce, 5: xBRZ +texture_filter = + +# Limits the speed of the game to run no faster than this value as a percentage of target speed. +# Will not have an effect if unthrottled is enabled. +# 5 - 995: Speed limit as a percentage of target game speed. 0 for unthrottled. 100 (default) +frame_limit = + +# Overrides the frame limiter to use frame_limit_alternate instead of frame_limit. +# 0: Off (default), 1: On +use_frame_limit_alternate = + +# Alternate speed limit to be used instead of frame_limit if use_frame_limit_alternate is enabled +# 5 - 995: Speed limit as a percentage of target game speed. 0 for unthrottled. 200 (default) +frame_limit_alternate = + +# The clear color for the renderer. What shows up on the sides of the bottom screen. +# Must be in range of 0.0-1.0. Defaults to 0.0 for all. +bg_red = +bg_blue = +bg_green = + +# Whether and how Stereoscopic 3D should be rendered +# 0 (default): Off, 1: Side by Side, 2: Reverse Side by Side, 3: Anaglyph, 4: Interlaced, 5: Reverse Interlaced +render_3d = + +# Change 3D Intensity +# 0 - 100: Intensity. 0 (default) +factor_3d = + +# Change Default Eye to Render When in Monoscopic Mode +# 0 (default): Left, 1: Right +mono_render_option = + +# The name of the post processing shader to apply. +# Loaded from shaders if render_3d is off or side by side. +pp_shader_name = + +# The name of the shader to apply when render_3d is anaglyph. +# Loaded from shaders/anaglyph +anaglyph_shader_name = + +# Whether to enable linear filtering or not +# This is required for some shaders to work correctly +# 0: Nearest, 1 (default): Linear +filter_mode = + +[Layout] +# Layout for the screen inside the render window. +# 0 (default): Default Above/Below Screen +# 1: Single Screen Only +# 2: Large Screen Small Screen +# 3: Side by Side +# 4: Separate Windows +# 5: Hybrid Screen +# 6: Custom Layout +layout_option = + +# Screen placement when using Custom layout option +# 0x, 0y is the top left corner of the render window. +custom_top_x = +custom_top_y = +custom_top_width = +custom_top_height = +custom_bottom_x = +custom_bottom_y = +custom_bottom_width = +custom_bottom_height = + +# Opacity of second layer when using custom layout option (bottom screen unless swapped) +custom_second_layer_opacity = + +# Swaps the prominent screen with the other screen. +# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen. +# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent +swap_screen = + +# Toggle upright orientation, for book style games. +# 0 (default): Off, 1: On +upright_screen = + +# The proportion between the large and small screens when playing in Large Screen Small Screen layout. +# Must be a real value between 1.0 and 16.0. Default is 4 +large_screen_proportion = + +# Dumps textures as PNG to dump/textures/[Title ID]/. +# 0 (default): Off, 1: On +dump_textures = + +# Reads PNG files from load/textures/[Title ID]/ and replaces textures. +# 0 (default): Off, 1: On +custom_textures = + +# Loads all custom textures into memory before booting. +# 0 (default): Off, 1: On +preload_textures = + +# Loads custom textures asynchronously with background threads. +# 0: Off, 1 (default): On +async_custom_loading = + +[Audio] +# Whether or not to enable DSP LLE +# 0 (default): No, 1: Yes +enable_dsp_lle = + +# Whether or not to run DSP LLE on a different thread +# 0 (default): No, 1: Yes +enable_dsp_lle_thread = + +# Whether or not to enable the audio-stretching post-processing effect. +# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, +# at the cost of increasing audio latency. +# 0: No, 1 (default): Yes +enable_audio_stretching = + +# Scales audio playback speed to account for drops in emulation framerate +# 0 (default): No, 1: Yes +enable_realtime_audio = + +# Output volume. +# 1.0 (default): 100%, 0.0; mute +volume = + +# Which audio output type to use. +# 0 (default): Auto-select, 1: No audio output, 2: Cubeb (if available), 3: OpenAL (if available), 4: SDL2 (if available) +output_type = + +# Which audio output device to use. +# auto (default): Auto-select +output_device = + +# Which audio input type to use. +# 0 (default): Auto-select, 1: No audio input, 2: Static noise, 3: Cubeb (if available), 4: OpenAL (if available) +input_type = + +# Which audio input device to use. +# auto (default): Auto-select +input_device = + +[Data Storage] +# Whether to create a virtual SD card. +# 1 (default): Yes, 0: No +use_virtual_sd = + +# Whether to use custom storage locations +# 1: Yes, 0 (default): No +use_custom_storage = + +# The path of the virtual SD card directory. +# empty (default) will use the user_path +sdmc_directory = + +# The path of NAND directory. +# empty (default) will use the user_path +nand_directory = + +[System] +# The system model that Lime3DS will try to emulate +# 0: Old 3DS, 1: New 3DS (default) +is_new_3ds = + +# Whether to use LLE system applets, if installed +# 0 (default): No, 1: Yes +lle_applets = + +# The system region that Lime3DS will use during emulation +# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan +region_value = + +# The clock to use when lime3ds starts +# 0: System clock (default), 1: fixed time +init_clock = + +# Time used when init_clock is set to fixed_time in the format %Y-%m-%d %H:%M:%S +# set to fixed time. Default 2000-01-01 00:00:01 +# Note: 3DS can only handle times later then Jan 1 2000 +init_time = + +# The system ticks count to use when lime3ds starts +# 0: Random (default), 1: Fixed +init_ticks_type = + +# Tick count to use when init_ticks_type is set to Fixed. +# Defaults to 0. +init_ticks_override = + +[Camera] +# Which camera engine to use for the right outer camera +# blank (default): a dummy camera that always returns black image +camera_outer_right_name = + +# A config string for the right outer camera. Its meaning is defined by the camera engine +camera_outer_right_config = + +# The image flip to apply +# 0: None (default), 1: Horizontal, 2: Vertical, 3: Reverse +camera_outer_right_flip = + +# ... for the left outer camera +camera_outer_left_name = +camera_outer_left_config = +camera_outer_left_flip = + +# ... for the inner camera +camera_inner_name = +camera_inner_config = +camera_inner_flip = + +[Miscellaneous] +# A filter which removes logs below a certain logging level. +# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical +log_filter = *:Info + +[Debugging] +# Record frame time data, can be found in the log directory. Boolean value +record_frame_times = + +# Port for listening to GDB connections. +use_gdbstub=false +gdbstub_port=24689 + +# Whether to enable additional debugging information during emulation +# 0 (default): Off, 1: On +renderer_debug = + +# To LLE a service module add "LLE\=true" + +[WebService] +# URL for Web API +web_api_url = https://api.citra-emu.org +# Username and token for Lime3DS Web Service +# See https://profile.citra-emu.org/ for more info +lime3ds_username = +lime3ds_token = + +[Video Dumping] +# Format of the video to output, default: webm +output_format = + +# Options passed to the muxer (optional) +# This is a param package, format: [key1]:[value1],[key2]:[value2],... +format_options = + +# Video encoder used, default: libvpx-vp9 +video_encoder = + +# Options passed to the video codec (optional) +video_encoder_options = + +# Video bitrate, default: 2500000 +video_bitrate = + +# Audio encoder used, default: libvorbis +audio_encoder = + +# Options passed to the audio codec (optional) +audio_encoder_options = + +# Audio bitrate, default: 64000 +audio_bitrate = +)"; +} diff --git a/src/lime_sdl/emu_window/emu_window_sdl2.cpp b/src/lime_sdl/emu_window/emu_window_sdl2.cpp new file mode 100644 index 000000000..75808642f --- /dev/null +++ b/src/lime_sdl/emu_window/emu_window_sdl2.cpp @@ -0,0 +1,252 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#define SDL_MAIN_HANDLED +#include +#include "common/logging/log.h" +#include "common/scm_rev.h" +#include "core/core.h" +#include "input_common/keyboard.h" +#include "input_common/main.h" +#include "input_common/motion_emu.h" +#include "lime_sdl/emu_window/emu_window_sdl2.h" +#include "network/network.h" + +void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { + TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); + InputCommon::GetMotionEmu()->Tilt(x, y); +} + +void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { + if (button == SDL_BUTTON_LEFT) { + if (state == SDL_PRESSED) { + TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); + } else { + TouchReleased(); + } + } else if (button == SDL_BUTTON_RIGHT) { + if (state == SDL_PRESSED) { + InputCommon::GetMotionEmu()->BeginTilt(x, y); + } else { + InputCommon::GetMotionEmu()->EndTilt(); + } + } +} + +std::pair EmuWindow_SDL2::TouchToPixelPos(float touch_x, float touch_y) const { + int w, h; + SDL_GetWindowSize(render_window, &w, &h); + + touch_x *= w; + touch_y *= h; + + return {static_cast(std::max(std::round(touch_x), 0.0f)), + static_cast(std::max(std::round(touch_y), 0.0f))}; +} + +void EmuWindow_SDL2::OnFingerDown(float x, float y) { + // TODO(NeatNit): keep track of multitouch using the fingerID and a dictionary of some kind + // This isn't critical because the best we can do when we have that is to average them, like the + // 3DS does + + const auto [px, py] = TouchToPixelPos(x, y); + TouchPressed(px, py); +} + +void EmuWindow_SDL2::OnFingerMotion(float x, float y) { + const auto [px, py] = TouchToPixelPos(x, y); + TouchMoved(px, py); +} + +void EmuWindow_SDL2::OnFingerUp() { + TouchReleased(); +} + +void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) { + if (state == SDL_PRESSED) { + InputCommon::GetKeyboard()->PressKey(key); + } else if (state == SDL_RELEASED) { + InputCommon::GetKeyboard()->ReleaseKey(key); + } +} + +bool EmuWindow_SDL2::IsOpen() const { + return is_open; +} + +void EmuWindow_SDL2::RequestClose() { + is_open = false; +} + +void EmuWindow_SDL2::OnResize() { + int width, height; + SDL_GL_GetDrawableSize(render_window, &width, &height); + UpdateCurrentFramebufferLayout(width, height); +} + +void EmuWindow_SDL2::Fullscreen() { + if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) { + return; + } + + LOG_ERROR(Frontend, "Fullscreening failed: {}", SDL_GetError()); + + // Try a different fullscreening method + LOG_INFO(Frontend, "Attempting to use borderless fullscreen..."); + if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) { + return; + } + + LOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError()); + + // Fallback algorithm: Maximise window. + // Works on all systems (unless something is seriously wrong), so no fallback for this one. + LOG_INFO(Frontend, "Falling back on a maximised window..."); + SDL_MaximizeWindow(render_window); +} + +EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system_, bool is_secondary) + : EmuWindow(is_secondary), system(system_) {} + +EmuWindow_SDL2::~EmuWindow_SDL2() { + SDL_Quit(); +} + +void EmuWindow_SDL2::InitializeSDL2() { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) < 0) { + LOG_CRITICAL(Frontend, "Failed to initialize SDL2: {}! Exiting...", SDL_GetError()); + exit(1); + } + + InputCommon::Init(); + Network::Init(); + + SDL_SetMainReady(); +} + +u32 EmuWindow_SDL2::GetEventWindowId(const SDL_Event& event) const { + switch (event.type) { + case SDL_WINDOWEVENT: + return event.window.windowID; + case SDL_KEYDOWN: + case SDL_KEYUP: + return event.key.windowID; + case SDL_MOUSEMOTION: + return event.motion.windowID; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + return event.button.windowID; + case SDL_MOUSEWHEEL: + return event.wheel.windowID; + case SDL_FINGERDOWN: + case SDL_FINGERMOTION: + case SDL_FINGERUP: + return event.tfinger.windowID; + case SDL_TEXTEDITING: + return event.edit.windowID; + case SDL_TEXTEDITING_EXT: + return event.editExt.windowID; + case SDL_TEXTINPUT: + return event.text.windowID; + case SDL_DROPBEGIN: + case SDL_DROPFILE: + case SDL_DROPTEXT: + case SDL_DROPCOMPLETE: + return event.drop.windowID; + case SDL_USEREVENT: + return event.user.windowID; + default: + // Event is not for any particular window, so we can just pretend it's for this one. + return render_window_id; + } +} + +void EmuWindow_SDL2::PollEvents() { + SDL_Event event; + std::vector other_window_events; + + // SDL_PollEvent returns 0 when there are no more events in the event queue + while (SDL_PollEvent(&event)) { + if (GetEventWindowId(event) != render_window_id) { + other_window_events.push_back(event); + continue; + } + + switch (event.type) { + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_MINIMIZED: + OnResize(); + break; + case SDL_WINDOWEVENT_CLOSE: + RequestClose(); + break; + } + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + OnKeyEvent(static_cast(event.key.keysym.scancode), event.key.state); + break; + case SDL_MOUSEMOTION: + // ignore if it came from touch + if (event.button.which != SDL_TOUCH_MOUSEID) + OnMouseMotion(event.motion.x, event.motion.y); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + // ignore if it came from touch + if (event.button.which != SDL_TOUCH_MOUSEID) { + OnMouseButton(event.button.button, event.button.state, event.button.x, + event.button.y); + } + break; + case SDL_FINGERDOWN: + OnFingerDown(event.tfinger.x, event.tfinger.y); + break; + case SDL_FINGERMOTION: + OnFingerMotion(event.tfinger.x, event.tfinger.y); + break; + case SDL_FINGERUP: + OnFingerUp(); + break; + case SDL_QUIT: + RequestClose(); + break; + default: + break; + } + } + for (auto& e : other_window_events) { + // This is a somewhat hacky workaround to re-emit window events meant for another window + // since SDL_PollEvent() is global but we poll events per window. + SDL_PushEvent(&e); + } + if (!is_secondary) { + UpdateFramerateCounter(); + } +} + +void EmuWindow_SDL2::OnMinimalClientAreaChangeRequest(std::pair minimal_size) { + SDL_SetWindowMinimumSize(render_window, minimal_size.first, minimal_size.second); +} + +void EmuWindow_SDL2::UpdateFramerateCounter() { + const u32 current_time = SDL_GetTicks(); + if (current_time > last_time + 2000) { + const auto results = system.GetAndResetPerfStats(); + const auto title = + fmt::format("Lime3DS {} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc, results.game_fps, + results.emulation_speed * 100.0f); + SDL_SetWindowTitle(render_window, title.c_str()); + last_time = current_time; + } +} diff --git a/src/lime_sdl/emu_window/emu_window_sdl2.h b/src/lime_sdl/emu_window/emu_window_sdl2.h new file mode 100644 index 000000000..28f86f81a --- /dev/null +++ b/src/lime_sdl/emu_window/emu_window_sdl2.h @@ -0,0 +1,91 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_types.h" +#include "core/frontend/emu_window.h" + +union SDL_Event; +struct SDL_Window; + +namespace Core { +class System; +} + +class EmuWindow_SDL2 : public Frontend::EmuWindow { +public: + explicit EmuWindow_SDL2(Core::System& system_, bool is_secondary); + ~EmuWindow_SDL2(); + + /// Initializes SDL2 + static void InitializeSDL2(); + + /// Presents the most recent frame from the video backend + virtual void Present() {} + + /// Polls window events + void PollEvents() override; + + /// Whether the window is still open, and a close request hasn't yet been sent + bool IsOpen() const; + + /// Close the window. + void RequestClose(); + +protected: + /// Gets the ID of the window an event originated from. + u32 GetEventWindowId(const SDL_Event& event) const; + + /// Called by PollEvents when a key is pressed or released. + void OnKeyEvent(int key, u8 state); + + /// Called by PollEvents when the mouse moves. + void OnMouseMotion(s32 x, s32 y); + + /// Called by PollEvents when a mouse button is pressed or released + void OnMouseButton(u32 button, u8 state, s32 x, s32 y); + + /// Translates pixel position (0..1) to pixel positions + std::pair TouchToPixelPos(float touch_x, float touch_y) const; + + /// Called by PollEvents when a finger starts touching the touchscreen + void OnFingerDown(float x, float y); + + /// Called by PollEvents when a finger moves while touching the touchscreen + void OnFingerMotion(float x, float y); + + /// Called by PollEvents when a finger stops touching the touchscreen + void OnFingerUp(); + + /// Called by PollEvents when any event that may cause the window to be resized occurs + void OnResize(); + + /// Called when user passes the fullscreen parameter flag + void Fullscreen(); + + /// Called when a configuration change affects the minimal size of the window + void OnMinimalClientAreaChangeRequest(std::pair minimal_size) override; + + /// Called when polling to update framerate + void UpdateFramerateCounter(); + + /// Is the window still open? + bool is_open = true; + + /// Internal SDL2 render window + SDL_Window* render_window; + + /// Internal SDL2 window ID + u32 render_window_id{}; + + /// Fake hidden window for the core context + SDL_Window* dummy_window; + + /// Keeps track of how often to update the title bar during gameplay + u32 last_time = 0; + + Core::System& system; +}; diff --git a/src/lime_sdl/emu_window/emu_window_sdl2_gl.cpp b/src/lime_sdl/emu_window/emu_window_sdl2_gl.cpp new file mode 100644 index 000000000..3cbc2de6c --- /dev/null +++ b/src/lime_sdl/emu_window/emu_window_sdl2_gl.cpp @@ -0,0 +1,167 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#define SDL_MAIN_HANDLED +#include +#include +#include "common/scm_rev.h" +#include "common/settings.h" +#include "core/core.h" +#include "lime_sdl/emu_window/emu_window_sdl2_gl.h" +#include "video_core/gpu.h" +#include "video_core/renderer_base.h" + +class SDLGLContext : public Frontend::GraphicsContext { +public: + using SDL_GLContext = void*; + + SDLGLContext() { + window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, + SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); + context = SDL_GL_CreateContext(window); + } + + ~SDLGLContext() override { + SDL_GL_DeleteContext(context); + SDL_DestroyWindow(window); + } + + void MakeCurrent() override { + SDL_GL_MakeCurrent(window, context); + } + + void DoneCurrent() override { + SDL_GL_MakeCurrent(window, nullptr); + } + +private: + SDL_Window* window; + SDL_GLContext context; +}; + +static SDL_Window* CreateGLWindow(const std::string& window_title, bool gles) { + if (gles) { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + } else { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + } + return SDL_CreateWindow(window_title.c_str(), + SDL_WINDOWPOS_UNDEFINED, // x position + SDL_WINDOWPOS_UNDEFINED, // y position + Core::kScreenTopWidth, + Core::kScreenTopHeight + Core::kScreenBottomHeight, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); +} + +EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, bool is_secondary) + : EmuWindow_SDL2{system_, is_secondary} { + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); + // Enable context sharing for the shared context + SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); + // Enable vsync + SDL_GL_SetSwapInterval(1); + // Enable debug context + if (Settings::values.renderer_debug) { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); + } + + std::string window_title = fmt::format("Lime3DS {} | {}-{}", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc); + + // First, try to create a context with the requested type. + render_window = CreateGLWindow(window_title, Settings::values.use_gles.GetValue()); + if (render_window == nullptr) { + // On failure, fall back to context with flipped type. + render_window = CreateGLWindow(window_title, !Settings::values.use_gles.GetValue()); + if (render_window == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError()); + exit(1); + } + } + + strict_context_required = std::strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0; + + dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, + SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); + + if (fullscreen) { + Fullscreen(); + } + + window_context = SDL_GL_CreateContext(render_window); + core_context = CreateSharedContext(); + last_saved_context = nullptr; + + if (window_context == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError()); + exit(1); + } + if (core_context == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError()); + exit(1); + } + + render_window_id = SDL_GetWindowID(render_window); + + int profile_mask = 0; + SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profile_mask); + auto gl_load_func = + profile_mask == SDL_GL_CONTEXT_PROFILE_ES ? gladLoadGLES2Loader : gladLoadGLLoader; + + if (!gl_load_func(static_cast(SDL_GL_GetProcAddress))) { + LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError()); + exit(1); + } + + OnResize(); + OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); + SDL_PumpEvents(); +} + +EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() { + core_context.reset(); + SDL_DestroyWindow(render_window); + SDL_GL_DeleteContext(window_context); +} + +std::unique_ptr EmuWindow_SDL2_GL::CreateSharedContext() const { + return std::make_unique(); +} + +void EmuWindow_SDL2_GL::MakeCurrent() { + core_context->MakeCurrent(); +} + +void EmuWindow_SDL2_GL::DoneCurrent() { + core_context->DoneCurrent(); +} + +void EmuWindow_SDL2_GL::SaveContext() { + last_saved_context = SDL_GL_GetCurrentContext(); +} + +void EmuWindow_SDL2_GL::RestoreContext() { + SDL_GL_MakeCurrent(render_window, last_saved_context); +} + +void EmuWindow_SDL2_GL::Present() { + SDL_GL_MakeCurrent(render_window, window_context); + SDL_GL_SetSwapInterval(1); + while (IsOpen()) { + system.GPU().Renderer().TryPresent(100, is_secondary); + SDL_GL_SwapWindow(render_window); + } + SDL_GL_MakeCurrent(render_window, nullptr); +} diff --git a/src/lime_sdl/emu_window/emu_window_sdl2_gl.h b/src/lime_sdl/emu_window/emu_window_sdl2_gl.h new file mode 100644 index 000000000..919d464b9 --- /dev/null +++ b/src/lime_sdl/emu_window/emu_window_sdl2_gl.h @@ -0,0 +1,39 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "lime_sdl/emu_window/emu_window_sdl2.h" + +struct SDL_Window; + +namespace Core { +class System; +} + +class EmuWindow_SDL2_GL : public EmuWindow_SDL2 { +public: + explicit EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, bool is_secondary); + ~EmuWindow_SDL2_GL(); + + void Present() override; + std::unique_ptr CreateSharedContext() const override; + void MakeCurrent() override; + void DoneCurrent() override; + void SaveContext() override; + void RestoreContext() override; + +private: + using SDL_GLContext = void*; + + /// The OpenGL context associated with the window + SDL_GLContext window_context; + + /// Used by SaveContext and RestoreContext + SDL_GLContext last_saved_context; + + /// The OpenGL context associated with the core + std::unique_ptr core_context; +}; diff --git a/src/lime_sdl/emu_window/emu_window_sdl2_sw.cpp b/src/lime_sdl/emu_window/emu_window_sdl2_sw.cpp new file mode 100644 index 000000000..351a5541d --- /dev/null +++ b/src/lime_sdl/emu_window/emu_window_sdl2_sw.cpp @@ -0,0 +1,108 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#define SDL_MAIN_HANDLED +#include +#include +#include "common/scm_rev.h" +#include "common/settings.h" +#include "core/core.h" +#include "core/frontend/emu_window.h" +#include "lime_sdl/emu_window/emu_window_sdl2_sw.h" +#include "video_core/gpu.h" +#include "video_core/renderer_software/renderer_software.h" + +class DummyContext : public Frontend::GraphicsContext {}; + +EmuWindow_SDL2_SW::EmuWindow_SDL2_SW(Core::System& system_, bool fullscreen, bool is_secondary) + : EmuWindow_SDL2{system_, is_secondary}, system{system_} { + std::string window_title = fmt::format("Lime3DS {} | {}-{}", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc); + render_window = + SDL_CreateWindow(window_title.c_str(), + SDL_WINDOWPOS_UNDEFINED, // x position + SDL_WINDOWPOS_UNDEFINED, // y position + Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight, + SDL_WINDOW_SHOWN); + + if (render_window == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError()); + exit(1); + } + + window_surface = SDL_GetWindowSurface(render_window); + renderer = SDL_CreateSoftwareRenderer(window_surface); + + if (renderer == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create SDL2 software renderer: {}", SDL_GetError()); + exit(1); + } + + if (fullscreen) { + Fullscreen(); + } + + render_window_id = SDL_GetWindowID(render_window); + + OnResize(); + OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); + SDL_PumpEvents(); +} + +EmuWindow_SDL2_SW::~EmuWindow_SDL2_SW() { + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(render_window); +} + +std::unique_ptr EmuWindow_SDL2_SW::CreateSharedContext() const { + return std::make_unique(); +} + +void EmuWindow_SDL2_SW::Present() { + const auto layout{Layout::DefaultFrameLayout( + Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight, false, false)}; + + using VideoCore::ScreenId; + + while (IsOpen()) { + SDL_SetRenderDrawColor(renderer, + static_cast(Settings::values.bg_red.GetValue() * 255), + static_cast(Settings::values.bg_green.GetValue() * 255), + static_cast(Settings::values.bg_blue.GetValue() * 255), 0xFF); + SDL_RenderClear(renderer); + + const auto draw_screen = [&](ScreenId screen_id) { + const auto dst_rect = + screen_id == ScreenId::TopLeft ? layout.top_screen : layout.bottom_screen; + SDL_Rect sdl_rect{static_cast(dst_rect.left), static_cast(dst_rect.top), + static_cast(dst_rect.GetWidth()), + static_cast(dst_rect.GetHeight())}; + SDL_Surface* screen = LoadFramebuffer(screen_id); + SDL_BlitSurface(screen, nullptr, window_surface, &sdl_rect); + SDL_FreeSurface(screen); + }; + + draw_screen(ScreenId::TopLeft); + draw_screen(ScreenId::Bottom); + + SDL_RenderPresent(renderer); + SDL_UpdateWindowSurface(render_window); + } +} + +SDL_Surface* EmuWindow_SDL2_SW::LoadFramebuffer(VideoCore::ScreenId screen_id) { + const auto& renderer = static_cast(system.GPU().Renderer()); + const auto& info = renderer.Screen(screen_id); + const int width = static_cast(info.width); + const int height = static_cast(info.height); + SDL_Surface* surface = + SDL_CreateRGBSurfaceWithFormat(0, width, height, 0, SDL_PIXELFORMAT_ABGR8888); + SDL_LockSurface(surface); + std::memcpy(surface->pixels, info.pixels.data(), info.pixels.size()); + SDL_UnlockSurface(surface); + return surface; +} diff --git a/src/lime_sdl/emu_window/emu_window_sdl2_sw.h b/src/lime_sdl/emu_window/emu_window_sdl2_sw.h new file mode 100644 index 000000000..60af4ffe9 --- /dev/null +++ b/src/lime_sdl/emu_window/emu_window_sdl2_sw.h @@ -0,0 +1,43 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "lime_sdl/emu_window/emu_window_sdl2.h" + +struct SDL_Renderer; +struct SDL_Surface; + +namespace VideoCore { +enum class ScreenId : u32; +} + +namespace Core { +class System; +} + +class EmuWindow_SDL2_SW : public EmuWindow_SDL2 { +public: + explicit EmuWindow_SDL2_SW(Core::System& system, bool fullscreen, bool is_secondary); + ~EmuWindow_SDL2_SW(); + + void Present() override; + std::unique_ptr CreateSharedContext() const override; + void MakeCurrent() override {} + void DoneCurrent() override {} + +private: + /// Loads a framebuffer to an SDL surface + SDL_Surface* LoadFramebuffer(VideoCore::ScreenId screen_id); + + /// The system class. + Core::System& system; + + /// The SDL software renderer + SDL_Renderer* renderer; + + /// The window surface + SDL_Surface* window_surface; +}; diff --git a/src/lime_sdl/emu_window/emu_window_sdl2_vk.cpp b/src/lime_sdl/emu_window/emu_window_sdl2_vk.cpp new file mode 100644 index 000000000..5837e3301 --- /dev/null +++ b/src/lime_sdl/emu_window/emu_window_sdl2_vk.cpp @@ -0,0 +1,90 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include "common/logging/log.h" +#include "common/scm_rev.h" +#include "core/frontend/emu_window.h" +#include "lime_sdl/emu_window/emu_window_sdl2_vk.h" + +class DummyContext : public Frontend::GraphicsContext {}; + +EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen, bool is_secondary) + : EmuWindow_SDL2{system, is_secondary} { + const std::string window_title = fmt::format("Lime3DS {} | {}-{}", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc); + render_window = + SDL_CreateWindow(window_title.c_str(), + SDL_WINDOWPOS_UNDEFINED, // x position + SDL_WINDOWPOS_UNDEFINED, // y position + Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight, + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + SDL_SysWMinfo wm; + SDL_VERSION(&wm.version); + if (SDL_GetWindowWMInfo(render_window, &wm) == SDL_FALSE) { + LOG_CRITICAL(Frontend, "Failed to get information from the window manager"); + std::exit(EXIT_FAILURE); + } + + if (fullscreen) { + Fullscreen(); + SDL_ShowCursor(false); + } + + switch (wm.subsystem) { +#ifdef SDL_VIDEO_DRIVER_WINDOWS + case SDL_SYSWM_TYPE::SDL_SYSWM_WINDOWS: + window_info.type = Frontend::WindowSystemType::Windows; + window_info.render_surface = reinterpret_cast(wm.info.win.window); + break; +#endif +#ifdef SDL_VIDEO_DRIVER_X11 + case SDL_SYSWM_TYPE::SDL_SYSWM_X11: + window_info.type = Frontend::WindowSystemType::X11; + window_info.display_connection = wm.info.x11.display; + window_info.render_surface = reinterpret_cast(wm.info.x11.window); + break; +#endif +#ifdef SDL_VIDEO_DRIVER_WAYLAND + case SDL_SYSWM_TYPE::SDL_SYSWM_WAYLAND: + window_info.type = Frontend::WindowSystemType::Wayland; + window_info.display_connection = wm.info.wl.display; + window_info.render_surface = wm.info.wl.surface; + break; +#endif +#ifdef SDL_VIDEO_DRIVER_COCOA + case SDL_SYSWM_TYPE::SDL_SYSWM_COCOA: + window_info.type = Frontend::WindowSystemType::MacOS; + window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(render_window)); + break; +#endif +#ifdef SDL_VIDEO_DRIVER_ANDROID + case SDL_SYSWM_TYPE::SDL_SYSWM_ANDROID: + window_info.type = Frontend::WindowSystemType::Android; + window_info.render_surface = reinterpret_cast(wm.info.android.window); + break; +#endif + default: + LOG_CRITICAL(Frontend, "Window manager subsystem {} not implemented", wm.subsystem); + std::exit(EXIT_FAILURE); + break; + } + + render_window_id = SDL_GetWindowID(render_window); + + OnResize(); + OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); + SDL_PumpEvents(); +} + +EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() = default; + +std::unique_ptr EmuWindow_SDL2_VK::CreateSharedContext() const { + return std::make_unique(); +} diff --git a/src/lime_sdl/emu_window/emu_window_sdl2_vk.h b/src/lime_sdl/emu_window/emu_window_sdl2_vk.h new file mode 100644 index 000000000..69ad6745e --- /dev/null +++ b/src/lime_sdl/emu_window/emu_window_sdl2_vk.h @@ -0,0 +1,24 @@ +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "lime_sdl/emu_window/emu_window_sdl2.h" + +namespace Frontend { +class GraphicsContext; +} + +namespace Core { +class System; +} + +class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { +public: + explicit EmuWindow_SDL2_VK(Core::System& system_, bool fullscreen, bool is_secondary); + ~EmuWindow_SDL2_VK() override; + + std::unique_ptr CreateSharedContext() const override; +}; diff --git a/src/lime_sdl/lime_sdl.cpp b/src/lime_sdl/lime_sdl.cpp new file mode 100644 index 000000000..cdd3a5978 --- /dev/null +++ b/src/lime_sdl/lime_sdl.cpp @@ -0,0 +1,528 @@ +// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include + +// This needs to be included before getopt.h because the latter #defines symbols used by it +#include "common/microprofile.h" + +#include "lime_sdl/config.h" +#include "lime_sdl/emu_window/emu_window_sdl2.h" +#ifdef ENABLE_OPENGL +#include "lime_sdl/emu_window/emu_window_sdl2_gl.h" +#endif +#ifdef ENABLE_SOFTWARE_RENDERER +#include "lime_sdl/emu_window/emu_window_sdl2_sw.h" +#endif +#ifdef ENABLE_VULKAN +#include "lime_sdl/emu_window/emu_window_sdl2_vk.h" +#endif +#include "SDL_messagebox.h" +#include "common/common_paths.h" +#include "common/detached_tasks.h" +#include "common/file_util.h" +#include "common/logging/backend.h" +#include "common/logging/log.h" +#include "common/scm_rev.h" +#include "common/scope_exit.h" +#include "common/settings.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/dumping/backend.h" +#include "core/dumping/ffmpeg_backend.h" +#include "core/frontend/applets/default_applets.h" +#include "core/frontend/framebuffer_layout.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/cfg/cfg.h" +#include "core/movie.h" +#include "input_common/main.h" +#include "lime/common_strings.h" +#include "network/network.h" +#include "video_core/gpu.h" +#include "video_core/renderer_base.h" + +#ifdef __unix__ +#include "common/linux/gamemode.h" +#endif + +#undef _UNICODE +#include +#ifndef _MSC_VER +#include +#endif + +#ifdef _WIN32 +// windows.h needs to be included before shellapi.h +#include + +#include +#endif + +static void ShowCommandOutput(std::string title, std::string message) { +#ifdef _WIN32 + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, title.c_str(), message.c_str(), NULL); +#else + std::cout << message << std::endl; +#endif +} + +static void PrintHelp(const char* argv0) { + ShowCommandOutput("Help", fmt::format(Common::help_string, argv0)); +} + +static void OnStateChanged(const Network::RoomMember::State& state) { + switch (state) { + case Network::RoomMember::State::Idle: + LOG_DEBUG(Network, "Network is idle"); + break; + case Network::RoomMember::State::Joining: + LOG_DEBUG(Network, "Connection sequence to room started"); + break; + case Network::RoomMember::State::Joined: + LOG_DEBUG(Network, "Successfully joined to the room"); + break; + case Network::RoomMember::State::Moderator: + LOG_DEBUG(Network, "Successfully joined the room as a moderator"); + break; + default: + break; + } +} + +static void OnNetworkError(const Network::RoomMember::Error& error) { + switch (error) { + case Network::RoomMember::Error::LostConnection: + LOG_DEBUG(Network, "Lost connection to the room"); + break; + case Network::RoomMember::Error::CouldNotConnect: + LOG_ERROR(Network, "Error: Could not connect"); + std::exit(1); + break; + case Network::RoomMember::Error::NameCollision: + LOG_ERROR( + Network, + "You tried to use the same nickname as another user that is connected to the Room"); + std::exit(1); + break; + case Network::RoomMember::Error::MacCollision: + LOG_ERROR(Network, "You tried to use the same MAC-Address as another user that is " + "connected to the Room"); + std::exit(1); + break; + case Network::RoomMember::Error::ConsoleIdCollision: + LOG_ERROR(Network, "Your Console ID conflicted with someone else in the Room"); + std::exit(1); + break; + case Network::RoomMember::Error::WrongPassword: + LOG_ERROR(Network, "Room replied with: Wrong password"); + std::exit(1); + break; + case Network::RoomMember::Error::WrongVersion: + LOG_ERROR(Network, + "You are using a different version than the room you are trying to connect to"); + std::exit(1); + break; + case Network::RoomMember::Error::RoomIsFull: + LOG_ERROR(Network, "The room is full"); + std::exit(1); + break; + case Network::RoomMember::Error::HostKicked: + LOG_ERROR(Network, "You have been kicked by the host"); + break; + case Network::RoomMember::Error::HostBanned: + LOG_ERROR(Network, "You have been banned by the host"); + break; + default: + LOG_ERROR(Network, "Unknown network error: {}", error); + break; + } +} + +static void OnMessageReceived(const Network::ChatEntry& msg) { + std::cout << std::endl << msg.nickname << ": " << msg.message << std::endl << std::endl; +} + +static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) { + std::string message; + switch (msg.type) { + case Network::IdMemberJoin: + message = fmt::format("{} has joined", msg.nickname); + break; + case Network::IdMemberLeave: + message = fmt::format("{} has left", msg.nickname); + break; + case Network::IdMemberKicked: + message = fmt::format("{} has been kicked", msg.nickname); + break; + case Network::IdMemberBanned: + message = fmt::format("{} has been banned", msg.nickname); + break; + case Network::IdAddressUnbanned: + message = fmt::format("{} has been unbanned", msg.nickname); + break; + } + if (!message.empty()) + std::cout << std::endl << "* " << message << std::endl << std::endl; +} + +/// Application entry point +void LaunchSdlFrontend(int argc, char** argv) { + Common::Log::Initialize(); + Common::Log::SetColorConsoleBackendEnabled(true); + Common::Log::Start(); + Common::DetachedTasks detached_tasks; + SdlConfig config; + int option_index = 0; + bool use_gdbstub = Settings::values.use_gdbstub.GetValue(); + u32 gdb_port = static_cast(Settings::values.gdbstub_port.GetValue()); + std::string movie_record; + std::string movie_record_author; + std::string movie_play; + std::string dump_video; + + char* endarg; +#ifdef _WIN32 + int argc_w; + auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w); + + if (argv_w == nullptr) { + LOG_CRITICAL(Frontend, "Failed to get command line arguments"); + exit(-1); + } +#endif + std::string filepath; + + bool use_multiplayer = false; + bool fullscreen = false; + std::string nickname{}; + std::string password{}; + std::string address{}; + u16 port = Network::DefaultRoomPort; + + static struct option long_options[] = { + {"dump-video", required_argument, 0, 'd'}, + {"fullscreen", no_argument, 0, 'f'}, + {"gdbport", required_argument, 0, 'g'}, + {"help", no_argument, 0, 'h'}, + {"install", required_argument, 0, 'i'}, + {"movie-play", required_argument, 0, 'p'}, + {"movie-record", required_argument, 0, 'r'}, + {"movie-record-author", required_argument, 0, 'a'}, + {"multiplayer", required_argument, 0, 'm'}, + {"version", no_argument, 0, 'v'}, + {"windowed", no_argument, 0, 'w'}, + {0, 0, 0, 0}, + }; + + while (optind < argc) { + int arg = getopt_long(argc, argv, "fg:hi:p:r:m:nvw", long_options, &option_index); + if (arg != -1) { + switch (static_cast(arg)) { + case 'd': + dump_video = optarg; + break; + case 'f': + fullscreen = true; + LOG_INFO(Frontend, "Starting in fullscreen mode..."); + break; + case 'g': + errno = 0; + gdb_port = strtoul(optarg, &endarg, 0); + use_gdbstub = true; + if (endarg == optarg) + errno = EINVAL; + if (errno != 0) { + perror("--gdbport"); + exit(1); + } + break; + case 'h': + PrintHelp(argv[0]); + exit(0); + case 'i': { + const auto cia_progress = [](std::size_t written, std::size_t total) { + LOG_INFO(Frontend, "{:02d}%", (written * 100 / total)); + }; + if (Service::AM::InstallCIA(std::string(optarg), cia_progress) != + Service::AM::InstallStatus::Success) + errno = EINVAL; + if (errno != 0) + exit(1); + break; + } + case 'p': + movie_play = optarg; + break; + case 'r': + movie_record = optarg; + break; + case 'a': + movie_record_author = optarg; + break; + case 'm': { + use_multiplayer = true; + const std::string str_arg(optarg); + // regex to check if the format is nickname:password@ip:port + // with optional :password + const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$"); + if (!std::regex_match(str_arg, re)) { + std::cout << "Wrong format for option --multiplayer\n"; + PrintHelp(argv[0]); + exit(0); + } + + std::smatch match; + std::regex_search(str_arg, match, re); + ASSERT(match.size() == 5); + nickname = match[1]; + password = match[2]; + address = match[3]; + if (!match[4].str().empty()) + port = std::stoi(match[4]); + std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$"); + if (!std::regex_match(nickname, nickname_re)) { + std::cout + << "Nickname is not valid. Must be 4 to 20 alphanumeric characters.\n"; + exit(0); + } + if (address.empty()) { + std::cout << "Address to room must not be empty.\n"; + exit(0); + } + break; + } + case 'v': + const std::string version_string = + std::string("Lime3DS ") + Common::g_scm_branch + " " + Common::g_scm_desc; + ShowCommandOutput("Version", version_string); + exit(0); + } + } else { +#ifdef _WIN32 + filepath = Common::UTF16ToUTF8(argv_w[optind]); +#else + filepath = argv[optind]; +#endif + optind++; + } + } + +#ifdef _WIN32 + LocalFree(argv_w); +#endif + + MicroProfileOnThreadCreate("EmuThread"); + SCOPE_EXIT({ MicroProfileShutdown(); }); + + if (filepath.empty()) { + LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); + exit(-1); + } + + if (!movie_record.empty() && !movie_play.empty()) { + LOG_CRITICAL(Frontend, "Cannot both play and record a movie"); + exit(-1); + } + + auto& system = Core::System::GetInstance(); + auto& movie = system.Movie(); + + if (!movie_record.empty()) { + movie.PrepareForRecording(); + } + if (!movie_play.empty()) { + movie.PrepareForPlayback(movie_play); + } + + // Apply the command line arguments + Settings::values.gdbstub_port = gdb_port; + Settings::values.use_gdbstub = use_gdbstub; + system.ApplySettings(); + + // Register frontend applets + Frontend::RegisterDefaultApplets(system); + + EmuWindow_SDL2::InitializeSDL2(); + + const auto create_emu_window = [&](bool fullscreen, + bool is_secondary) -> std::unique_ptr { + const auto graphics_api = Settings::values.graphics_api.GetValue(); + switch (graphics_api) { +#ifdef ENABLE_OPENGL + case Settings::GraphicsAPI::OpenGL: + return std::make_unique(system, fullscreen, is_secondary); +#endif +#ifdef ENABLE_VULKAN + case Settings::GraphicsAPI::Vulkan: + return std::make_unique(system, fullscreen, is_secondary); +#endif +#ifdef ENABLE_SOFTWARE_RENDERER + case Settings::GraphicsAPI::Software: + return std::make_unique(system, fullscreen, is_secondary); +#endif + default: + LOG_CRITICAL( + Frontend, + "Unknown or unsupported graphics API {}, falling back to available default", + graphics_api); +#ifdef ENABLE_OPENGL + return std::make_unique(system, fullscreen, is_secondary); +#elif ENABLE_VULKAN + return std::make_unique(system, fullscreen, is_secondary); +#elif ENABLE_SOFTWARE_RENDERER + return std::make_unique(system, fullscreen, is_secondary); +#else + // TODO: Add a null renderer backend for this, perhaps. +#error "At least one renderer must be enabled." +#endif + } + }; + + const auto emu_window{create_emu_window(fullscreen, false)}; + const bool use_secondary_window{ + Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows && + Settings::values.graphics_api.GetValue() != Settings::GraphicsAPI::Software}; + const auto secondary_window = use_secondary_window ? create_emu_window(false, true) : nullptr; + + const auto scope = emu_window->Acquire(); + + LOG_INFO(Frontend, "Lime3DS Version: {} | {}-{}", Common::g_build_fullname, + Common::g_scm_branch, Common::g_scm_desc); + Settings::LogSettings(); + + const Core::System::ResultStatus load_result{ + system.Load(*emu_window, filepath, secondary_window.get())}; + + switch (load_result) { + case Core::System::ResultStatus::ErrorGetLoader: + LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath); + exit(-1); + case Core::System::ResultStatus::ErrorLoader: + LOG_CRITICAL(Frontend, "Failed to load ROM!"); + exit(-1); + case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: + LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " + "being used with Lime3DS. \n\n For more information on dumping and " + "decrypting games, please refer to: " + "https://web.archive.org/web/20240304210021/https://citra-emu.org/" + "wiki/dumping-game-cartridges/"); + exit(-1); + case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: + LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); + exit(-1); + case Core::System::ResultStatus::ErrorNotInitialized: + LOG_CRITICAL(Frontend, "CPUCore not initialized"); + exit(-1); + case Core::System::ResultStatus::ErrorSystemMode: + LOG_CRITICAL(Frontend, "Failed to determine system mode!"); + exit(-1); + case Core::System::ResultStatus::Success: + break; // Expected case + default: + LOG_ERROR(Frontend, "Error while loading ROM: {}", system.GetStatusDetails()); + break; + } + + if (use_multiplayer) { + if (auto member = Network::GetRoomMember().lock()) { + member->BindOnChatMessageRecieved(OnMessageReceived); + member->BindOnStatusMessageReceived(OnStatusMessageReceived); + member->BindOnStateChanged(OnStateChanged); + member->BindOnError(OnNetworkError); + LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port, + nickname); + member->Join(nickname, Service::CFG::GetConsoleIdHash(system), address.c_str(), port, 0, + Network::NoPreferredMac, password); + } else { + LOG_ERROR(Network, "Could not access RoomMember"); + exit(0); + } + } + + if (!movie_play.empty()) { + auto metadata = movie.GetMovieMetadata(movie_play); + LOG_INFO(Movie, "Author: {}", metadata.author); + LOG_INFO(Movie, "Rerecord count: {}", metadata.rerecord_count); + LOG_INFO(Movie, "Input count: {}", metadata.input_count); + movie.StartPlayback(movie_play); + } + if (!movie_record.empty()) { + movie.StartRecording(movie_record, movie_record_author); + } + if (!dump_video.empty() && DynamicLibrary::FFmpeg::LoadFFmpeg()) { + auto& renderer = system.GPU().Renderer(); + const auto layout{ + Layout::FrameLayoutFromResolutionScale(renderer.GetResolutionScaleFactor())}; + auto dumper = std::make_shared(renderer); + if (dumper->StartDumping(dump_video, layout)) { + system.RegisterVideoDumper(dumper); + } + } + +#ifdef __unix__ + Common::Linux::StartGamemode(); +#endif + + std::thread main_render_thread([&emu_window] { emu_window->Present(); }); + std::thread secondary_render_thread([&secondary_window] { + if (secondary_window) { + secondary_window->Present(); + } + }); + + std::atomic_bool stop_run; + system.GPU().Renderer().Rasterizer()->LoadDiskResources( + stop_run, [](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { + LOG_DEBUG(Frontend, "Loading stage {} progress {} {}", static_cast(stage), value, + total); + }); + + const auto secondary_is_open = [&secondary_window] { + // if the secondary window isn't created, it shouldn't affect the main loop + return secondary_window ? secondary_window->IsOpen() : true; + }; + while (emu_window->IsOpen() && secondary_is_open()) { + const auto result = system.RunLoop(); + + switch (result) { + case Core::System::ResultStatus::ShutdownRequested: + emu_window->RequestClose(); + break; + case Core::System::ResultStatus::Success: + break; + default: + LOG_ERROR(Frontend, "Error in main run loop: {}", result, system.GetStatusDetails()); + break; + } + } + emu_window->RequestClose(); + if (secondary_window) { + secondary_window->RequestClose(); + } + main_render_thread.join(); + secondary_render_thread.join(); + + movie.Shutdown(); + + auto video_dumper = system.GetVideoDumper(); + if (video_dumper && video_dumper->IsDumping()) { + video_dumper->StopDumping(); + } + + Network::Shutdown(); + InputCommon::Shutdown(); + + system.Shutdown(); + +#ifdef __unix__ + Common::Linux::StopGamemode(); +#endif + + detached_tasks.WaitForAllTasks(); + exit(0); +} diff --git a/src/lime_sdl/lime_sdl.h b/src/lime_sdl/lime_sdl.h new file mode 100644 index 000000000..ba4b90ce0 --- /dev/null +++ b/src/lime_sdl/lime_sdl.h @@ -0,0 +1,7 @@ +// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +void LaunchSdlFrontend(int argc, char** argv); diff --git a/src/lime_sdl/precompiled_headers.h b/src/lime_sdl/precompiled_headers.h new file mode 100644 index 000000000..ffbb5e177 --- /dev/null +++ b/src/lime_sdl/precompiled_headers.h @@ -0,0 +1,7 @@ +// Copyright 2022 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_precompiled_headers.h" diff --git a/src/lime_sdl/resource.h b/src/lime_sdl/resource.h new file mode 100644 index 000000000..df8e459e4 --- /dev/null +++ b/src/lime_sdl/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by pcafe.rc +// +#define IDI_ICON3 103 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 105 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif