mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-22 11:40:06 +00:00
qt: Migrate to Qt6. (#6418)
This commit is contained in:
parent
70335a7f4d
commit
2273df4d70
@ -17,12 +17,5 @@ rm -rf ./AppDir/usr/local
|
|||||||
#Circumvent missing LibFuse in Docker, by extracting the AppImage
|
#Circumvent missing LibFuse in Docker, by extracting the AppImage
|
||||||
export APPIMAGE_EXTRACT_AND_RUN=1
|
export APPIMAGE_EXTRACT_AND_RUN=1
|
||||||
|
|
||||||
#Copy External Libraries
|
|
||||||
mkdir -p ./AppDir/usr/plugins/platformthemes
|
|
||||||
mkdir -p ./AppDir/usr/plugins/styles
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/qt5/plugins/platformthemes/libqt5ct.so ./AppDir/usr/plugins/platformthemes
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/qt5/plugins/platformthemes/libqgtk3.so ./AppDir/usr/plugins/platformthemes
|
|
||||||
cp /usr/lib/x86_64-linux-gnu/qt5/plugins/styles/libqt5ct-style.so ./AppDir/usr/plugins/styles
|
|
||||||
|
|
||||||
#Build AppImage
|
#Build AppImage
|
||||||
/linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage
|
QMAKE=/usr/lib/qt6/bin/qmake DEPLOY_PLATFORM_THEMES=1 /linuxdeploy-x86_64.AppImage --appdir AppDir --plugin qt --output appimage
|
||||||
|
@ -17,15 +17,14 @@ echo 'Prepare binaries...'
|
|||||||
cd ..
|
cd ..
|
||||||
mkdir package
|
mkdir package
|
||||||
|
|
||||||
QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins/platforms/'
|
QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt6/plugins/platforms/'
|
||||||
find build/ -name "citra*.exe" -exec cp {} 'package' \;
|
find build/ -name "citra*.exe" -exec cp {} 'package' \;
|
||||||
|
|
||||||
# copy Qt plugins
|
# copy Qt plugins
|
||||||
mkdir package/platforms
|
mkdir package/platforms
|
||||||
cp "${QT_PLATFORM_DLL_PATH}/qwindows.dll" package/platforms/
|
cp "${QT_PLATFORM_DLL_PATH}/qwindows.dll" package/platforms/
|
||||||
cp -rv "${QT_PLATFORM_DLL_PATH}/../mediaservice/" package/
|
cp -rv "${QT_PLATFORM_DLL_PATH}/../multimedia/" package/
|
||||||
cp -rv "${QT_PLATFORM_DLL_PATH}/../imageformats/" package/
|
cp -rv "${QT_PLATFORM_DLL_PATH}/../imageformats/" package/
|
||||||
cp -rv "${QT_PLATFORM_DLL_PATH}/../styles/" package/
|
cp -rv "${QT_PLATFORM_DLL_PATH}/../styles/" package/
|
||||||
rm -f package/mediaservice/*d.dll
|
|
||||||
|
|
||||||
python3 .ci/linux-mingw/scan_dll.py package/*.exe package/imageformats/*.dll "package/"
|
python3 .ci/linux-mingw/scan_dll.py package/*.exe package/imageformats/*.dll "package/"
|
||||||
|
@ -1,13 +1,3 @@
|
|||||||
#!/bin/sh -ex
|
#!/bin/sh -ex
|
||||||
|
|
||||||
brew install ccache ninja || true
|
brew install ccache ninja || true
|
||||||
|
|
||||||
export QT_VER=5.15.8
|
|
||||||
|
|
||||||
mkdir tmp
|
|
||||||
cd tmp/
|
|
||||||
|
|
||||||
# install Qt
|
|
||||||
wget https://github.com/citra-emu/ext-macos-bin/raw/main/qt/qt-${QT_VER}.7z
|
|
||||||
7z x qt-${QT_VER}.7z
|
|
||||||
sudo cp -rv $(pwd)/qt-${QT_VER}/* /usr/local/
|
|
||||||
|
@ -6,7 +6,7 @@ cmake .. \
|
|||||||
-G Ninja \
|
-G Ninja \
|
||||||
-DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MSVCCache.cmake" \
|
-DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MSVCCache.cmake" \
|
||||||
-DCITRA_USE_CCACHE=ON \
|
-DCITRA_USE_CCACHE=ON \
|
||||||
-DENABLE_QT_TRANSLATION=OFF \
|
-DENABLE_QT_TRANSLATION=ON \
|
||||||
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
|
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
|
||||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
||||||
-DUSE_DISCORD_PRESENCE=ON \
|
-DUSE_DISCORD_PRESENCE=ON \
|
||||||
|
@ -25,7 +25,7 @@ option(USE_SYSTEM_SDL2 "Use the system SDL2 lib (instead of the bundled one)" OF
|
|||||||
# Set bundled qt as dependent options.
|
# Set bundled qt as dependent options.
|
||||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||||
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
|
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC" OFF)
|
CMAKE_DEPENDENT_OPTION(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC OR APPLE" OFF)
|
||||||
|
|
||||||
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
@ -202,23 +202,23 @@ find_package(Threads REQUIRED)
|
|||||||
|
|
||||||
if (ENABLE_QT)
|
if (ENABLE_QT)
|
||||||
if (CITRA_USE_BUNDLED_QT)
|
if (CITRA_USE_BUNDLED_QT)
|
||||||
download_qt_external(5.15.2 QT_PREFIX)
|
download_qt_external(6.5.0 QT_PREFIX)
|
||||||
list(APPEND CMAKE_PREFIX_PATH ${QT_PREFIX})
|
list(APPEND CMAKE_PREFIX_PATH ${QT_PREFIX})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt5 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||||
|
|
||||||
if (UNIX AND NOT APPLE)
|
if (UNIX AND NOT APPLE)
|
||||||
find_package(Qt5 REQUIRED COMPONENTS DBus)
|
find_package(Qt6 REQUIRED COMPONENTS DBus)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_QT_TRANSLATION)
|
if (ENABLE_QT_TRANSLATION)
|
||||||
find_package(Qt5 REQUIRED COMPONENTS LinguistTools)
|
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT CITRA_USE_BUNDLED_QT)
|
if (NOT CITRA_USE_BUNDLED_QT)
|
||||||
# Make sure the Qt bin directory is in the prefix path for later consumers.
|
# Make sure the Qt bin directory is in the prefix path for later use, such as in post-build scripts.
|
||||||
get_target_property(qmake_executable Qt5::qmake IMPORTED_LOCATION)
|
get_target_property(qmake_executable Qt6::qmake IMPORTED_LOCATION)
|
||||||
get_filename_component(qt_bin_dir "${qmake_executable}" DIRECTORY)
|
get_filename_component(qt_bin_dir "${qmake_executable}" DIRECTORY)
|
||||||
list(APPEND CMAKE_PREFIX_PATH ${qt_bin_dir})
|
list(APPEND CMAKE_PREFIX_PATH ${qt_bin_dir})
|
||||||
endif()
|
endif()
|
||||||
@ -282,8 +282,6 @@ if (APPLE)
|
|||||||
find_library(AVFOUNDATION_LIBRARY AVFoundation)
|
find_library(AVFOUNDATION_LIBRARY AVFoundation)
|
||||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
||||||
elseif (WIN32)
|
elseif (WIN32)
|
||||||
# WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista)
|
|
||||||
add_definitions(-D_WIN32_WINNT=0x0601 -DWINVER=0x0601)
|
|
||||||
set(PLATFORM_LIBRARIES winmm ws2_32)
|
set(PLATFORM_LIBRARIES winmm ws2_32)
|
||||||
if (MINGW)
|
if (MINGW)
|
||||||
# PSAPI is the Process Status API
|
# PSAPI is the Process Status API
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
function(copy_citra_Qt5_deps target_dir)
|
|
||||||
include(WindowsCopyFiles)
|
|
||||||
set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
|
|
||||||
set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin")
|
|
||||||
set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
|
|
||||||
set(Qt5_MEDIASERVICE_DIR "${Qt5_DIR}/../../../plugins/mediaservice/")
|
|
||||||
set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
|
|
||||||
set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
|
|
||||||
set(PLATFORMS ${DLL_DEST}plugins/platforms/)
|
|
||||||
set(MEDIASERVICE ${DLL_DEST}plugins/mediaservice/)
|
|
||||||
set(STYLES ${DLL_DEST}plugins/styles/)
|
|
||||||
set(IMAGEFORMATS ${DLL_DEST}plugins/imageformats/)
|
|
||||||
windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST}
|
|
||||||
icudt*.dll
|
|
||||||
icuin*.dll
|
|
||||||
icuuc*.dll
|
|
||||||
Qt5Core$<$<CONFIG:Debug>:d>.*
|
|
||||||
Qt5Gui$<$<CONFIG:Debug>:d>.*
|
|
||||||
Qt5Widgets$<$<CONFIG:Debug>:d>.*
|
|
||||||
Qt5Concurrent$<$<CONFIG:Debug>:d>.*
|
|
||||||
Qt5Multimedia$<$<CONFIG:Debug>:d>.*
|
|
||||||
Qt5Network$<$<CONFIG:Debug>:d>.*
|
|
||||||
)
|
|
||||||
windows_copy_files(citra-qt ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
|
|
||||||
windows_copy_files(citra-qt ${Qt5_MEDIASERVICE_DIR} ${MEDIASERVICE}
|
|
||||||
dsengine$<$<CONFIG:Debug>:d>.*
|
|
||||||
wmfengine$<$<CONFIG:Debug>:d>.*
|
|
||||||
)
|
|
||||||
windows_copy_files(citra-qt ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
|
|
||||||
windows_copy_files(${target_dir} ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS}
|
|
||||||
qgif$<$<CONFIG:Debug>:d>.dll
|
|
||||||
qicns$<$<CONFIG:Debug>:d>.dll
|
|
||||||
qico$<$<CONFIG:Debug>:d>.dll
|
|
||||||
qjpeg$<$<CONFIG:Debug>:d>.dll
|
|
||||||
qsvg$<$<CONFIG:Debug>:d>.dll
|
|
||||||
qtga$<$<CONFIG:Debug>:d>.dll
|
|
||||||
qtiff$<$<CONFIG:Debug>:d>.dll
|
|
||||||
qwbmp$<$<CONFIG:Debug>:d>.dll
|
|
||||||
qwebp$<$<CONFIG:Debug>:d>.dll
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create an empty qt.conf file. Qt will detect that this file exists, and use the folder that its in as the root folder.
|
|
||||||
# This way it'll look for plugins in the root/plugins/ folder
|
|
||||||
add_custom_command(TARGET citra-qt POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E touch ${DLL_DEST}qt.conf
|
|
||||||
)
|
|
||||||
endfunction(copy_citra_Qt5_deps)
|
|
46
CMakeModules/CopyCitraQt6Deps.cmake
Normal file
46
CMakeModules/CopyCitraQt6Deps.cmake
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
function(copy_citra_Qt6_deps target_dir)
|
||||||
|
include(WindowsCopyFiles)
|
||||||
|
set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/")
|
||||||
|
set(Qt6_DLL_DIR "${Qt6_DIR}/../../../bin")
|
||||||
|
set(Qt6_PLATFORMS_DIR "${Qt6_DIR}/../../../plugins/platforms/")
|
||||||
|
set(Qt6_MULTIMEDIA_DIR "${Qt6_DIR}/../../../plugins/multimedia/")
|
||||||
|
set(Qt6_STYLES_DIR "${Qt6_DIR}/../../../plugins/styles/")
|
||||||
|
set(Qt6_IMAGEFORMATS_DIR "${Qt6_DIR}/../../../plugins/imageformats/")
|
||||||
|
set(PLATFORMS ${DLL_DEST}plugins/platforms/)
|
||||||
|
set(MULTIMEDIA ${DLL_DEST}plugins/multimedia/)
|
||||||
|
set(STYLES ${DLL_DEST}plugins/styles/)
|
||||||
|
set(IMAGEFORMATS ${DLL_DEST}plugins/imageformats/)
|
||||||
|
windows_copy_files(${target_dir} ${Qt6_DLL_DIR} ${DLL_DEST}
|
||||||
|
icudt*.dll
|
||||||
|
icuin*.dll
|
||||||
|
icuuc*.dll
|
||||||
|
Qt6Core$<$<CONFIG:Debug>:d>.*
|
||||||
|
Qt6Gui$<$<CONFIG:Debug>:d>.*
|
||||||
|
Qt6Widgets$<$<CONFIG:Debug>:d>.*
|
||||||
|
Qt6Concurrent$<$<CONFIG:Debug>:d>.*
|
||||||
|
Qt6Multimedia$<$<CONFIG:Debug>:d>.*
|
||||||
|
Qt6Network$<$<CONFIG:Debug>:d>.*
|
||||||
|
)
|
||||||
|
windows_copy_files(citra-qt ${Qt6_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
|
||||||
|
windows_copy_files(citra-qt ${Qt6_MULTIMEDIA_DIR} ${MULTIMEDIA}
|
||||||
|
windowsmediaplugin$<$<CONFIG:Debug>:d>.*
|
||||||
|
)
|
||||||
|
windows_copy_files(citra-qt ${Qt6_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
|
||||||
|
windows_copy_files(${target_dir} ${Qt6_IMAGEFORMATS_DIR} ${IMAGEFORMATS}
|
||||||
|
qgif$<$<CONFIG:Debug>:d>.dll
|
||||||
|
qicns$<$<CONFIG:Debug>:d>.dll
|
||||||
|
qico$<$<CONFIG:Debug>:d>.dll
|
||||||
|
qjpeg$<$<CONFIG:Debug>:d>.dll
|
||||||
|
qsvg$<$<CONFIG:Debug>:d>.dll
|
||||||
|
qtga$<$<CONFIG:Debug>:d>.dll
|
||||||
|
qtiff$<$<CONFIG:Debug>:d>.dll
|
||||||
|
qwbmp$<$<CONFIG:Debug>:d>.dll
|
||||||
|
qwebp$<$<CONFIG:Debug>:d>.dll
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create an empty qt.conf file. Qt will detect that this file exists, and use the folder that its in as the root folder.
|
||||||
|
# This way it'll look for plugins in the root/plugins/ folder
|
||||||
|
add_custom_command(TARGET citra-qt POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E touch ${DLL_DEST}qt.conf
|
||||||
|
)
|
||||||
|
endfunction(copy_citra_Qt6_deps)
|
@ -70,7 +70,7 @@ function(download_qt_external target prefix_var)
|
|||||||
set(install_args install-tool --outputdir ${base_path} ${host} desktop ${target})
|
set(install_args install-tool --outputdir ${base_path} ${host} desktop ${target})
|
||||||
else()
|
else()
|
||||||
set(prefix "${base_path}/${target}/${arch_path}")
|
set(prefix "${base_path}/${target}/${arch_path}")
|
||||||
set(install_args install-qt --outputdir ${base_path} ${host} desktop ${target} ${arch})
|
set(install_args install-qt --outputdir ${base_path} ${host} desktop ${target} ${arch} -m qtmultimedia)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT EXISTS "${prefix}")
|
if (NOT EXISTS "${prefix}")
|
||||||
|
@ -209,7 +209,7 @@ if (ENABLE_QT_TRANSLATION)
|
|||||||
# Update source TS file if enabled
|
# Update source TS file if enabled
|
||||||
if (GENERATE_QT_TRANSLATION)
|
if (GENERATE_QT_TRANSLATION)
|
||||||
get_target_property(SRCS citra-qt SOURCES)
|
get_target_property(SRCS citra-qt SOURCES)
|
||||||
qt5_create_translation(QM_FILES ${SRCS} ${UIS} ${CITRA_QT_LANGUAGES}/en.ts)
|
qt6_create_translation(QM_FILES ${SRCS} ${UIS} ${CITRA_QT_LANGUAGES}/en.ts)
|
||||||
add_custom_target(translation ALL DEPENDS ${CITRA_QT_LANGUAGES}/en.ts)
|
add_custom_target(translation ALL DEPENDS ${CITRA_QT_LANGUAGES}/en.ts)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@ -218,7 +218,7 @@ if (ENABLE_QT_TRANSLATION)
|
|||||||
list(REMOVE_ITEM LANGUAGES_TS ${CITRA_QT_LANGUAGES}/en.ts)
|
list(REMOVE_ITEM LANGUAGES_TS ${CITRA_QT_LANGUAGES}/en.ts)
|
||||||
|
|
||||||
# Compile TS files to QM files
|
# Compile TS files to QM files
|
||||||
qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
|
qt6_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
|
||||||
|
|
||||||
# Build a QRC file from the QM file list
|
# Build a QRC file from the QM file list
|
||||||
set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
|
set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
|
||||||
@ -230,7 +230,7 @@ if (ENABLE_QT_TRANSLATION)
|
|||||||
file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
|
file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
|
||||||
|
|
||||||
# Add the QRC file to package in all QM files
|
# Add the QRC file to package in all QM files
|
||||||
qt5_add_resources(LANGUAGES ${LANGUAGES_QRC})
|
qt6_add_resources(LANGUAGES ${LANGUAGES_QRC})
|
||||||
else()
|
else()
|
||||||
set(LANGUAGES)
|
set(LANGUAGES)
|
||||||
endif()
|
endif()
|
||||||
@ -257,7 +257,7 @@ if (APPLE)
|
|||||||
)
|
)
|
||||||
elseif(WIN32)
|
elseif(WIN32)
|
||||||
# compile as a win32 gui application instead of a console application
|
# compile as a win32 gui application instead of a console application
|
||||||
target_link_libraries(citra-qt PRIVATE Qt5::WinMain)
|
target_link_libraries(citra-qt PRIVATE Qt6::EntryPointImplementation)
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
set_target_properties(citra-qt PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
|
set_target_properties(citra-qt PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
|
||||||
elseif(MINGW)
|
elseif(MINGW)
|
||||||
@ -284,15 +284,15 @@ endif()
|
|||||||
create_target_directory_groups(citra-qt)
|
create_target_directory_groups(citra-qt)
|
||||||
|
|
||||||
target_link_libraries(citra-qt PRIVATE audio_core citra_common citra_core input_common network video_core)
|
target_link_libraries(citra-qt PRIVATE audio_core citra_common citra_core input_common network video_core)
|
||||||
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::Widgets Qt5::Multimedia Qt5::Concurrent)
|
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt6::Widgets Qt6::Multimedia Qt6::Concurrent)
|
||||||
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||||
|
|
||||||
if (NOT WIN32)
|
if (NOT WIN32)
|
||||||
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
target_include_directories(citra-qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (UNIX AND NOT APPLE)
|
if (UNIX AND NOT APPLE)
|
||||||
target_link_libraries(citra-qt PRIVATE Qt5::DBus)
|
target_link_libraries(citra-qt PRIVATE Qt6::DBus)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_compile_definitions(citra-qt PRIVATE
|
target_compile_definitions(citra-qt PRIVATE
|
||||||
@ -336,9 +336,9 @@ if(UNIX AND NOT APPLE)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (MSVC)
|
if (MSVC)
|
||||||
include(CopyCitraQt5Deps)
|
include(CopyCitraQt6Deps)
|
||||||
include(CopyCitraSDLDeps)
|
include(CopyCitraSDLDeps)
|
||||||
copy_citra_Qt5_deps(citra-qt)
|
copy_citra_Qt6_deps(citra-qt)
|
||||||
copy_citra_SDL_deps(citra-qt)
|
copy_citra_SDL_deps(citra-qt)
|
||||||
if (ENABLE_WEB_SERVICE AND OPENSSL_DLL_DIR)
|
if (ENABLE_WEB_SERVICE AND OPENSSL_DLL_DIR)
|
||||||
include(CopyCitraOpensslDeps)
|
include(CopyCitraOpensslDeps)
|
||||||
|
@ -533,7 +533,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
|||||||
|
|
||||||
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
|
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
|
||||||
// TouchBegin always has exactly one touch point, so take the .first()
|
// TouchBegin always has exactly one touch point, so take the .first()
|
||||||
const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
|
const auto [x, y] = ScaleTouch(event->points().first().position());
|
||||||
this->TouchPressed(x, y);
|
this->TouchPressed(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -542,10 +542,10 @@ void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
|
|||||||
int active_points = 0;
|
int active_points = 0;
|
||||||
|
|
||||||
// average all active touch points
|
// average all active touch points
|
||||||
for (const auto& tp : event->touchPoints()) {
|
for (const auto& tp : event->points()) {
|
||||||
if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
|
if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
|
||||||
active_points++;
|
active_points++;
|
||||||
pos += tp.pos();
|
pos += tp.position();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,9 +3,8 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <QCamera>
|
#include <QCamera>
|
||||||
#include <QCameraInfo>
|
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
#include <QMessageBox>
|
#include <QMediaDevices>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include "citra_qt/camera/qt_multimedia_camera.h"
|
#include "citra_qt/camera/qt_multimedia_camera.h"
|
||||||
#include "citra_qt/main.h"
|
#include "citra_qt/main.h"
|
||||||
@ -16,229 +15,85 @@
|
|||||||
|
|
||||||
namespace Camera {
|
namespace Camera {
|
||||||
|
|
||||||
QList<QVideoFrame::PixelFormat> QtCameraSurface::supportedPixelFormats(
|
std::shared_ptr<QtMultimediaCameraHandler> QtMultimediaCameraHandlerFactory::Create(
|
||||||
[[maybe_unused]] QAbstractVideoBuffer::HandleType handleType) const {
|
|
||||||
return QList<QVideoFrame::PixelFormat>()
|
|
||||||
<< QVideoFrame::Format_RGB32 << QVideoFrame::Format_RGB24
|
|
||||||
<< QVideoFrame::Format_ARGB32_Premultiplied << QVideoFrame::Format_ARGB32
|
|
||||||
<< QVideoFrame::Format_RGB565 << QVideoFrame::Format_RGB555
|
|
||||||
<< QVideoFrame::Format_Jpeg
|
|
||||||
// the following formats are supported via Qt internal conversions
|
|
||||||
<< QVideoFrame::Format_ARGB8565_Premultiplied << QVideoFrame::Format_BGRA32
|
|
||||||
<< QVideoFrame::Format_BGRA32_Premultiplied << QVideoFrame::Format_BGR32
|
|
||||||
<< QVideoFrame::Format_BGR24 << QVideoFrame::Format_BGR565 << QVideoFrame::Format_BGR555
|
|
||||||
<< QVideoFrame::Format_AYUV444 << QVideoFrame::Format_YUV444
|
|
||||||
<< QVideoFrame::Format_YUV420P << QVideoFrame::Format_YV12 << QVideoFrame::Format_UYVY
|
|
||||||
<< QVideoFrame::Format_YUYV << QVideoFrame::Format_NV12
|
|
||||||
<< QVideoFrame::Format_NV21; // Supporting all the QImage convertible formats, ordered by
|
|
||||||
// QImage decoding performance
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QtCameraSurface::present(const QVideoFrame& frame) {
|
|
||||||
if (!frame.isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
|
||||||
QMutexLocker locker(&mutex);
|
|
||||||
// In Qt 5.15, the image is already flipped
|
|
||||||
current_frame = frame.image();
|
|
||||||
locker.unlock();
|
|
||||||
#else
|
|
||||||
QVideoFrame cloneFrame(frame);
|
|
||||||
cloneFrame.map(QAbstractVideoBuffer::ReadOnly);
|
|
||||||
const QImage image(cloneFrame.bits(), cloneFrame.width(), cloneFrame.height(),
|
|
||||||
QVideoFrame::imageFormatFromPixelFormat(cloneFrame.pixelFormat()));
|
|
||||||
QMutexLocker locker(&mutex);
|
|
||||||
current_frame = image.mirrored(true, true);
|
|
||||||
locker.unlock();
|
|
||||||
cloneFrame.unmap();
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QtMultimediaCamera::QtMultimediaCamera(const std::string& camera_name,
|
|
||||||
const Service::CAM::Flip& flip)
|
|
||||||
: QtCameraInterface(flip), handler(QtMultimediaCameraHandler::GetHandler(camera_name)) {
|
|
||||||
if (handler->thread() == QThread::currentThread()) {
|
|
||||||
handler->CreateCamera(camera_name);
|
|
||||||
} else {
|
|
||||||
QMetaObject::invokeMethod(handler.get(), "CreateCamera", Qt::BlockingQueuedConnection,
|
|
||||||
Q_ARG(const std::string&, camera_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QtMultimediaCamera::~QtMultimediaCamera() {
|
|
||||||
handler->StopCamera();
|
|
||||||
QtMultimediaCameraHandler::ReleaseHandler(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
void QtMultimediaCamera::StartCapture() {
|
|
||||||
if (handler->thread() == QThread::currentThread()) {
|
|
||||||
handler->StartCamera();
|
|
||||||
} else {
|
|
||||||
QMetaObject::invokeMethod(handler.get(), "StartCamera", Qt::BlockingQueuedConnection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QtMultimediaCamera::StopCapture() {
|
|
||||||
handler->StopCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QtMultimediaCamera::SetFrameRate(Service::CAM::FrameRate frame_rate) {
|
|
||||||
const std::array<QCamera::FrameRateRange, 13> FrameRateList = {
|
|
||||||
/* Rate_15 */ QCamera::FrameRateRange(15, 15),
|
|
||||||
/* Rate_15_To_5 */ QCamera::FrameRateRange(5, 15),
|
|
||||||
/* Rate_15_To_2 */ QCamera::FrameRateRange(2, 15),
|
|
||||||
/* Rate_10 */ QCamera::FrameRateRange(10, 10),
|
|
||||||
/* Rate_8_5 */ QCamera::FrameRateRange(8.5, 8.5),
|
|
||||||
/* Rate_5 */ QCamera::FrameRateRange(5, 5),
|
|
||||||
/* Rate_20 */ QCamera::FrameRateRange(20, 20),
|
|
||||||
/* Rate_20_To_5 */ QCamera::FrameRateRange(5, 20),
|
|
||||||
/* Rate_30 */ QCamera::FrameRateRange(30, 30),
|
|
||||||
/* Rate_30_To_5 */ QCamera::FrameRateRange(5, 30),
|
|
||||||
/* Rate_15_To_10 */ QCamera::FrameRateRange(10, 15),
|
|
||||||
/* Rate_20_To_10 */ QCamera::FrameRateRange(10, 20),
|
|
||||||
/* Rate_30_To_10 */ QCamera::FrameRateRange(10, 30),
|
|
||||||
};
|
|
||||||
|
|
||||||
auto framerate = FrameRateList[static_cast<int>(frame_rate)];
|
|
||||||
|
|
||||||
if (handler->camera->supportedViewfinderFrameRateRanges().contains(framerate)) {
|
|
||||||
handler->settings.setMinimumFrameRate(framerate.minimumFrameRate);
|
|
||||||
handler->settings.setMaximumFrameRate(framerate.maximumFrameRate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage QtMultimediaCamera::QtReceiveFrame() {
|
|
||||||
QMutexLocker locker(&handler->camera_surface.mutex);
|
|
||||||
return handler->camera_surface.current_frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QtMultimediaCamera::IsPreviewAvailable() {
|
|
||||||
return handler->CameraAvailable();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<CameraInterface> QtMultimediaCameraFactory::Create(const std::string& config,
|
|
||||||
const Service::CAM::Flip& flip) {
|
|
||||||
return std::make_unique<QtMultimediaCamera>(config, flip);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<std::shared_ptr<QtMultimediaCameraHandler>, 3> QtMultimediaCameraHandler::handlers;
|
|
||||||
|
|
||||||
std::array<bool, 3> QtMultimediaCameraHandler::status;
|
|
||||||
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<QtMultimediaCameraHandler>>
|
|
||||||
QtMultimediaCameraHandler::loaded;
|
|
||||||
|
|
||||||
void QtMultimediaCameraHandler::Init() {
|
|
||||||
std::generate(std::begin(handlers), std::end(handlers),
|
|
||||||
std::make_shared<QtMultimediaCameraHandler>);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<QtMultimediaCameraHandler> QtMultimediaCameraHandler::GetHandler(
|
|
||||||
const std::string& camera_name) {
|
const std::string& camera_name) {
|
||||||
if (loaded.count(camera_name)) {
|
if (thread() == QThread::currentThread()) {
|
||||||
return loaded.at(camera_name);
|
std::shared_ptr<QtMultimediaCameraHandler> handler;
|
||||||
}
|
if (!handlers.contains(camera_name) || !(handler = handlers[camera_name].lock())) {
|
||||||
for (std::size_t i = 0; i < handlers.size(); i++) {
|
LOG_INFO(Service_CAM, "Creating new handler for camera '{}'", camera_name);
|
||||||
if (!status[i]) {
|
handler = std::make_shared<QtMultimediaCameraHandler>(camera_name);
|
||||||
LOG_INFO(Service_CAM, "Successfully got handler {}", i);
|
handlers[camera_name] = handler;
|
||||||
status[i] = true;
|
} else {
|
||||||
loaded.emplace(camera_name, handlers[i]);
|
LOG_INFO(Service_CAM, "Reusing existing handler for camera '{}'", camera_name);
|
||||||
return handlers[i];
|
|
||||||
}
|
}
|
||||||
|
return handler;
|
||||||
|
} else {
|
||||||
|
std::shared_ptr<QtMultimediaCameraHandler> handler;
|
||||||
|
QMetaObject::invokeMethod(this, "Create", Qt::BlockingQueuedConnection,
|
||||||
|
Q_RETURN_ARG(std::shared_ptr<QtMultimediaCameraHandler>, handler),
|
||||||
|
Q_ARG(std::string, camera_name));
|
||||||
|
return handler;
|
||||||
}
|
}
|
||||||
LOG_CRITICAL(Service_CAM, "All handlers taken up");
|
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtMultimediaCameraHandler::ReleaseHandler(
|
void QtMultimediaCameraHandlerFactory::PauseCameras() {
|
||||||
const std::shared_ptr<Camera::QtMultimediaCameraHandler>& handler) {
|
LOG_INFO(Service_CAM, "Pausing all cameras");
|
||||||
for (std::size_t i = 0; i < handlers.size(); i++) {
|
for (auto& handler_pair : handlers) {
|
||||||
if (handlers[i] == handler) {
|
auto handler = handler_pair.second.lock();
|
||||||
LOG_INFO(Service_CAM, "Successfully released handler {}", i);
|
if (handler && handler->IsActive()) {
|
||||||
status[i] = false;
|
handler->PauseCapture();
|
||||||
handlers[i]->started = false;
|
|
||||||
for (auto it = loaded.begin(); it != loaded.end(); it++) {
|
|
||||||
if (it->second == handlers[i]) {
|
|
||||||
loaded.erase(it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtMultimediaCameraHandler::CreateCamera(const std::string& camera_name) {
|
void QtMultimediaCameraHandlerFactory::ResumeCameras() {
|
||||||
QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
|
LOG_INFO(Service_CAM, "Resuming all cameras");
|
||||||
for (const QCameraInfo& cameraInfo : cameras) {
|
for (auto& handler_pair : handlers) {
|
||||||
if (cameraInfo.deviceName().toStdString() == camera_name)
|
auto handler = handler_pair.second.lock();
|
||||||
camera = std::make_unique<QCamera>(cameraInfo);
|
if (handler && handler->IsPaused()) {
|
||||||
|
handler->StartCapture();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!camera) { // no cameras found, using default camera
|
}
|
||||||
|
|
||||||
|
QtMultimediaCameraHandler::QtMultimediaCameraHandler(const std::string& camera_name) {
|
||||||
|
auto cameras = QMediaDevices::videoInputs();
|
||||||
|
auto requested_camera =
|
||||||
|
std::find_if(cameras.begin(), cameras.end(), [camera_name](QCameraDevice& camera_info) {
|
||||||
|
return camera_info.description().toStdString() == camera_name;
|
||||||
|
});
|
||||||
|
if (requested_camera != cameras.end()) {
|
||||||
|
camera = std::make_unique<QCamera>(*requested_camera);
|
||||||
|
} else {
|
||||||
camera = std::make_unique<QCamera>();
|
camera = std::make_unique<QCamera>();
|
||||||
}
|
}
|
||||||
settings.setMinimumFrameRate(30);
|
camera_surface = std::make_unique<QVideoSink>();
|
||||||
settings.setMaximumFrameRate(30);
|
capture_session.setVideoSink(camera_surface.get());
|
||||||
camera->setViewfinder(&camera_surface);
|
capture_session.setCamera(camera.get());
|
||||||
camera->load();
|
|
||||||
if (camera->supportedViewfinderPixelFormats().isEmpty()) {
|
|
||||||
// The gstreamer plugin (used on linux systems) returns an empty list on querying supported
|
|
||||||
// viewfinder pixel formats, and will not work without expliciting setting it to some value,
|
|
||||||
// so we are defaulting to RGB565 here which should be fairly widely supported.
|
|
||||||
settings.setPixelFormat(QVideoFrame::PixelFormat::Format_RGB565);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtMultimediaCameraHandler::StopCamera() {
|
QtMultimediaCameraHandler::~QtMultimediaCameraHandler() {
|
||||||
camera->stop();
|
StopCapture();
|
||||||
started = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QtMultimediaCameraHandler::StartCamera() {
|
void QtMultimediaCameraHandler::StartCapture() {
|
||||||
|
if (!camera->isActive()) {
|
||||||
#if defined(__APPLE__)
|
#if defined(__APPLE__)
|
||||||
if (!AppleAuthorization::CheckAuthorizationForCamera()) {
|
if (!AppleAuthorization::CheckAuthorizationForCamera()) {
|
||||||
LOG_ERROR(Service_CAM, "Unable to start camera due to lack of authorization");
|
LOG_ERROR(Service_CAM, "Unable to start camera due to lack of authorization");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
camera->setViewfinderSettings(settings);
|
camera->start();
|
||||||
camera->start();
|
}
|
||||||
started = true;
|
|
||||||
paused = false;
|
paused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QtMultimediaCameraHandler::CameraAvailable() const {
|
void QtMultimediaCameraHandler::StopCapture() {
|
||||||
return camera && camera->isAvailable();
|
if (camera->isActive()) {
|
||||||
}
|
camera->stop();
|
||||||
|
|
||||||
void QtMultimediaCameraHandler::StopCameras() {
|
|
||||||
LOG_INFO(Service_CAM, "Stopping all cameras");
|
|
||||||
for (auto& handler : handlers) {
|
|
||||||
if (handler && handler->started) {
|
|
||||||
handler->StopCamera();
|
|
||||||
handler->paused = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QtMultimediaCameraHandler::ResumeCameras() {
|
|
||||||
for (auto& handler : handlers) {
|
|
||||||
if (handler && handler->paused) {
|
|
||||||
handler->StartCamera();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void QtMultimediaCameraHandler::ReleaseHandlers() {
|
|
||||||
StopCameras();
|
|
||||||
LOG_INFO(Service_CAM, "Releasing all handlers");
|
|
||||||
for (std::size_t i = 0; i < handlers.size(); i++) {
|
|
||||||
status[i] = false;
|
|
||||||
handlers[i]->started = false;
|
|
||||||
handlers[i]->paused = false;
|
|
||||||
}
|
}
|
||||||
|
paused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Camera
|
} // namespace Camera
|
||||||
|
@ -4,99 +4,113 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
|
||||||
#include <QAbstractVideoSurface>
|
|
||||||
#include <QCamera>
|
#include <QCamera>
|
||||||
#include <QCameraViewfinderSettings>
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QMutex>
|
#include <QMediaCaptureSession>
|
||||||
|
#include <QVideoSink>
|
||||||
#include "citra_qt/camera/camera_util.h"
|
#include "citra_qt/camera/camera_util.h"
|
||||||
#include "citra_qt/camera/qt_camera_base.h"
|
#include "citra_qt/camera/qt_camera_base.h"
|
||||||
#include "core/frontend/camera/interface.h"
|
#include "core/frontend/camera/interface.h"
|
||||||
|
|
||||||
class GMainWindow;
|
|
||||||
|
|
||||||
namespace Camera {
|
namespace Camera {
|
||||||
|
|
||||||
class QtCameraSurface final : public QAbstractVideoSurface {
|
// NOTE: Must be created on the Qt thread. QtMultimediaCameraHandlerFactory ensures this.
|
||||||
|
class QtMultimediaCameraHandler final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
|
explicit QtMultimediaCameraHandler(const std::string& camera_name);
|
||||||
QAbstractVideoBuffer::HandleType) const override;
|
~QtMultimediaCameraHandler();
|
||||||
bool present(const QVideoFrame&) override;
|
|
||||||
|
void StartCapture();
|
||||||
|
void StopCapture();
|
||||||
|
|
||||||
|
QImage QtReceiveFrame() {
|
||||||
|
return camera_surface->videoFrame().toImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsPreviewAvailable() {
|
||||||
|
return camera->isAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsActive() {
|
||||||
|
return camera->isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsPaused() {
|
||||||
|
return paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PauseCapture() {
|
||||||
|
StopCapture();
|
||||||
|
paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMutex mutex;
|
std::unique_ptr<QCamera> camera;
|
||||||
QImage current_frame;
|
std::unique_ptr<QVideoSink> camera_surface;
|
||||||
|
QMediaCaptureSession capture_session{};
|
||||||
friend class QtMultimediaCamera; // For access to current_frame
|
bool paused = false; // was previously started but was paused, to be resumed
|
||||||
};
|
};
|
||||||
|
|
||||||
class QtMultimediaCameraHandler;
|
// NOTE: Must be created on the Qt thread.
|
||||||
|
class QtMultimediaCameraHandlerFactory final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE std::shared_ptr<QtMultimediaCameraHandler> Create(const std::string& camera_name);
|
||||||
|
void PauseCameras();
|
||||||
|
void ResumeCameras();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, std::weak_ptr<QtMultimediaCameraHandler>> handlers;
|
||||||
|
};
|
||||||
|
|
||||||
/// This class is only an interface. It just calls QtMultimediaCameraHandler.
|
/// This class is only an interface. It just calls QtMultimediaCameraHandler.
|
||||||
class QtMultimediaCamera final : public QtCameraInterface {
|
class QtMultimediaCamera final : public QtCameraInterface {
|
||||||
public:
|
public:
|
||||||
QtMultimediaCamera(const std::string& camera_name, const Service::CAM::Flip& flip);
|
QtMultimediaCamera(const std::shared_ptr<QtMultimediaCameraHandler>& handler,
|
||||||
~QtMultimediaCamera();
|
const Service::CAM::Flip& flip)
|
||||||
void StartCapture() override;
|
: QtCameraInterface(flip), handler(handler) {}
|
||||||
void StopCapture() override;
|
|
||||||
void SetFrameRate(Service::CAM::FrameRate frame_rate) override;
|
void StartCapture() override {
|
||||||
QImage QtReceiveFrame() override;
|
handler->StartCapture();
|
||||||
bool IsPreviewAvailable() override;
|
}
|
||||||
|
|
||||||
|
void StopCapture() override {
|
||||||
|
handler->StopCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFrameRate(Service::CAM::FrameRate frame_rate) override {}
|
||||||
|
|
||||||
|
QImage QtReceiveFrame() override {
|
||||||
|
return handler->QtReceiveFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsPreviewAvailable() override {
|
||||||
|
return handler->IsPreviewAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<QtMultimediaCameraHandler> handler;
|
std::shared_ptr<QtMultimediaCameraHandler> handler;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// This class is only an interface. It just calls QtMultimediaCameraHandlerFactory.
|
||||||
class QtMultimediaCameraFactory final : public QtCameraFactory {
|
class QtMultimediaCameraFactory final : public QtCameraFactory {
|
||||||
public:
|
public:
|
||||||
|
QtMultimediaCameraFactory(
|
||||||
|
const std::shared_ptr<QtMultimediaCameraHandlerFactory>& handler_factory)
|
||||||
|
: handler_factory(handler_factory) {}
|
||||||
|
|
||||||
std::unique_ptr<CameraInterface> Create(const std::string& config,
|
std::unique_ptr<CameraInterface> Create(const std::string& config,
|
||||||
const Service::CAM::Flip& flip) override;
|
const Service::CAM::Flip& flip) override {
|
||||||
};
|
return std::make_unique<QtMultimediaCamera>(handler_factory->Create(config), flip);
|
||||||
|
}
|
||||||
class QtMultimediaCameraHandler final : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Creates the global handler. Must be called in UI thread.
|
|
||||||
static void Init();
|
|
||||||
static std::shared_ptr<QtMultimediaCameraHandler> GetHandler(const std::string& camera_name);
|
|
||||||
static void ReleaseHandler(const std::shared_ptr<QtMultimediaCameraHandler>& handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the camera.
|
|
||||||
* Note: This function must be called via QMetaObject::invokeMethod in UI thread.
|
|
||||||
*/
|
|
||||||
Q_INVOKABLE void CreateCamera(const std::string& camera_name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the camera.
|
|
||||||
* Note: This function must be called via QMetaObject::invokeMethod in UI thread when
|
|
||||||
* starting the camera for the first time. 'Resume' calls can be in other threads.
|
|
||||||
*/
|
|
||||||
Q_INVOKABLE void StartCamera();
|
|
||||||
|
|
||||||
void StopCamera();
|
|
||||||
bool CameraAvailable() const;
|
|
||||||
static void StopCameras();
|
|
||||||
static void ResumeCameras();
|
|
||||||
static void ReleaseHandlers();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<QCamera> camera;
|
std::shared_ptr<QtMultimediaCameraHandlerFactory> handler_factory;
|
||||||
QtCameraSurface camera_surface{};
|
|
||||||
QCameraViewfinderSettings settings;
|
|
||||||
bool started = false;
|
|
||||||
bool paused = false; // was previously started but was paused, to be resumed
|
|
||||||
|
|
||||||
static std::array<std::shared_ptr<QtMultimediaCameraHandler>, 3> handlers;
|
|
||||||
static std::array<bool, 3> status;
|
|
||||||
static std::unordered_map<std::string, std::shared_ptr<QtMultimediaCameraHandler>> loaded;
|
|
||||||
|
|
||||||
friend class QtMultimediaCamera; // For access to camera_surface (and camera)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Camera
|
} // namespace Camera
|
||||||
|
@ -2,16 +2,16 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include <QCameraInfo>
|
#include <QCameraDevice>
|
||||||
#include <QDirIterator>
|
#include <QDirIterator>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
|
#include <QMediaDevices>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include "citra_qt/configuration/configure_camera.h"
|
#include "citra_qt/configuration/configure_camera.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "core/frontend/camera/factory.h"
|
#include "core/frontend/camera/factory.h"
|
||||||
#include "core/frontend/camera/interface.h"
|
|
||||||
#include "core/hle/service/cam/cam.h"
|
#include "core/hle/service/cam/cam.h"
|
||||||
#include "ui_configure_camera.h"
|
#include "ui_configure_camera.h"
|
||||||
|
|
||||||
@ -32,9 +32,9 @@ ConfigureCamera::ConfigureCamera(QWidget* parent)
|
|||||||
camera_name = Settings::values.camera_name;
|
camera_name = Settings::values.camera_name;
|
||||||
camera_config = Settings::values.camera_config;
|
camera_config = Settings::values.camera_config;
|
||||||
camera_flip = Settings::values.camera_flip;
|
camera_flip = Settings::values.camera_flip;
|
||||||
QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
|
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
|
||||||
for (const QCameraInfo& cameraInfo : cameras) {
|
for (const QCameraDevice& camera : cameras) {
|
||||||
ui->system_camera->addItem(cameraInfo.deviceName());
|
ui->system_camera->addItem(camera.description());
|
||||||
}
|
}
|
||||||
UpdateCameraMode();
|
UpdateCameraMode();
|
||||||
SetConfiguration();
|
SetConfiguration();
|
||||||
|
@ -579,7 +579,7 @@ void ConfigureInput::AutoMap() {
|
|||||||
void ConfigureInput::HandleClick(QPushButton* button,
|
void ConfigureInput::HandleClick(QPushButton* button,
|
||||||
std::function<void(const Common::ParamPackage&)> new_input_setter,
|
std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||||
InputCommon::Polling::DeviceType type) {
|
InputCommon::Polling::DeviceType type) {
|
||||||
previous_key_code = QKeySequence(button->text())[0];
|
previous_key_code = QKeySequence(button->text())[0].toCombined();
|
||||||
button->setText(tr("[press key]"));
|
button->setText(tr("[press key]"));
|
||||||
button->setFocus();
|
button->setFocus();
|
||||||
|
|
||||||
|
@ -234,6 +234,8 @@ void ConfigureMotionTouch::ConnectEvents() {
|
|||||||
&ConfigureMotionTouch::OnConfigureTouchCalibration);
|
&ConfigureMotionTouch::OnConfigureTouchCalibration);
|
||||||
connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
|
connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
|
||||||
&ConfigureMotionTouch::OnConfigureTouchFromButton);
|
&ConfigureMotionTouch::OnConfigureTouchFromButton);
|
||||||
|
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
|
||||||
|
&ConfigureMotionTouch::ApplyConfiguration);
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
|
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
|
||||||
if (CanCloseDialog()) {
|
if (CanCloseDialog()) {
|
||||||
reject();
|
reject();
|
||||||
|
@ -324,22 +324,4 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>accepted()</signal>
|
|
||||||
<receiver>ConfigureMotionTouch</receiver>
|
|
||||||
<slot>ApplyConfiguration()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>220</x>
|
|
||||||
<y>380</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>220</x>
|
|
||||||
<y>200</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -283,7 +283,7 @@ void ConfigureSystem::SetConfiguration() {
|
|||||||
|
|
||||||
ui->combo_init_clock->setCurrentIndex(static_cast<u8>(Settings::values.init_clock.GetValue()));
|
ui->combo_init_clock->setCurrentIndex(static_cast<u8>(Settings::values.init_clock.GetValue()));
|
||||||
QDateTime date_time;
|
QDateTime date_time;
|
||||||
date_time.setTime_t(Settings::values.init_time.GetValue());
|
date_time.setSecsSinceEpoch(Settings::values.init_time.GetValue());
|
||||||
ui->edit_init_time->setDateTime(date_time);
|
ui->edit_init_time->setDateTime(date_time);
|
||||||
|
|
||||||
long long init_time_offset = Settings::values.init_time_offset.GetValue();
|
long long init_time_offset = Settings::values.init_time_offset.GetValue();
|
||||||
@ -406,7 +406,7 @@ void ConfigureSystem::ApplyConfiguration() {
|
|||||||
|
|
||||||
Settings::values.init_clock =
|
Settings::values.init_clock =
|
||||||
static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex());
|
static_cast<Settings::InitClock>(ui->combo_init_clock->currentIndex());
|
||||||
Settings::values.init_time = ui->edit_init_time->dateTime().toTime_t();
|
Settings::values.init_time = ui->edit_init_time->dateTime().toSecsSinceEpoch();
|
||||||
|
|
||||||
s64 time_offset_time = ui->edit_init_time_offset_time->time().msecsSinceStartOfDay() / 1000;
|
s64 time_offset_time = ui->edit_init_time_offset_time->time().msecsSinceStartOfDay() / 1000;
|
||||||
s64 time_offset_days = ui->edit_init_time_offset_days->value() * 86400;
|
s64 time_offset_days = ui->edit_init_time_offset_days->value() * 86400;
|
||||||
|
@ -509,7 +509,8 @@ void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
|
|||||||
if (!coord_label) {
|
if (!coord_label) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto pos = MapToDeviceCoords(event->x(), event->y());
|
const auto point = event->position().toPoint();
|
||||||
|
const auto pos = MapToDeviceCoords(point.x(), point.y());
|
||||||
if (pos) {
|
if (pos) {
|
||||||
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
|
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
|
||||||
} else {
|
} else {
|
||||||
@ -527,7 +528,8 @@ void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
|
|||||||
if (event->button() != Qt::MouseButton::LeftButton) {
|
if (event->button() != Qt::MouseButton::LeftButton) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto pos = MapToDeviceCoords(event->x(), event->y());
|
const auto point = event->position().toPoint();
|
||||||
|
const auto pos = MapToDeviceCoords(point.x(), point.y());
|
||||||
if (pos) {
|
if (pos) {
|
||||||
emit DotAdded(*pos);
|
emit DotAdded(*pos);
|
||||||
}
|
}
|
||||||
@ -543,7 +545,7 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
emit DotSelected(obj->property(PropId).toInt());
|
emit DotSelected(obj->property(PropId).toInt());
|
||||||
|
|
||||||
drag_state.dot = qobject_cast<QLabel*>(obj);
|
drag_state.dot = qobject_cast<QLabel*>(obj);
|
||||||
drag_state.start_pos = mouse_event->globalPos();
|
drag_state.start_pos = mouse_event->globalPosition().toPoint();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case QEvent::Type::MouseMove: {
|
case QEvent::Type::MouseMove: {
|
||||||
@ -552,14 +554,13 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
}
|
}
|
||||||
const auto mouse_event = static_cast<QMouseEvent*>(event);
|
const auto mouse_event = static_cast<QMouseEvent*>(event);
|
||||||
if (!drag_state.active) {
|
if (!drag_state.active) {
|
||||||
drag_state.active =
|
drag_state.active = (mouse_event->globalPosition().toPoint() - drag_state.start_pos)
|
||||||
(mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >=
|
.manhattanLength() >= QApplication::startDragDistance();
|
||||||
QApplication::startDragDistance();
|
|
||||||
if (!drag_state.active) {
|
if (!drag_state.active) {
|
||||||
break;
|
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(),
|
current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(),
|
||||||
contentsMargins().left() + contentsRect().width() - 1));
|
contentsMargins().left() + contentsRect().width() - 1));
|
||||||
current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
|
current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
|
||||||
|
@ -40,8 +40,9 @@ void SurfacePicture::mousePressEvent(QMouseEvent* event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (surface_widget) {
|
if (surface_widget) {
|
||||||
surface_widget->Pick(event->x() * pixmap.width() / width(),
|
const auto pos = event->position().toPoint();
|
||||||
event->y() * pixmap.height() / height());
|
surface_widget->Pick(pos.x() * pixmap.width() / width(),
|
||||||
|
pos.y() * pixmap.height() / height());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,24 +142,28 @@ void MicroProfileWidget::hideEvent(QHideEvent* event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MicroProfileWidget::mouseMoveEvent(QMouseEvent* event) {
|
void MicroProfileWidget::mouseMoveEvent(QMouseEvent* event) {
|
||||||
MicroProfileMousePosition(event->x() / x_scale, event->y() / y_scale, 0);
|
const auto point = event->position().toPoint();
|
||||||
|
MicroProfileMousePosition(point.x() / x_scale, point.y() / y_scale, 0);
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroProfileWidget::mousePressEvent(QMouseEvent* event) {
|
void MicroProfileWidget::mousePressEvent(QMouseEvent* event) {
|
||||||
MicroProfileMousePosition(event->x() / x_scale, event->y() / y_scale, 0);
|
const auto point = event->position().toPoint();
|
||||||
|
MicroProfileMousePosition(point.x() / x_scale, point.y() / y_scale, 0);
|
||||||
MicroProfileMouseButton(event->buttons() & Qt::LeftButton, event->buttons() & Qt::RightButton);
|
MicroProfileMouseButton(event->buttons() & Qt::LeftButton, event->buttons() & Qt::RightButton);
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* event) {
|
void MicroProfileWidget::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
MicroProfileMousePosition(event->x() / x_scale, event->y() / y_scale, 0);
|
const auto point = event->position().toPoint();
|
||||||
|
MicroProfileMousePosition(point.x() / x_scale, point.y() / y_scale, 0);
|
||||||
MicroProfileMouseButton(event->buttons() & Qt::LeftButton, event->buttons() & Qt::RightButton);
|
MicroProfileMouseButton(event->buttons() & Qt::LeftButton, event->buttons() & Qt::RightButton);
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MicroProfileWidget::wheelEvent(QWheelEvent* event) {
|
void MicroProfileWidget::wheelEvent(QWheelEvent* event) {
|
||||||
MicroProfileMousePosition(event->position().x() / x_scale, event->position().y() / y_scale,
|
const auto point = event->position().toPoint();
|
||||||
|
MicroProfileMousePosition(point.x() / x_scale, point.y() / y_scale,
|
||||||
event->angleDelta().y() / 120);
|
event->angleDelta().y() / 120);
|
||||||
event->accept();
|
event->accept();
|
||||||
}
|
}
|
||||||
|
@ -239,7 +239,8 @@ void GameList::OnTextChanged(const QString& new_text) {
|
|||||||
file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
|
file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
|
||||||
file_title;
|
file_title;
|
||||||
if (ContainsAllWords(file_name, edit_filter_text) ||
|
if (ContainsAllWords(file_name, edit_filter_text) ||
|
||||||
(file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) {
|
(file_program_id.length() == 16 &&
|
||||||
|
edit_filter_text.contains(file_program_id))) {
|
||||||
tree_view->setRowHidden(j, folder_index, false);
|
tree_view->setRowHidden(j, folder_index, false);
|
||||||
++result_count;
|
++result_count;
|
||||||
} else {
|
} else {
|
||||||
@ -419,10 +420,10 @@ void GameList::DonePopulating(const QStringList& watch_list) {
|
|||||||
// Workaround: Add the watch paths in chunks to allow the gui to refresh
|
// Workaround: Add the watch paths in chunks to allow the gui to refresh
|
||||||
// This prevents the UI from stalling when a large number of watch paths are added
|
// This prevents the UI from stalling when a large number of watch paths are added
|
||||||
// Also artificially caps the watcher to a certain number of directories
|
// Also artificially caps the watcher to a certain number of directories
|
||||||
constexpr int LIMIT_WATCH_DIRECTORIES = 5000;
|
constexpr qsizetype LIMIT_WATCH_DIRECTORIES = 5000;
|
||||||
constexpr int SLICE_SIZE = 25;
|
constexpr int SLICE_SIZE = 25;
|
||||||
int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES);
|
const qsizetype len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES);
|
||||||
for (int i = 0; i < len; i += SLICE_SIZE) {
|
for (qsizetype i = 0; i < len; i += SLICE_SIZE) {
|
||||||
watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE));
|
watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE));
|
||||||
QCoreApplication::processEvents();
|
QCoreApplication::processEvents();
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ void HotkeyRegistry::SaveHotkeys() {
|
|||||||
void HotkeyRegistry::LoadHotkeys() {
|
void HotkeyRegistry::LoadHotkeys() {
|
||||||
// Make sure NOT to use a reference here because it would become invalid once we call
|
// Make sure NOT to use a reference here because it would become invalid once we call
|
||||||
// beginGroup()
|
// beginGroup()
|
||||||
for (auto shortcut : UISettings::values.shortcuts) {
|
for (const auto shortcut : UISettings::values.shortcuts) {
|
||||||
Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
|
Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
|
||||||
if (!shortcut.shortcut.keyseq.isEmpty()) {
|
if (!shortcut.shortcut.keyseq.isEmpty()) {
|
||||||
hk.keyseq =
|
hk.keyseq =
|
||||||
@ -40,7 +40,7 @@ void HotkeyRegistry::LoadHotkeys() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
|
QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QObject* widget) {
|
||||||
Hotkey& hk = hotkey_groups[group][action];
|
Hotkey& hk = hotkey_groups[group][action];
|
||||||
|
|
||||||
if (!hk.shortcut) {
|
if (!hk.shortcut) {
|
||||||
|
@ -47,7 +47,7 @@ public:
|
|||||||
* will be the same. Thus, you shouldn't rely on the caller really being the
|
* will be the same. Thus, you shouldn't rely on the caller really being the
|
||||||
* QShortcut's parent.
|
* QShortcut's parent.
|
||||||
*/
|
*/
|
||||||
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
|
QShortcut* GetHotkey(const QString& group, const QString& action, QObject* widget);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
|
* Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
|
||||||
|
@ -198,7 +198,7 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
|
|||||||
|
|
||||||
void LoadingScreen::paintEvent(QPaintEvent* event) {
|
void LoadingScreen::paintEvent(QPaintEvent* event) {
|
||||||
QStyleOption opt;
|
QStyleOption opt;
|
||||||
opt.init(this);
|
opt.initFrom(this);
|
||||||
QPainter p(this);
|
QPainter p(this);
|
||||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||||
QWidget::paintEvent(event);
|
QWidget::paintEvent(event);
|
||||||
|
@ -73,5 +73,3 @@ private:
|
|||||||
std::chrono::duration<double> rolling_average = {};
|
std::chrono::duration<double> rolling_average = {};
|
||||||
bool eta_shown = false;
|
bool eta_shown = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(VideoCore::LoadCallbackStage);
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
#include <clocale>
|
#include <clocale>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <QDesktopWidget>
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
@ -200,6 +199,11 @@ GMainWindow::GMainWindow()
|
|||||||
qRegisterMetaType<std::size_t>("std::size_t");
|
qRegisterMetaType<std::size_t>("std::size_t");
|
||||||
qRegisterMetaType<Service::AM::InstallStatus>("Service::AM::InstallStatus");
|
qRegisterMetaType<Service::AM::InstallStatus>("Service::AM::InstallStatus");
|
||||||
|
|
||||||
|
// Register CameraFactory
|
||||||
|
qt_cameras = std::make_shared<Camera::QtMultimediaCameraHandlerFactory>();
|
||||||
|
Camera::RegisterFactory("image", std::make_unique<Camera::StillImageCameraFactory>());
|
||||||
|
Camera::RegisterFactory("qt", std::make_unique<Camera::QtMultimediaCameraFactory>(qt_cameras));
|
||||||
|
|
||||||
LoadTranslation();
|
LoadTranslation();
|
||||||
|
|
||||||
Pica::g_debug_context = Pica::DebugContext::Construct();
|
Pica::g_debug_context = Pica::DebugContext::Construct();
|
||||||
@ -647,7 +651,7 @@ void GMainWindow::ShowUpdaterWidgets() {
|
|||||||
|
|
||||||
void GMainWindow::SetDefaultUIGeometry() {
|
void GMainWindow::SetDefaultUIGeometry() {
|
||||||
// geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
|
// geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
|
||||||
const QRect screenRect = QApplication::desktop()->screenGeometry(this);
|
const QRect screenRect = screen()->geometry();
|
||||||
|
|
||||||
const int w = screenRect.width() * 2 / 3;
|
const int w = screenRect.width() * 2 / 3;
|
||||||
const int h = screenRect.height() / 2;
|
const int h = screenRect.height() / 2;
|
||||||
@ -1284,8 +1288,6 @@ void GMainWindow::ShutdownGame() {
|
|||||||
|
|
||||||
discord_rpc->Update();
|
discord_rpc->Update();
|
||||||
|
|
||||||
Camera::QtMultimediaCameraHandler::ReleaseHandlers();
|
|
||||||
|
|
||||||
// The emulation is stopped, so closing the window or not does not matter anymore
|
// The emulation is stopped, so closing the window or not does not matter anymore
|
||||||
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
||||||
disconnect(secondary_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
disconnect(secondary_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
|
||||||
@ -1344,7 +1346,7 @@ void GMainWindow::StoreRecentFile(const QString& filename) {
|
|||||||
|
|
||||||
void GMainWindow::UpdateRecentFiles() {
|
void GMainWindow::UpdateRecentFiles() {
|
||||||
const int num_recent_files =
|
const int num_recent_files =
|
||||||
std::min(UISettings::values.recent_files.size(), max_recent_files_item);
|
std::min(static_cast<int>(UISettings::values.recent_files.size()), max_recent_files_item);
|
||||||
|
|
||||||
for (int i = 0; i < num_recent_files; i++) {
|
for (int i = 0; i < num_recent_files; i++) {
|
||||||
const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(
|
const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(
|
||||||
@ -1648,7 +1650,7 @@ void GMainWindow::InstallCIA(QStringList filepaths) {
|
|||||||
progress_bar->show();
|
progress_bar->show();
|
||||||
progress_bar->setMaximum(INT_MAX);
|
progress_bar->setMaximum(INT_MAX);
|
||||||
|
|
||||||
QtConcurrent::run([&, filepaths] {
|
(void)QtConcurrent::run([&, filepaths] {
|
||||||
Service::AM::InstallStatus status;
|
Service::AM::InstallStatus status;
|
||||||
const auto cia_progress = [&](std::size_t written, std::size_t total) {
|
const auto cia_progress = [&](std::size_t written, std::size_t total) {
|
||||||
emit UpdateProgress(written, total);
|
emit UpdateProgress(written, total);
|
||||||
@ -1724,7 +1726,7 @@ void GMainWindow::OnMenuRecentFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnStartGame() {
|
void GMainWindow::OnStartGame() {
|
||||||
Camera::QtMultimediaCameraHandler::ResumeCameras();
|
qt_cameras->ResumeCameras();
|
||||||
|
|
||||||
PreventOSSleep();
|
PreventOSSleep();
|
||||||
|
|
||||||
@ -1751,7 +1753,7 @@ void GMainWindow::OnRestartGame() {
|
|||||||
|
|
||||||
void GMainWindow::OnPauseGame() {
|
void GMainWindow::OnPauseGame() {
|
||||||
emu_thread->SetRunning(false);
|
emu_thread->SetRunning(false);
|
||||||
Camera::QtMultimediaCameraHandler::StopCameras();
|
qt_cameras->PauseCameras();
|
||||||
|
|
||||||
UpdateMenuState();
|
UpdateMenuState();
|
||||||
AllowOSSleep();
|
AllowOSSleep();
|
||||||
@ -2690,7 +2692,7 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
|
|||||||
#undef main
|
#undef main
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void SetHighDPIAttributes() {
|
static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// For Windows, we want to avoid scaling artifacts on fractional scaling ratios.
|
// For Windows, we want to avoid scaling artifacts on fractional scaling ratios.
|
||||||
// This is done by setting the optimal scaling policy for the primary screen.
|
// This is done by setting the optimal scaling policy for the primary screen.
|
||||||
@ -2703,40 +2705,34 @@ static void SetHighDPIAttributes() {
|
|||||||
// Get the current screen geometry.
|
// Get the current screen geometry.
|
||||||
const QScreen* primary_screen = QGuiApplication::primaryScreen();
|
const QScreen* primary_screen = QGuiApplication::primaryScreen();
|
||||||
if (primary_screen == nullptr) {
|
if (primary_screen == nullptr) {
|
||||||
return;
|
return Qt::HighDpiScaleFactorRoundingPolicy::PassThrough;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QRect screen_rect = primary_screen->geometry();
|
const QRect screen_rect = primary_screen->geometry();
|
||||||
const int real_width = screen_rect.width();
|
const qreal real_ratio = primary_screen->devicePixelRatio();
|
||||||
const int real_height = screen_rect.height();
|
const qreal real_width = std::trunc(screen_rect.width() * real_ratio);
|
||||||
const float real_ratio = primary_screen->logicalDotsPerInch() / 96.0f;
|
const qreal real_height = std::trunc(screen_rect.height() * real_ratio);
|
||||||
|
|
||||||
// Recommended minimum width and height for proper window fit.
|
// Recommended minimum width and height for proper window fit.
|
||||||
// Any screen with a lower resolution than this will still have a scale of 1.
|
// Any screen with a lower resolution than this will still have a scale of 1.
|
||||||
constexpr float minimum_width = 1350.0f;
|
constexpr qreal minimum_width = 1350.0;
|
||||||
constexpr float minimum_height = 900.0f;
|
constexpr qreal minimum_height = 900.0;
|
||||||
|
|
||||||
const float width_ratio = std::max(1.0f, real_width / minimum_width);
|
const qreal width_ratio = std::max(1.0, real_width / minimum_width);
|
||||||
const float height_ratio = std::max(1.0f, real_height / minimum_height);
|
const qreal height_ratio = std::max(1.0, real_height / minimum_height);
|
||||||
|
|
||||||
// Get the lower of the 2 ratios and truncate, this is the maximum integer scale.
|
// Get the lower of the 2 ratios and truncate, this is the maximum integer scale.
|
||||||
const float max_ratio = std::trunc(std::min(width_ratio, height_ratio));
|
const qreal max_ratio = std::trunc(std::min(width_ratio, height_ratio));
|
||||||
|
|
||||||
if (max_ratio > real_ratio) {
|
if (max_ratio > real_ratio) {
|
||||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
return Qt::HighDpiScaleFactorRoundingPolicy::Round;
|
||||||
Qt::HighDpiScaleFactorRoundingPolicy::Round);
|
|
||||||
} else {
|
} else {
|
||||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
return Qt::HighDpiScaleFactorRoundingPolicy::Floor;
|
||||||
Qt::HighDpiScaleFactorRoundingPolicy::Floor);
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Other OSes should be better than Windows at fractional scaling.
|
// Other OSes should be better than Windows at fractional scaling.
|
||||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
return Qt::HighDpiScaleFactorRoundingPolicy::PassThrough;
|
||||||
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
|
||||||
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
@ -2748,16 +2744,14 @@ int main(int argc, char* argv[]) {
|
|||||||
QCoreApplication::setOrganizationName(QStringLiteral("Citra team"));
|
QCoreApplication::setOrganizationName(QStringLiteral("Citra team"));
|
||||||
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
|
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
|
||||||
|
|
||||||
SetHighDPIAttributes();
|
auto rounding_policy = GetHighDpiRoundingPolicy();
|
||||||
|
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
||||||
chdir(bin_path.c_str());
|
chdir(bin_path.c_str());
|
||||||
#endif
|
#endif
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
// Disables the "?" button on all dialogs. Disabled by default on Qt6.
|
|
||||||
QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton);
|
|
||||||
#endif
|
|
||||||
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
|
QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
|
||||||
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
@ -2768,11 +2762,6 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
GMainWindow main_window;
|
GMainWindow main_window;
|
||||||
|
|
||||||
// Register CameraFactory
|
|
||||||
Camera::RegisterFactory("image", std::make_unique<Camera::StillImageCameraFactory>());
|
|
||||||
Camera::RegisterFactory("qt", std::make_unique<Camera::QtMultimediaCameraFactory>());
|
|
||||||
Camera::QtMultimediaCameraHandler::Init();
|
|
||||||
|
|
||||||
// Register frontend applets
|
// Register frontend applets
|
||||||
Frontend::RegisterDefaultApplets();
|
Frontend::RegisterDefaultApplets();
|
||||||
|
|
||||||
|
@ -49,6 +49,10 @@ class RegistersWidget;
|
|||||||
class Updater;
|
class Updater;
|
||||||
class WaitTreeWidget;
|
class WaitTreeWidget;
|
||||||
|
|
||||||
|
namespace Camera {
|
||||||
|
class QtMultimediaCameraHandlerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
namespace DiscordRPC {
|
namespace DiscordRPC {
|
||||||
class DiscordInterface;
|
class DiscordInterface;
|
||||||
}
|
}
|
||||||
@ -335,6 +339,8 @@ private:
|
|||||||
|
|
||||||
HotkeyRegistry hotkey_registry;
|
HotkeyRegistry hotkey_registry;
|
||||||
|
|
||||||
|
std::shared_ptr<Camera::QtMultimediaCameraHandlerFactory> qt_cameras;
|
||||||
|
|
||||||
#ifdef __unix__
|
#ifdef __unix__
|
||||||
QDBusObjectPath wake_lock{};
|
QDBusObjectPath wake_lock{};
|
||||||
#endif
|
#endif
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
#include <QIntValidator>
|
#include <QIntValidator>
|
||||||
#include <QRegExpValidator>
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QtConcurrent/QtConcurrentRun>
|
#include <QtConcurrent/QtConcurrentRun>
|
||||||
#include "citra_qt/main.h"
|
#include "citra_qt/main.h"
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QRegExp>
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QValidator>
|
#include <QValidator>
|
||||||
|
|
||||||
@ -30,15 +30,17 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
/// room name can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20
|
/// room name can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20
|
||||||
QRegExp room_name_regex = QRegExp(QStringLiteral("^[a-zA-Z0-9._- ]{4,20}$"));
|
QRegularExpression room_name_regex =
|
||||||
QRegExpValidator room_name;
|
QRegularExpression(QStringLiteral("^[a-zA-Z0-9._\\- ]{4,20}$"));
|
||||||
|
QRegularExpressionValidator room_name;
|
||||||
|
|
||||||
/// nickname can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20
|
/// nickname can be alphanumeric and " " "_" "." and "-" and must have a size of 4-20
|
||||||
QRegExp nickname_regex = QRegExp(QStringLiteral("^[a-zA-Z0-9._- ]{4,20}$"));
|
QRegularExpression nickname_regex =
|
||||||
QRegExpValidator nickname;
|
QRegularExpression(QStringLiteral("^[a-zA-Z0-9._\\- ]{4,20}$"));
|
||||||
|
QRegularExpressionValidator nickname;
|
||||||
|
|
||||||
/// ipv4 / ipv6 / hostnames
|
/// ipv4 / ipv6 / hostnames
|
||||||
QRegExp ip_regex = QRegExp(QStringLiteral(
|
QRegularExpression ip_regex = QRegularExpression(QStringLiteral(
|
||||||
// IPv4 regex
|
// IPv4 regex
|
||||||
"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|"
|
"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|"
|
||||||
// IPv6 regex
|
// IPv6 regex
|
||||||
@ -59,7 +61,7 @@ private:
|
|||||||
"\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$|"
|
"\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$|"
|
||||||
// Hostname regex
|
// Hostname regex
|
||||||
"^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}$"));
|
"^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}$"));
|
||||||
QRegExpValidator ip;
|
QRegularExpressionValidator ip;
|
||||||
|
|
||||||
/// port must be between 0 and 65535
|
/// port must be between 0 and 65535
|
||||||
QIntValidator port;
|
QIntValidator port;
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QRegExpValidator>
|
#include <QRegularExpression>
|
||||||
#include "citra_qt/util/spinbox.h"
|
#include "citra_qt/util/spinbox.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
|
||||||
@ -244,14 +244,15 @@ QValidator::State CSpinBox::validate(QString& input, int& pos) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Match string
|
// Match string
|
||||||
QRegExp num_regexp(regexp);
|
QRegularExpression num_regexp(QRegularExpression::anchoredPattern(regexp));
|
||||||
int num_pos = strpos;
|
int num_pos = strpos;
|
||||||
QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length());
|
QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length());
|
||||||
|
|
||||||
if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0)
|
auto match = num_regexp.match(sub_input);
|
||||||
|
if (!match.hasMatch())
|
||||||
return QValidator::Invalid;
|
return QValidator::Invalid;
|
||||||
|
|
||||||
sub_input = sub_input.left(num_regexp.matchedLength());
|
sub_input = sub_input.left(match.capturedLength());
|
||||||
bool ok;
|
bool ok;
|
||||||
qint64 val = sub_input.toLongLong(&ok, base);
|
qint64 val = sub_input.toLongLong(&ok, base);
|
||||||
|
|
||||||
@ -263,7 +264,7 @@ QValidator::State CSpinBox::validate(QString& input, int& pos) const {
|
|||||||
return QValidator::Invalid;
|
return QValidator::Invalid;
|
||||||
|
|
||||||
// Make sure we are actually at the end of this string...
|
// Make sure we are actually at the end of this string...
|
||||||
strpos += num_regexp.matchedLength();
|
strpos += match.capturedLength();
|
||||||
|
|
||||||
if (!suffix.isEmpty() && input.mid(strpos) != suffix) {
|
if (!suffix.isEmpty() && input.mid(strpos) != suffix) {
|
||||||
return QValidator::Invalid;
|
return QValidator::Invalid;
|
||||||
|
@ -44,11 +44,11 @@ std::vector<u16> SMDH::GetIcon(bool large) const {
|
|||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<u16, 0x40> SMDH::GetShortTitle(Loader::SMDH::TitleLanguage language) const {
|
std::array<char16_t, 0x40> SMDH::GetShortTitle(Loader::SMDH::TitleLanguage language) const {
|
||||||
return titles[static_cast<int>(language)].short_title;
|
return titles[static_cast<int>(language)].short_title;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<u16, 0x80> SMDH::GetLongTitle(Loader::SMDH::TitleLanguage language) const {
|
std::array<char16_t, 0x80> SMDH::GetLongTitle(Loader::SMDH::TitleLanguage language) const {
|
||||||
return titles[static_cast<int>(language)].long_title;
|
return titles[static_cast<int>(language)].long_title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,9 +26,9 @@ struct SMDH {
|
|||||||
INSERT_PADDING_BYTES(2);
|
INSERT_PADDING_BYTES(2);
|
||||||
|
|
||||||
struct Title {
|
struct Title {
|
||||||
std::array<u16, 0x40> short_title;
|
std::array<char16_t, 0x40> short_title;
|
||||||
std::array<u16, 0x80> long_title;
|
std::array<char16_t, 0x80> long_title;
|
||||||
std::array<u16, 0x40> publisher;
|
std::array<char16_t, 0x40> publisher;
|
||||||
};
|
};
|
||||||
std::array<Title, 16> titles;
|
std::array<Title, 16> titles;
|
||||||
|
|
||||||
@ -88,14 +88,14 @@ struct SMDH {
|
|||||||
* @param language title language
|
* @param language title language
|
||||||
* @return UTF-16 array of the short title
|
* @return UTF-16 array of the short title
|
||||||
*/
|
*/
|
||||||
std::array<u16, 0x40> GetShortTitle(Loader::SMDH::TitleLanguage language) const;
|
std::array<char16_t, 0x40> GetShortTitle(Loader::SMDH::TitleLanguage language) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the long game title from SMDH
|
* Gets the long game title from SMDH
|
||||||
* @param language title language
|
* @param language title language
|
||||||
* @return UTF-16 array of the long title
|
* @return UTF-16 array of the long title
|
||||||
*/
|
*/
|
||||||
std::array<u16, 0x80> GetLongTitle(Loader::SMDH::TitleLanguage language) const;
|
std::array<char16_t, 0x80> GetLongTitle(Loader::SMDH::TitleLanguage language) const;
|
||||||
|
|
||||||
std::vector<GameRegion> GetRegions() const;
|
std::vector<GameRegion> GetRegions() const;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user