From 4e377dde5a95ba0d7e32eb6796f39517725302c6 Mon Sep 17 00:00:00 2001 From: Zephyron Date: Fri, 26 Sep 2025 19:42:30 +1000 Subject: [PATCH] feat: Implement Qt6 migration with aqtinstall 3.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migrate from Qt5 to Qt6.7.3 using aqtinstall v3.3.0 - Add comprehensive Qt6 API compatibility updates - Implement responsive UI with High DPI scaling support - Add MSVC runtime library configuration for consistency - Update touch/mouse event handling for Qt6 APIs - Fix locale handling (countryToString → territoryToString) - Update string size methods (count() → size()) - Remove deprecated Qt5 high DPI attributes - Add new CopyCitronQt6Deps.cmake for Qt6 dependency management - Update CMake configuration for Qt6-only approach - Add aqt_config.ini with mirror configuration for reliable downloads Signed-off-by: Zephyron --- CMakeLists.txt | 292 +++++++----------- CMakeModules/CopyCitronQt6Deps.cmake | 61 ++++ CMakeModules/DownloadExternals.cmake | 177 +++++++++++ CMakeModules/aqt_config.ini | 31 ++ src/citron/CMakeLists.txt | 24 +- src/citron/bootmanager.cpp | 8 +- .../configure_touch_from_button.cpp | 10 +- src/citron/configuration/configure_ui.cpp | 2 +- src/citron/game_list.cpp | 4 +- src/citron/util/performance_overlay.cpp | 4 +- src/citron/util/vram_overlay.cpp | 4 +- 11 files changed, 409 insertions(+), 208 deletions(-) create mode 100644 CMakeModules/CopyCitronQt6Deps.cmake create mode 100644 CMakeModules/aqt_config.ini diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e469c386..8a6cf4a95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,11 +13,24 @@ include(DownloadExternals) include(CMakeDependentOption) include(CTest) +# Disable Warnings as Errors for MSVC +if (MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3 /WX-") + # Add specific warning suppressions for Qt6 compatibility + add_compile_options(/wd4996) # Suppress deprecated function warnings + add_compile_options(/wd4267) # Suppress size_t conversion warnings +endif() + +# Check if SDL2::SDL2 target exists; if not, create an alias +if (TARGET SDL2::SDL2-static) + add_library(SDL2::SDL2 ALIAS SDL2::SDL2-static) +elseif (TARGET SDL2::SDL2-shared) + add_library(SDL2::SDL2 ALIAS SDL2::SDL2-shared) +endif() + # Set bundled sdl2/qt as dependent options. -# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON option(ENABLE_SDL2 "Enable the SDL2 frontend" ON) CMAKE_DEPENDENT_OPTION(CITRON_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON "ENABLE_SDL2;MSVC" OFF) -# On Linux system SDL2 is likely to be lacking HIDAPI support which have drawbacks but is needed for SDL motion CMAKE_DEPENDENT_OPTION(CITRON_USE_EXTERNAL_SDL2 "Compile external SDL2" ON "ENABLE_SDL2;NOT MSVC" OFF) cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "NOT ANDROID" OFF) @@ -25,8 +38,6 @@ cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "NOT ANDROID" option(ENABLE_OPENGL "Enable OpenGL" ON) mark_as_advanced(FORCE ENABLE_OPENGL) option(ENABLE_QT "Enable the Qt frontend" ON) -option(ENABLE_QT6 "Allow usage of Qt6 to be attempted" OFF) -set(QT6_LOCATION "" CACHE PATH "Additional Location to search for Qt6 libraries like C:/Qt/6.3.1/msvc2019_64/") option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF) CMAKE_DEPENDENT_OPTION(CITRON_USE_BUNDLED_QT "Download bundled Qt binaries" "${MSVC}" "ENABLE_QT" OFF) @@ -77,12 +88,6 @@ CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead set(DEFAULT_ENABLE_OPENSSL ON) if (ANDROID OR WIN32 OR APPLE) - # - Windows defaults to the Schannel backend. - # - macOS defaults to the SecureTransport backend. - # - Android currently has no SSL backend as the NDK doesn't include any SSL - # library; a proper 'native' backend would have to go through Java. - # But you can force builds for those platforms to use OpenSSL if you have - # your own copy of it. set(DEFAULT_ENABLE_OPENSSL OFF) endif() option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL}) @@ -367,170 +372,75 @@ if (UNIX AND NOT APPLE) find_package(gamemode 1.7 MODULE) endif() -# Please consider this as a stub -if(ENABLE_QT6 AND Qt6_LOCATION) - list(APPEND CMAKE_PREFIX_PATH "${Qt6_LOCATION}") +add_subdirectory(externals) + + +if (ENABLE_QT) + if (NOT USE_SYSTEM_QT) + download_qt(6.7.3) + endif() + + find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent) + + if (UNIX AND NOT APPLE) + find_package(Qt6 REQUIRED COMPONENTS DBus) + endif() + + if (ENABLE_QT_TRANSLATION) + find_package(Qt6 REQUIRED COMPONENTS LinguistTools) + endif() + + if (NOT DEFINED QT_TARGET_PATH) + # Try to find Qt6 installation path + if (EXISTS "${CMAKE_BINARY_DIR}/externals/qt/6.8.3/msvc2019_64") + set(QT_TARGET_PATH "${CMAKE_BINARY_DIR}/externals/qt/6.8.3/msvc2019_64") + elseif (EXISTS "${CMAKE_BINARY_DIR}/externals/qt/6.7.3/msvc2019_64") + set(QT_TARGET_PATH "${CMAKE_BINARY_DIR}/externals/qt/6.7.3/msvc2019_64") + else() + get_target_property(qtcore_path Qt6::Core LOCATION_Release) + string(FIND "${qtcore_path}" "/bin/" qtcore_path_bin_pos REVERSE) + string(FIND "${qtcore_path}" "/lib/" qtcore_path_lib_pos REVERSE) + if (qtcore_path_bin_pos GREATER qtcore_path_lib_pos) + string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_bin_pos} QT_TARGET_PATH) + else() + string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_lib_pos} QT_TARGET_PATH) + endif() + endif() + endif() + + if (NOT DEFINED QT_HOST_PATH) + set(QT_HOST_PATH "${QT_TARGET_PATH}") + endif() + + # Add Qt path to CMAKE_PREFIX_PATH to help find Qt6 + if (QT_TARGET_PATH) + list(APPEND CMAKE_PREFIX_PATH "${QT_TARGET_PATH}") + endif() + + message(STATUS "Using target Qt at ${QT_TARGET_PATH}") + message(STATUS "Using host Qt at ${QT_HOST_PATH}") endif() function(set_citron_qt_components) - # Best practice is to ask for all components at once, so they are from the same version - set(CITRON_QT_COMPONENTS2 Core Widgets Concurrent Network) - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") - list(APPEND CITRON_QT_COMPONENTS2 DBus) - endif() - if (CITRON_USE_QT_MULTIMEDIA) - list(APPEND CITRON_QT_COMPONENTS2 Multimedia) +# Best practice is to ask for all components at once, so they are from the same version +set(CITRON_QT_COMPONENTS2 Core Widgets Concurrent) +if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") +list(APPEND CITRON_QT_COMPONENTS2 DBus) +endif() +if (CITRON_USE_QT_MULTIMEDIA) +list(APPEND CITRON_QT_COMPONENTS2 Multimedia) endif() if (CITRON_USE_QT_WEB_ENGINE) - list(APPEND CITRON_QT_COMPONENTS2 WebEngineCore WebEngineWidgets) + list(APPEND CITRON_QT_COMPONENTS2 WebEngineCore WebEngineWidgets) endif() if (ENABLE_QT_TRANSLATION) - list(APPEND CITRON_QT_COMPONENTS2 LinguistTools) + list(APPEND CITRON_QT_COMPONENTS2 LinguistTools) endif() if (USE_DISCORD_PRESENCE) - list(APPEND CITRON_QT_COMPONENTS2 Network) + list(APPEND CITRON_QT_COMPONENTS2 Network) endif() set(CITRON_QT_COMPONENTS ${CITRON_QT_COMPONENTS2} PARENT_SCOPE) -endfunction(set_citron_qt_components) - -# Qt5 requires that we find components, so it doesn't fit our pretty little find package function -if(ENABLE_QT) - set(QT_VERSION 5.15) - # These are used to specify minimum versions - set(QT5_VERSION 5.15) - set(QT6_VERSION 6.3.1) - - set_citron_qt_components() - if (ENABLE_QT6) - find_package(Qt6 ${QT6_VERSION} COMPONENTS ${CITRON_QT_COMPONENTS}) - endif() - if (Qt6_FOUND) - message(STATUS "citron/CMakeLists.txt: Qt6Widgets_VERSION ${Qt6Widgets_VERSION}, setting QT_VERSION") - set(QT_VERSION ${Qt6Widgets_VERSION}) - set(QT_MAJOR_VERSION 6) - # Qt6 sets cxx_std_17 and we need to undo that - set_target_properties(Qt6::Platform PROPERTIES INTERFACE_COMPILE_FEATURES "") - else() - message(STATUS "citron/CMakeLists.txt: Qt6 not found/not selected, trying for Qt5") - # When Qt6 partially found, need this set to use Qt5 when not specifying version - set(QT_DEFAULT_MAJOR_VERSION 5) - set(QT_MAJOR_VERSION 5) - - set(CITRON_USE_QT_MULTIMEDIA ON) - # Check for system Qt on Linux, fallback to bundled Qt - if (UNIX AND NOT APPLE) - if (NOT CITRON_USE_BUNDLED_QT) - find_package(Qt5 ${QT5_VERSION} COMPONENTS Widgets DBus Multimedia) - endif() - if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND (NOT Qt5_FOUND OR CITRON_USE_BUNDLED_QT)) - # Check for dependencies, then enable bundled Qt download - - # Check that the system GLIBCXX version is compatible - find_program(OBJDUMP objdump) - if (NOT OBJDUMP) - message(FATAL_ERROR "Required program `objdump` not found.") - endif() - find_library(LIBSTDCXX libstdc++.so.6) - execute_process( - COMMAND - ${OBJDUMP} -T ${LIBSTDCXX} - COMMAND - grep GLIBCXX_3.4.28 - COMMAND - sed "s/[0-9a-f]*.* //" - COMMAND - sed "s/ .*//" - COMMAND - sort -u - OUTPUT_VARIABLE - GLIBCXX_MET - ) - if (NOT GLIBCXX_MET) - message(FATAL_ERROR "Qt too old or not found, and bundled Qt package is not \ - compatible with this system. Either install Qt ${QT_VERSION}, or provide the path \ - to Qt by setting the variable Qt5_ROOT.") - endif() - - # Check for headers - find_package(PkgConfig REQUIRED) - pkg_check_modules(QT_DEP_GLU QUIET glu>=9.0.0) - if (NOT QT_DEP_GLU_FOUND) - message(FATAL_ERROR "Qt bundled package dependency `glu` not found. \ - Perhaps `libglu1-mesa-dev` needs to be installed?") - endif() - pkg_check_modules(QT_DEP_MESA QUIET dri>=20.0.8) - if (NOT QT_DEP_MESA_FOUND) - message(FATAL_ERROR "Qt bundled package dependency `dri` not found. \ - Perhaps `mesa-common-dev` needs to be installed?") - endif() - - # Check for X libraries - set(BUNDLED_QT_REQUIREMENTS - libxcb-icccm.so.4 - libxcb-image.so.0 - libxcb-keysyms.so.1 - libxcb-randr.so.0 - libxcb-render-util.so.0 - libxcb-render.so.0 - libxcb-shape.so.0 - libxcb-shm.so.0 - libxcb-sync.so.1 - libxcb-xfixes.so.0 - libxcb-xinerama.so.0 - libxcb-xkb.so.1 - libxcb.so.1 - libxkbcommon-x11.so.0 - libxkbcommon.so.0 - ) - set(UNRESOLVED_QT_DEPS "") - foreach (REQUIREMENT ${BUNDLED_QT_REQUIREMENTS}) - find_library(BUNDLED_QT_${REQUIREMENT} ${REQUIREMENT}) - if (NOT BUNDLED_QT_${REQUIREMENT}) - set(UNRESOLVED_QT_DEPS ${UNRESOLVED_QT_DEPS} ${REQUIREMENT}) - endif() - unset(BUNDLED_QT_${REQUIREMENT}) - endforeach() - unset(BUNDLED_QT_REQUIREMENTS) - - if (NOT "${UNRESOLVED_QT_DEPS}" STREQUAL "") - message(FATAL_ERROR "Bundled Qt package missing required dependencies: ${UNRESOLVED_QT_DEPS}") - endif() - - set(CITRON_USE_BUNDLED_QT ON CACHE BOOL "Download bundled Qt" FORCE) - endif() - if (CITRON_USE_BUNDLED_QT) - # Binary package currently does not support Qt webengine, so make sure it's disabled - set(CITRON_USE_QT_WEB_ENGINE OFF CACHE BOOL "Use Qt Webengine" FORCE) - endif() - endif() - - set(CITRON_QT_NO_CMAKE_SYSTEM_PATH) - - if(CITRON_USE_BUNDLED_QT) - if ((MSVC_VERSION GREATER_EQUAL 1920 AND ARCHITECTURE_x86_64)) - set(QT_BUILD qt-5.15.2-msvc2019_64) - elseif ((${CMAKE_SYSTEM_NAME} STREQUAL "Linux") AND NOT MINGW AND ARCHITECTURE_x86_64) - set(QT_BUILD qt5_5_15_2) - else() - message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable CITRON_USE_BUNDLED_QT and provide your own.") - endif() - - if (DEFINED QT_BUILD) - download_bundled_external("qt/" ${QT_BUILD} QT_PREFIX) - endif() - - set(QT_PREFIX_HINT HINTS "${QT_PREFIX}") - - set(CITRON_QT_NO_CMAKE_SYSTEM_PATH "NO_CMAKE_SYSTEM_PATH") - # Binary package for Qt5 has Qt Multimedia - set(CITRON_USE_QT_MULTIMEDIA ON CACHE BOOL "Use Qt Multimedia" FORCE) - endif() - - set_citron_qt_components() - find_package(Qt5 ${QT5_VERSION} COMPONENTS ${CITRON_QT_COMPONENTS} ${QT_PREFIX_HINT} ${CITRON_QT_NO_CMAKE_SYSTEM_PATH}) - endif() - -endif() + endfunction(set_citron_qt_components) # find SDL2 exports a bunch of variables that are needed, so its easier to do this outside of the citron_find_package if (ENABLE_SDL2) @@ -561,21 +471,29 @@ if (ENABLE_SDL2) endif() endif() -# List of all FFmpeg components required -set(FFmpeg_COMPONENTS - avcodec - avfilter - avutil - swscale) + # List of all FFmpeg components required + set(FFmpeg_COMPONENTS + avcodec + avfilter + avutil + swscale) -if (UNIX AND NOT APPLE AND NOT ANDROID) - find_package(PkgConfig REQUIRED) - pkg_check_modules(LIBVA libva) -endif() -if (NOT CITRON_USE_BUNDLED_FFMPEG) - # Use system installed FFmpeg - find_package(FFmpeg 4.3 REQUIRED QUIET COMPONENTS ${FFmpeg_COMPONENTS}) -endif() + if (UNIX AND NOT APPLE AND NOT ANDROID) + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBVA libva) + endif() + if (NOT CITRON_USE_BUNDLED_FFMPEG) + # Use system installed FFmpeg + find_package(FFmpeg 4.3 REQUIRED QUIET COMPONENTS ${FFmpeg_COMPONENTS}) + endif() + +if(ENABLE_QT) + set_citron_qt_components() + find_package(Qt6 REQUIRED COMPONENTS ${CITRON_QT_COMPONENTS}) + set(QT_MAJOR_VERSION 6) + # Qt6 sets cxx_std_17 and we need to undo that + set_target_properties(Qt6::Platform PROPERTIES INTERFACE_COMPILE_FEATURES "") + endif() if (WIN32 AND CITRON_CRASH_DUMPS) set(BREAKPAD_VER "breakpad-c89f9dd") @@ -738,7 +656,25 @@ if (CITRON_USE_FASTER_LD AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") endif() endif() -add_subdirectory(externals) +# Set runtime library to MD/MDd for all configurations +if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>DLL") + + # Force all projects (including external dependencies) to use the same runtime + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD") + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MD") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MDd") + + # Add this to ensure Cubeb uses the same runtime + add_compile_options( + $<$:/MDd> + $<$:/MD> + $<$:/MD> + $<$:/MD> + ) +endif() + add_subdirectory(src) # Set citron project or citron-cmd project as default StartUp Project in Visual Studio depending on whether QT is enabled or not diff --git a/CMakeModules/CopyCitronQt6Deps.cmake b/CMakeModules/CopyCitronQt6Deps.cmake new file mode 100644 index 000000000..3e5748f10 --- /dev/null +++ b/CMakeModules/CopyCitronQt6Deps.cmake @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: 2025 citron Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +function(copy_citron_Qt6_deps target_dir) + include(WindowsCopyFiles) + if (MSVC) + set(DLL_DEST "$/") + set(Qt6_DLL_DIR "${Qt6_DIR}/../../../bin") + else() + set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/") + set(Qt6_DLL_DIR "${Qt6_DIR}/../../../lib/") + endif() + set(Qt6_PLATFORMS_DIR "${Qt6_DIR}/../../../plugins/platforms/") + set(Qt6_STYLES_DIR "${Qt6_DIR}/../../../plugins/styles/") + set(Qt6_IMAGEFORMATS_DIR "${Qt6_DIR}/../../../plugins/imageformats/") + set(Qt6_RESOURCES_DIR "${Qt6_DIR}/../../../resources/") + set(PLATFORMS ${DLL_DEST}plugins/platforms/) + set(STYLES ${DLL_DEST}plugins/styles/) + set(IMAGEFORMATS ${DLL_DEST}plugins/imageformats/) + + if (MSVC) + windows_copy_files(${target_dir} ${Qt6_DLL_DIR} ${DLL_DEST} + Qt6Core$<$:d>.* + Qt6Gui$<$:d>.* + Qt6Widgets$<$:d>.* + Qt6Network$<$:d>.* + ) + if (CITRON_USE_QT_MULTIMEDIA) + windows_copy_files(${target_dir} ${Qt6_DLL_DIR} ${DLL_DEST} + Qt6Multimedia$<$:d>.* + ) + endif() + if (CITRON_USE_QT_WEB_ENGINE) + windows_copy_files(${target_dir} ${Qt6_DLL_DIR} ${DLL_DEST} + Qt6WebEngineCore$<$:d>.* + Qt6WebEngineWidgets$<$:d>.* + QtWebEngineProcess$<$:d>.* + ) + windows_copy_files(${target_dir} ${Qt6_RESOURCES_DIR} ${DLL_DEST} + icudtl.dat + qtwebengine_devtools_resources.pak + qtwebengine_resources.pak + qtwebengine_resources_100p.pak + qtwebengine_resources_200p.pak + ) + endif() + windows_copy_files(citron ${Qt6_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$:d>.*) + windows_copy_files(citron ${Qt6_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$:d>.*) + windows_copy_files(citron ${Qt6_IMAGEFORMATS_DIR} ${IMAGEFORMATS} + qjpeg$<$:d>.* + qgif$<$:d>.* + ) + else() + # Update for non-MSVC platforms if needed + endif() + + # Create an empty qt.conf file + add_custom_command(TARGET citron POST_BUILD + COMMAND ${CMAKE_COMMAND} -E touch ${DLL_DEST}qt.conf + ) +endfunction(copy_citron_Qt6_deps) diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake index 47070cae6..95a9b828d 100644 --- a/CMakeModules/DownloadExternals.cmake +++ b/CMakeModules/DownloadExternals.cmake @@ -6,6 +6,7 @@ # Params: # remote_path: path to the file to download, relative to the remote repository root # prefix_var: name of a variable which will be set with the path to the extracted contents +set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR}) function(download_bundled_external remote_path lib_name prefix_var) set(package_base_url "https://git.citron-emu.org/citron/") @@ -58,3 +59,179 @@ function(download_moltenvk_external platform version) list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${platform}") set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) endfunction() + +# Determine installation parameters for OS, architecture, and compiler +function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out) + if (WIN32) + set(host "windows") + set(type "desktop") + + if (NOT tool) + if (MINGW) + set(arch "win64_mingw") + set(arch_path "mingw_64") + elseif (MSVC) + if ("arm64" IN_LIST ARCHITECTURE) + set(arch_path "msvc2019_arm64") + elseif ("x86_64" IN_LIST ARCHITECTURE) + set(arch_path "msvc2019_64") + else() + message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.") + endif() + set(arch "win64_${arch_path}") + + if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64") + set(host_arch_path "msvc2019_64") + elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64") + set(host_arch_path "msvc2019_64") + endif() + set(host_arch "win64_${host_arch_path}") + else() + message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.") + endif() + endif() + elseif (APPLE) + set(host "mac") + set(type "desktop") + set(arch "clang_64") + set(arch_path "macos") + else() + set(host "linux") + set(type "desktop") + set(arch "gcc_64") + set(arch_path "linux") + endif() + + set(${host_out} "${host}" PARENT_SCOPE) + set(${type_out} "${type}" PARENT_SCOPE) + set(${arch_out} "${arch}" PARENT_SCOPE) + set(${arch_path_out} "${arch_path}" PARENT_SCOPE) + if (DEFINED host_type) + set(${host_type_out} "${host_type}" PARENT_SCOPE) + else() + set(${host_type_out} "${type}" PARENT_SCOPE) + endif() + if (DEFINED host_arch) + set(${host_arch_out} "${host_arch}" PARENT_SCOPE) + else() + set(${host_arch_out} "${arch}" PARENT_SCOPE) + endif() + if (DEFINED host_arch_path) + set(${host_arch_path_out} "${host_arch_path}" PARENT_SCOPE) + else() + set(${host_arch_path_out} "${arch_path}" PARENT_SCOPE) + endif() +endfunction() + +# Download Qt binaries for a specific configuration. +function(download_qt_configuration prefix_out target host type arch arch_path base_path) + if (target MATCHES "tools_.*") + set(tool ON) + else() + set(tool OFF) + endif() + + set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini") + if (tool) + set(prefix "${base_path}/Tools") + set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target}) + else() + set(prefix "${base_path}/${target}/${arch_path}") + set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} + -m qtmultimedia --archives qttranslations qttools qtsvg qtbase) + endif() + + if (NOT EXISTS "${prefix}") + message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}") + set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.3.0") + if (WIN32) + set(aqt_path "${base_path}/aqt.exe") + if (NOT EXISTS "${aqt_path}") + file(DOWNLOAD + ${AQT_PREBUILD_BASE_URL}/aqt.exe + ${aqt_path} SHOW_PROGRESS) + endif() + execute_process(COMMAND ${aqt_path} ${install_args} + WORKING_DIRECTORY ${base_path} + RESULT_VARIABLE aqt_result) + if (NOT aqt_result EQUAL 0) + message(WARNING "aqt install failed, trying without multimedia module") + set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini" install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} --archives qttranslations qttools qtsvg qtbase) + execute_process(COMMAND ${aqt_path} ${install_args} + WORKING_DIRECTORY ${base_path}) + endif() + elseif (APPLE) + set(aqt_path "${base_path}/aqt-macos") + if (NOT EXISTS "${aqt_path}") + file(DOWNLOAD + ${AQT_PREBUILD_BASE_URL}/aqt-macos + ${aqt_path} SHOW_PROGRESS) + endif() + execute_process(COMMAND chmod +x ${aqt_path}) + execute_process(COMMAND ${aqt_path} ${install_args} + WORKING_DIRECTORY ${base_path}) + else() + set(aqt_install_path "${base_path}/aqt") + file(MAKE_DIRECTORY "${aqt_install_path}") + + execute_process(COMMAND python3 -m pip install --target=${aqt_install_path} aqtinstall + WORKING_DIRECTORY ${base_path}) + execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args} + WORKING_DIRECTORY ${base_path}) + endif() + + message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}") + endif() + + set(${prefix_out} "${prefix}" PARENT_SCOPE) +endfunction() + +# This function downloads Qt using aqt. +# The path of the downloaded content will be added to the CMAKE_PREFIX_PATH. +# QT_TARGET_PATH is set to the Qt for the compile target platform. +# QT_HOST_PATH is set to a host-compatible Qt, for running tools. +# Params: +# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool. +function(download_qt target) + determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path) + + get_external_prefix(qt base_path) + file(MAKE_DIRECTORY "${base_path}") + + download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}") + if (DEFINED host_arch_path AND NOT "${host_arch_path}" STREQUAL "${arch_path}") + download_qt_configuration(host_prefix "${target}" "${host}" "${host_type}" "${host_arch}" "${host_arch_path}" "${base_path}") + else() + set(host_prefix "${prefix}") + endif() + + set(QT_TARGET_PATH "${prefix}" CACHE STRING "") + set(QT_HOST_PATH "${host_prefix}" CACHE STRING "") + + list(APPEND CMAKE_PREFIX_PATH "${prefix}") + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) +endfunction() + +function(download_moltenvk) +set(MOLTENVK_PLATFORM "macOS") + +set(MOLTENVK_DIR "${CMAKE_BINARY_DIR}/externals/MoltenVK") +set(MOLTENVK_TAR "${CMAKE_BINARY_DIR}/externals/MoltenVK.tar") +if (NOT EXISTS ${MOLTENVK_DIR}) +if (NOT EXISTS ${MOLTENVK_TAR}) + file(DOWNLOAD https://github.com/KhronosGroup/MoltenVK/releases/download/v1.2.10-rc2/MoltenVK-all.tar + ${MOLTENVK_TAR} SHOW_PROGRESS) +endif() + +execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${MOLTENVK_TAR}" + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") +endif() + +# Add the MoltenVK library path to the prefix so find_library can locate it. +list(APPEND CMAKE_PREFIX_PATH "${MOLTENVK_DIR}/MoltenVK/dylib/${MOLTENVK_PLATFORM}") +set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) +endfunction() + +function(get_external_prefix lib_name prefix_var) + set(${prefix_var} "${CMAKE_BINARY_DIR}/externals/${lib_name}" PARENT_SCOPE) +endfunction() diff --git a/CMakeModules/aqt_config.ini b/CMakeModules/aqt_config.ini new file mode 100644 index 000000000..d2f2c9f59 --- /dev/null +++ b/CMakeModules/aqt_config.ini @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2025 citron Emulator Project +# SPDX-License-Identifier: GPL-3.0-or-later + +[aqt] +concurrency: 4 + +[mirrors] +trusted_mirrors: + https://download.qt.io +blacklist: + https://qt.mirror.constant.com + https://mirrors.ocf.berkeley.edu + https://mirrors.ustc.edu.cn + https://mirrors.tuna.tsinghua.edu.cn + https://mirrors.geekpie.club + https://mirrors-wan.geekpie.club + https://mirrors.sjtug.sjtu.edu.cn +fallbacks: + https://qtproject.mirror.liquidtelecom.com/ + https://mirrors.aliyun.com/qt/ + https://ftp.jaist.ac.jp/pub/qtproject/ + https://ftp.yz.yamagata-u.ac.jp/pub/qtproject/ + https://qt-mirror.dannhauer.de/ + https://ftp.fau.de/qtproject/ + https://mirror.netcologne.de/qtproject/ + https://mirrors.dotsrc.org/qtproject/ + https://www.nic.funet.fi/pub/mirrors/download.qt-project.org/ + https://master.qt.io/ + https://mirrors.ukfast.co.uk/sites/qt.io/ + https://ftp2.nluug.nl/languages/qt/ + https://ftp1.nluug.nl/languages/qt/ diff --git a/src/citron/CMakeLists.txt b/src/citron/CMakeLists.txt index f22d4412a..21e3ffbb2 100644 --- a/src/citron/CMakeLists.txt +++ b/src/citron/CMakeLists.txt @@ -389,11 +389,7 @@ if (APPLE) elseif(WIN32) # compile as a win32 gui application instead of a console application - if (QT_VERSION VERSION_GREATER_EQUAL 6) - target_link_libraries(citron PRIVATE Qt6::EntryPointPrivate) - else() - target_link_libraries(citron PRIVATE Qt5::WinMain) - endif() + target_link_libraries(citron PRIVATE Qt6::EntryPointPrivate) if(MSVC) target_link_libraries(citron PRIVATE version.lib) set_target_properties(citron PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") @@ -426,7 +422,7 @@ else() target_link_libraries(citron PRIVATE common core input_common frontend_common network video_core) endif() -target_link_libraries(citron PRIVATE Boost::headers glad Qt${QT_MAJOR_VERSION}::Widgets Qt${QT_MAJOR_VERSION}::Network) +target_link_libraries(citron PRIVATE Boost::headers glad Qt6::Widgets) target_link_libraries(citron PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) # Add libarchive for updater functionality @@ -437,10 +433,10 @@ endif() target_link_libraries(citron PRIVATE Vulkan::Headers) if (NOT WIN32) - target_include_directories(citron PRIVATE ${Qt${QT_MAJOR_VERSION}Gui_PRIVATE_INCLUDE_DIRS}) + target_include_directories(citron PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS}) endif() if (UNIX AND NOT APPLE) - target_link_libraries(citron PRIVATE Qt${QT_MAJOR_VERSION}::DBus) + target_link_libraries(citron PRIVATE Qt6::DBus) endif() target_compile_definitions(citron PRIVATE @@ -471,7 +467,7 @@ if (USE_DISCORD_PRESENCE) discord_impl.cpp discord_impl.h ) - target_link_libraries(citron PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt${QT_MAJOR_VERSION}::Network) + target_link_libraries(citron PRIVATE DiscordRPC::discord-rpc httplib::httplib Qt6::Network) target_compile_definitions(citron PRIVATE -DUSE_DISCORD_PRESENCE) endif() @@ -480,12 +476,12 @@ if (ENABLE_WEB_SERVICE) endif() if (CITRON_USE_QT_MULTIMEDIA) - target_link_libraries(citron PRIVATE Qt${QT_MAJOR_VERSION}::Multimedia) + target_link_libraries(citron PRIVATE Qt6::Multimedia) target_compile_definitions(citron PRIVATE -DCITRON_USE_QT_MULTIMEDIA) endif () if (CITRON_USE_QT_WEB_ENGINE) - target_link_libraries(citron PRIVATE Qt${QT_MAJOR_VERSION}::WebEngineCore Qt${QT_MAJOR_VERSION}::WebEngineWidgets) + target_link_libraries(citron PRIVATE Qt6::WebEngineCore Qt6::WebEngineWidgets) target_compile_definitions(citron PRIVATE -DCITRON_USE_QT_WEB_ENGINE) endif () @@ -498,9 +494,9 @@ if (WIN32 AND QT_VERSION VERSION_GREATER_EQUAL 6) add_custom_command(TARGET citron POST_BUILD COMMAND ${WINDEPLOYQT_EXECUTABLE} "${CITRON_EXE_DIR}/citron.exe" --dir "${CITRON_EXE_DIR}" --libdir "${CITRON_EXE_DIR}" --plugindir "${CITRON_EXE_DIR}/plugins" --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --no-translations --verbose 0) endif() -if (CITRON_USE_BUNDLED_QT AND QT_VERSION VERSION_LESS 6) - include(CopyCitronQt5Deps) - copy_citron_Qt5_deps(citron) +if (CITRON_USE_BUNDLED_QT) + include(CopyCitronQt6Deps) + copy_citron_Qt6_deps(citron) endif() if (ENABLE_SDL2) diff --git a/src/citron/bootmanager.cpp b/src/citron/bootmanager.cpp index 20e2b414d..9f68f85bc 100644 --- a/src/citron/bootmanager.cpp +++ b/src/citron/bootmanager.cpp @@ -738,19 +738,19 @@ void GRenderWindow::wheelEvent(QWheelEvent* event) { } void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { - QList touch_points = event->touchPoints(); + QList touch_points = event->points(); for (const auto& touch_point : touch_points) { - const auto [x, y] = ScaleTouch(touch_point.pos()); + const auto [x, y] = ScaleTouch(touch_point.position()); const auto [touch_x, touch_y] = MapToTouchScreen(x, y); input_subsystem->GetTouchScreen()->TouchPressed(touch_x, touch_y, touch_point.id()); } } void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) { - QList touch_points = event->touchPoints(); + QList touch_points = event->points(); input_subsystem->GetTouchScreen()->ClearActiveFlag(); for (const auto& touch_point : touch_points) { - const auto [x, y] = ScaleTouch(touch_point.pos()); + const auto [x, y] = ScaleTouch(touch_point.position()); const auto [touch_x, touch_y] = MapToTouchScreen(x, y); input_subsystem->GetTouchScreen()->TouchMoved(touch_x, touch_y, touch_point.id()); } diff --git a/src/citron/configuration/configure_touch_from_button.cpp b/src/citron/configuration/configure_touch_from_button.cpp index 90cfdee08..50511e08b 100644 --- a/src/citron/configuration/configure_touch_from_button.cpp +++ b/src/citron/configuration/configure_touch_from_button.cpp @@ -505,7 +505,7 @@ void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) { if (!coord_label) { return; } - const auto pos = MapToDeviceCoords(event->x(), event->y()); + const auto pos = MapToDeviceCoords(event->position().x(), event->position().y()); if (pos) { coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y())); } else { @@ -523,7 +523,7 @@ void TouchScreenPreview::mousePressEvent(QMouseEvent* event) { if (event->button() != Qt::MouseButton::LeftButton) { return; } - const auto pos = MapToDeviceCoords(event->x(), event->y()); + const auto pos = MapToDeviceCoords(event->position().x(), event->position().y()); if (pos) { emit DotAdded(*pos); } @@ -539,7 +539,7 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) { emit DotSelected(obj->property(PropId).toInt()); drag_state.dot = qobject_cast(obj); - drag_state.start_pos = mouse_event->globalPos(); + drag_state.start_pos = mouse_event->globalPosition().toPoint(); return true; } case QEvent::Type::MouseMove: { @@ -549,13 +549,13 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) { const auto mouse_event = static_cast(event); if (!drag_state.active) { drag_state.active = - (mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >= + (mouse_event->globalPosition().toPoint() - drag_state.start_pos).manhattanLength() >= QApplication::startDragDistance(); if (!drag_state.active) { break; } } - auto current_pos = mapFromGlobal(mouse_event->globalPos()); + auto current_pos = mapFromGlobal(mouse_event->globalPosition().toPoint()); current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(), contentsMargins().left() + contentsRect().width() - 1)); current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(), diff --git a/src/citron/configuration/configure_ui.cpp b/src/citron/configuration/configure_ui.cpp index 2876e048a..a77efffdf 100644 --- a/src/citron/configuration/configure_ui.cpp +++ b/src/citron/configuration/configure_ui.cpp @@ -259,7 +259,7 @@ void ConfigureUi::InitializeLanguageComboBox() { locale.truncate(locale.lastIndexOf(QLatin1Char{'.'})); locale.remove(0, locale.lastIndexOf(QLatin1Char{'/'}) + 1); const QString lang = QLocale::languageToString(QLocale(locale).language()); - const QString country = QLocale::countryToString(QLocale(locale).country()); + const QString country = QLocale::territoryToString(QLocale(locale).territory()); ui->language_combobox->addItem(QStringLiteral("%1 (%2)").arg(lang, country), locale); } diff --git a/src/citron/game_list.cpp b/src/citron/game_list.cpp index f71e11adb..6360ac512 100644 --- a/src/citron/game_list.cpp +++ b/src/citron/game_list.cpp @@ -247,7 +247,7 @@ void GameList::FilterGridView(const QString& filter_text) { const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + file_title; should_show = ContainsAllWords(file_name, filter_text) || - (file_program_id.count() == 16 && file_program_id.contains(filter_text)); + (file_program_id.size() == 16 && file_program_id.contains(filter_text)); } if (should_show) { @@ -331,7 +331,7 @@ void GameList::FilterTreeView(const QString& filter_text) { file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} + file_title; if (ContainsAllWords(file_name, filter_text) || - (file_program_id.count() == 16 && file_program_id.contains(filter_text))) { + (file_program_id.size() == 16 && file_program_id.contains(filter_text))) { tree_view->setRowHidden(j, folder_index, false); ++result_count; } else { diff --git a/src/citron/util/performance_overlay.cpp b/src/citron/util/performance_overlay.cpp index 5093464fa..f82678392 100644 --- a/src/citron/util/performance_overlay.cpp +++ b/src/citron/util/performance_overlay.cpp @@ -108,7 +108,7 @@ void PerformanceOverlay::resizeEvent(QResizeEvent* event) { void PerformanceOverlay::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { is_dragging = true; - drag_start_pos = event->globalPos(); + drag_start_pos = event->globalPosition().toPoint(); widget_start_pos = this->pos(); setCursor(Qt::ClosedHandCursor); } @@ -117,7 +117,7 @@ void PerformanceOverlay::mousePressEvent(QMouseEvent* event) { void PerformanceOverlay::mouseMoveEvent(QMouseEvent* event) { if (is_dragging) { - QPoint delta = event->globalPos() - drag_start_pos; + QPoint delta = event->globalPosition().toPoint() - drag_start_pos; move(widget_start_pos + delta); } QWidget::mouseMoveEvent(event); diff --git a/src/citron/util/vram_overlay.cpp b/src/citron/util/vram_overlay.cpp index 908b467bc..fa1a43745 100644 --- a/src/citron/util/vram_overlay.cpp +++ b/src/citron/util/vram_overlay.cpp @@ -260,14 +260,14 @@ void VramOverlay::resizeEvent(QResizeEvent* event) { void VramOverlay::mousePressEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { is_dragging = true; - drag_start_pos = event->globalPos(); + drag_start_pos = event->globalPosition().toPoint(); widget_start_pos = pos(); } } void VramOverlay::mouseMoveEvent(QMouseEvent* event) { if (is_dragging) { - QPoint delta = event->globalPos() - drag_start_pos; + QPoint delta = event->globalPosition().toPoint() - drag_start_pos; move(widget_start_pos + delta); has_been_moved = true; }