qt: Migrate to Qt6. (#6418)

This commit is contained in:
Steveice10 2023-05-05 03:10:34 -07:00 committed by GitHub
parent 70335a7f4d
commit 2273df4d70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 299 additions and 464 deletions

View File

@ -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

View File

@ -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/"

View File

@ -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/

View File

@ -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 \

View File

@ -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

View File

@ -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)

View 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)

View File

@ -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}")

View File

@ -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)

View File

@ -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();
} }
} }

View File

@ -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())) {
LOG_INFO(Service_CAM, "Creating new handler for camera '{}'", camera_name);
handler = std::make_shared<QtMultimediaCameraHandler>(camera_name);
handlers[camera_name] = handler;
} else {
LOG_INFO(Service_CAM, "Reusing existing handler for camera '{}'", camera_name);
} }
for (std::size_t i = 0; i < handlers.size(); i++) { return handler;
if (!status[i]) { } else {
LOG_INFO(Service_CAM, "Successfully got handler {}", i); std::shared_ptr<QtMultimediaCameraHandler> handler;
status[i] = true; QMetaObject::invokeMethod(this, "Create", Qt::BlockingQueuedConnection,
loaded.emplace(camera_name, handlers[i]); Q_RETURN_ARG(std::shared_ptr<QtMultimediaCameraHandler>, handler),
return handlers[i]; 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

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -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>

View File

@ -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;

View File

@ -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(),

View File

@ -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());
} }
} }

View File

@ -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();
} }

View File

@ -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();
} }

View File

@ -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) {

View File

@ -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.

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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

View File

@ -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"

View File

@ -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;

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;
}; };