mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-25 15:50:13 +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
|
||||
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
|
||||
/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 ..
|
||||
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' \;
|
||||
|
||||
# copy Qt plugins
|
||||
mkdir 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}/../styles/" package/
|
||||
rm -f package/mediaservice/*d.dll
|
||||
|
||||
python3 .ci/linux-mingw/scan_dll.py package/*.exe package/imageformats/*.dll "package/"
|
||||
|
@ -1,13 +1,3 @@
|
||||
#!/bin/sh -ex
|
||||
|
||||
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 \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MSVCCache.cmake" \
|
||||
-DCITRA_USE_CCACHE=ON \
|
||||
-DENABLE_QT_TRANSLATION=OFF \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
|
||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=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.
|
||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||
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)
|
||||
if (MSVC)
|
||||
@ -202,23 +202,23 @@ find_package(Threads REQUIRED)
|
||||
|
||||
if (ENABLE_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})
|
||||
endif()
|
||||
|
||||
find_package(Qt5 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
find_package(Qt5 REQUIRED COMPONENTS DBus)
|
||||
find_package(Qt6 REQUIRED COMPONENTS DBus)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT_TRANSLATION)
|
||||
find_package(Qt5 REQUIRED COMPONENTS LinguistTools)
|
||||
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
|
||||
endif()
|
||||
|
||||
if (NOT CITRA_USE_BUNDLED_QT)
|
||||
# Make sure the Qt bin directory is in the prefix path for later consumers.
|
||||
get_target_property(qmake_executable Qt5::qmake IMPORTED_LOCATION)
|
||||
# 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 Qt6::qmake IMPORTED_LOCATION)
|
||||
get_filename_component(qt_bin_dir "${qmake_executable}" DIRECTORY)
|
||||
list(APPEND CMAKE_PREFIX_PATH ${qt_bin_dir})
|
||||
endif()
|
||||
@ -282,8 +282,6 @@ if (APPLE)
|
||||
find_library(AVFOUNDATION_LIBRARY AVFoundation)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${AVFOUNDATION_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
||||
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)
|
||||
if (MINGW)
|
||||
# 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})
|
||||
else()
|
||||
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()
|
||||
|
||||
if (NOT EXISTS "${prefix}")
|
||||
|
@ -209,7 +209,7 @@ if (ENABLE_QT_TRANSLATION)
|
||||
# Update source TS file if enabled
|
||||
if (GENERATE_QT_TRANSLATION)
|
||||
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)
|
||||
endif()
|
||||
|
||||
@ -218,7 +218,7 @@ if (ENABLE_QT_TRANSLATION)
|
||||
list(REMOVE_ITEM LANGUAGES_TS ${CITRA_QT_LANGUAGES}/en.ts)
|
||||
|
||||
# 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
|
||||
set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
|
||||
@ -230,7 +230,7 @@ if (ENABLE_QT_TRANSLATION)
|
||||
file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
|
||||
|
||||
# Add the QRC file to package in all QM files
|
||||
qt5_add_resources(LANGUAGES ${LANGUAGES_QRC})
|
||||
qt6_add_resources(LANGUAGES ${LANGUAGES_QRC})
|
||||
else()
|
||||
set(LANGUAGES)
|
||||
endif()
|
||||
@ -257,7 +257,7 @@ if (APPLE)
|
||||
)
|
||||
elseif(WIN32)
|
||||
# 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)
|
||||
set_target_properties(citra-qt PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
|
||||
elseif(MINGW)
|
||||
@ -284,15 +284,15 @@ endif()
|
||||
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 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)
|
||||
|
||||
if (NOT WIN32)
|
||||
target_include_directories(citra-qt PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
||||
target_include_directories(citra-qt PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(citra-qt PRIVATE Qt5::DBus)
|
||||
target_link_libraries(citra-qt PRIVATE Qt6::DBus)
|
||||
endif()
|
||||
|
||||
target_compile_definitions(citra-qt PRIVATE
|
||||
@ -336,9 +336,9 @@ if(UNIX AND NOT APPLE)
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
include(CopyCitraQt5Deps)
|
||||
include(CopyCitraQt6Deps)
|
||||
include(CopyCitraSDLDeps)
|
||||
copy_citra_Qt5_deps(citra-qt)
|
||||
copy_citra_Qt6_deps(citra-qt)
|
||||
copy_citra_SDL_deps(citra-qt)
|
||||
if (ENABLE_WEB_SERVICE AND OPENSSL_DLL_DIR)
|
||||
include(CopyCitraOpensslDeps)
|
||||
|
@ -533,7 +533,7 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||
|
||||
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
@ -542,10 +542,10 @@ void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
|
||||
int active_points = 0;
|
||||
|
||||
// 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)) {
|
||||
active_points++;
|
||||
pos += tp.pos();
|
||||
pos += tp.position();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,8 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QCamera>
|
||||
#include <QCameraInfo>
|
||||
#include <QImageReader>
|
||||
#include <QMessageBox>
|
||||
#include <QMediaDevices>
|
||||
#include <QThread>
|
||||
#include "citra_qt/camera/qt_multimedia_camera.h"
|
||||
#include "citra_qt/main.h"
|
||||
@ -16,229 +15,85 @@
|
||||
|
||||
namespace Camera {
|
||||
|
||||
QList<QVideoFrame::PixelFormat> QtCameraSurface::supportedPixelFormats(
|
||||
[[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(
|
||||
std::shared_ptr<QtMultimediaCameraHandler> QtMultimediaCameraHandlerFactory::Create(
|
||||
const std::string& camera_name) {
|
||||
if (loaded.count(camera_name)) {
|
||||
return loaded.at(camera_name);
|
||||
if (thread() == QThread::currentThread()) {
|
||||
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++) {
|
||||
if (!status[i]) {
|
||||
LOG_INFO(Service_CAM, "Successfully got handler {}", i);
|
||||
status[i] = true;
|
||||
loaded.emplace(camera_name, handlers[i]);
|
||||
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(
|
||||
const std::shared_ptr<Camera::QtMultimediaCameraHandler>& handler) {
|
||||
for (std::size_t i = 0; i < handlers.size(); i++) {
|
||||
if (handlers[i] == handler) {
|
||||
LOG_INFO(Service_CAM, "Successfully released handler {}", i);
|
||||
status[i] = false;
|
||||
handlers[i]->started = false;
|
||||
for (auto it = loaded.begin(); it != loaded.end(); it++) {
|
||||
if (it->second == handlers[i]) {
|
||||
loaded.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
void QtMultimediaCameraHandlerFactory::PauseCameras() {
|
||||
LOG_INFO(Service_CAM, "Pausing all cameras");
|
||||
for (auto& handler_pair : handlers) {
|
||||
auto handler = handler_pair.second.lock();
|
||||
if (handler && handler->IsActive()) {
|
||||
handler->PauseCapture();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QtMultimediaCameraHandler::CreateCamera(const std::string& camera_name) {
|
||||
QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
|
||||
for (const QCameraInfo& cameraInfo : cameras) {
|
||||
if (cameraInfo.deviceName().toStdString() == camera_name)
|
||||
camera = std::make_unique<QCamera>(cameraInfo);
|
||||
void QtMultimediaCameraHandlerFactory::ResumeCameras() {
|
||||
LOG_INFO(Service_CAM, "Resuming all cameras");
|
||||
for (auto& handler_pair : handlers) {
|
||||
auto handler = handler_pair.second.lock();
|
||||
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>();
|
||||
}
|
||||
settings.setMinimumFrameRate(30);
|
||||
settings.setMaximumFrameRate(30);
|
||||
camera->setViewfinder(&camera_surface);
|
||||
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);
|
||||
}
|
||||
camera_surface = std::make_unique<QVideoSink>();
|
||||
capture_session.setVideoSink(camera_surface.get());
|
||||
capture_session.setCamera(camera.get());
|
||||
}
|
||||
|
||||
void QtMultimediaCameraHandler::StopCamera() {
|
||||
camera->stop();
|
||||
started = false;
|
||||
QtMultimediaCameraHandler::~QtMultimediaCameraHandler() {
|
||||
StopCapture();
|
||||
}
|
||||
|
||||
void QtMultimediaCameraHandler::StartCamera() {
|
||||
void QtMultimediaCameraHandler::StartCapture() {
|
||||
if (!camera->isActive()) {
|
||||
#if defined(__APPLE__)
|
||||
if (!AppleAuthorization::CheckAuthorizationForCamera()) {
|
||||
LOG_ERROR(Service_CAM, "Unable to start camera due to lack of authorization");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
camera->setViewfinderSettings(settings);
|
||||
camera->start();
|
||||
started = true;
|
||||
}
|
||||
paused = false;
|
||||
}
|
||||
|
||||
bool QtMultimediaCameraHandler::CameraAvailable() const {
|
||||
return camera && camera->isAvailable();
|
||||
}
|
||||
|
||||
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;
|
||||
void QtMultimediaCameraHandler::StopCapture() {
|
||||
if (camera->isActive()) {
|
||||
camera->stop();
|
||||
}
|
||||
paused = false;
|
||||
}
|
||||
|
||||
} // namespace Camera
|
||||
|
@ -4,99 +4,113 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <QAbstractVideoSurface>
|
||||
#include <QCamera>
|
||||
#include <QCameraViewfinderSettings>
|
||||
#include <QImage>
|
||||
#include <QMutex>
|
||||
#include <QMediaCaptureSession>
|
||||
#include <QVideoSink>
|
||||
#include "citra_qt/camera/camera_util.h"
|
||||
#include "citra_qt/camera/qt_camera_base.h"
|
||||
#include "core/frontend/camera/interface.h"
|
||||
|
||||
class GMainWindow;
|
||||
|
||||
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:
|
||||
QList<QVideoFrame::PixelFormat> supportedPixelFormats(
|
||||
QAbstractVideoBuffer::HandleType) const override;
|
||||
bool present(const QVideoFrame&) override;
|
||||
explicit QtMultimediaCameraHandler(const std::string& camera_name);
|
||||
~QtMultimediaCameraHandler();
|
||||
|
||||
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:
|
||||
QMutex mutex;
|
||||
QImage current_frame;
|
||||
|
||||
friend class QtMultimediaCamera; // For access to current_frame
|
||||
std::unique_ptr<QCamera> camera;
|
||||
std::unique_ptr<QVideoSink> camera_surface;
|
||||
QMediaCaptureSession capture_session{};
|
||||
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.
|
||||
class QtMultimediaCamera final : public QtCameraInterface {
|
||||
public:
|
||||
QtMultimediaCamera(const std::string& camera_name, const Service::CAM::Flip& flip);
|
||||
~QtMultimediaCamera();
|
||||
void StartCapture() override;
|
||||
void StopCapture() override;
|
||||
void SetFrameRate(Service::CAM::FrameRate frame_rate) override;
|
||||
QImage QtReceiveFrame() override;
|
||||
bool IsPreviewAvailable() override;
|
||||
QtMultimediaCamera(const std::shared_ptr<QtMultimediaCameraHandler>& handler,
|
||||
const Service::CAM::Flip& flip)
|
||||
: QtCameraInterface(flip), handler(handler) {}
|
||||
|
||||
void StartCapture() override {
|
||||
handler->StartCapture();
|
||||
}
|
||||
|
||||
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:
|
||||
std::shared_ptr<QtMultimediaCameraHandler> handler;
|
||||
};
|
||||
|
||||
/// This class is only an interface. It just calls QtMultimediaCameraHandlerFactory.
|
||||
class QtMultimediaCameraFactory final : public QtCameraFactory {
|
||||
public:
|
||||
QtMultimediaCameraFactory(
|
||||
const std::shared_ptr<QtMultimediaCameraHandlerFactory>& handler_factory)
|
||||
: handler_factory(handler_factory) {}
|
||||
|
||||
std::unique_ptr<CameraInterface> Create(const std::string& config,
|
||||
const Service::CAM::Flip& flip) override;
|
||||
};
|
||||
|
||||
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();
|
||||
const Service::CAM::Flip& flip) override {
|
||||
return std::make_unique<QtMultimediaCamera>(handler_factory->Create(config), flip);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<QCamera> camera;
|
||||
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)
|
||||
std::shared_ptr<QtMultimediaCameraHandlerFactory> handler_factory;
|
||||
};
|
||||
|
||||
} // namespace Camera
|
||||
|
@ -2,16 +2,16 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <QCameraInfo>
|
||||
#include <QCameraDevice>
|
||||
#include <QDirIterator>
|
||||
#include <QFileDialog>
|
||||
#include <QImageReader>
|
||||
#include <QMediaDevices>
|
||||
#include <QMessageBox>
|
||||
#include <QWidget>
|
||||
#include "citra_qt/configuration/configure_camera.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/frontend/camera/factory.h"
|
||||
#include "core/frontend/camera/interface.h"
|
||||
#include "core/hle/service/cam/cam.h"
|
||||
#include "ui_configure_camera.h"
|
||||
|
||||
@ -32,9 +32,9 @@ ConfigureCamera::ConfigureCamera(QWidget* parent)
|
||||
camera_name = Settings::values.camera_name;
|
||||
camera_config = Settings::values.camera_config;
|
||||
camera_flip = Settings::values.camera_flip;
|
||||
QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
|
||||
for (const QCameraInfo& cameraInfo : cameras) {
|
||||
ui->system_camera->addItem(cameraInfo.deviceName());
|
||||
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
|
||||
for (const QCameraDevice& camera : cameras) {
|
||||
ui->system_camera->addItem(camera.description());
|
||||
}
|
||||
UpdateCameraMode();
|
||||
SetConfiguration();
|
||||
|
@ -579,7 +579,7 @@ void ConfigureInput::AutoMap() {
|
||||
void ConfigureInput::HandleClick(QPushButton* button,
|
||||
std::function<void(const Common::ParamPackage&)> new_input_setter,
|
||||
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->setFocus();
|
||||
|
||||
|
@ -234,6 +234,8 @@ void ConfigureMotionTouch::ConnectEvents() {
|
||||
&ConfigureMotionTouch::OnConfigureTouchCalibration);
|
||||
connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
|
||||
&ConfigureMotionTouch::OnConfigureTouchFromButton);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
|
||||
&ConfigureMotionTouch::ApplyConfiguration);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
|
||||
if (CanCloseDialog()) {
|
||||
reject();
|
||||
|
@ -324,22 +324,4 @@
|
||||
</layout>
|
||||
</widget>
|
||||
<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>
|
||||
|
@ -283,7 +283,7 @@ void ConfigureSystem::SetConfiguration() {
|
||||
|
||||
ui->combo_init_clock->setCurrentIndex(static_cast<u8>(Settings::values.init_clock.GetValue()));
|
||||
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);
|
||||
|
||||
long long init_time_offset = Settings::values.init_time_offset.GetValue();
|
||||
@ -406,7 +406,7 @@ void ConfigureSystem::ApplyConfiguration() {
|
||||
|
||||
Settings::values.init_clock =
|
||||
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_days = ui->edit_init_time_offset_days->value() * 86400;
|
||||
|
@ -509,7 +509,8 @@ void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
|
||||
if (!coord_label) {
|
||||
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) {
|
||||
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
|
||||
} else {
|
||||
@ -527,7 +528,8 @@ void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
|
||||
if (event->button() != Qt::MouseButton::LeftButton) {
|
||||
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) {
|
||||
emit DotAdded(*pos);
|
||||
}
|
||||
@ -543,7 +545,7 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
|
||||
emit DotSelected(obj->property(PropId).toInt());
|
||||
|
||||
drag_state.dot = qobject_cast<QLabel*>(obj);
|
||||
drag_state.start_pos = mouse_event->globalPos();
|
||||
drag_state.start_pos = mouse_event->globalPosition().toPoint();
|
||||
return true;
|
||||
}
|
||||
case QEvent::Type::MouseMove: {
|
||||
@ -552,14 +554,13 @@ bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
|
||||
}
|
||||
const auto mouse_event = static_cast<QMouseEvent*>(event);
|
||||
if (!drag_state.active) {
|
||||
drag_state.active =
|
||||
(mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >=
|
||||
QApplication::startDragDistance();
|
||||
drag_state.active = (mouse_event->globalPosition().toPoint() - drag_state.start_pos)
|
||||
.manhattanLength() >= QApplication::startDragDistance();
|
||||
if (!drag_state.active) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
auto current_pos = mapFromGlobal(mouse_event->globalPos());
|
||||
auto current_pos = mapFromGlobal(mouse_event->globalPosition().toPoint());
|
||||
current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(),
|
||||
contentsMargins().left() + contentsRect().width() - 1));
|
||||
current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
|
||||
|
@ -40,8 +40,9 @@ void SurfacePicture::mousePressEvent(QMouseEvent* event) {
|
||||
}
|
||||
|
||||
if (surface_widget) {
|
||||
surface_widget->Pick(event->x() * pixmap.width() / width(),
|
||||
event->y() * pixmap.height() / height());
|
||||
const auto pos = event->position().toPoint();
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
event->accept();
|
||||
}
|
||||
|
||||
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);
|
||||
event->accept();
|
||||
}
|
||||
|
||||
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->accept();
|
||||
}
|
||||
|
@ -239,7 +239,8 @@ void GameList::OnTextChanged(const QString& new_text) {
|
||||
file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
|
||||
file_title;
|
||||
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);
|
||||
++result_count;
|
||||
} 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
|
||||
// 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
|
||||
constexpr int LIMIT_WATCH_DIRECTORIES = 5000;
|
||||
constexpr qsizetype LIMIT_WATCH_DIRECTORIES = 5000;
|
||||
constexpr int SLICE_SIZE = 25;
|
||||
int len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES);
|
||||
for (int i = 0; i < len; i += SLICE_SIZE) {
|
||||
const qsizetype len = std::min(watch_list.length(), LIMIT_WATCH_DIRECTORIES);
|
||||
for (qsizetype i = 0; i < len; i += SLICE_SIZE) {
|
||||
watcher->addPaths(watch_list.mid(i, i + SLICE_SIZE));
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ void HotkeyRegistry::SaveHotkeys() {
|
||||
void HotkeyRegistry::LoadHotkeys() {
|
||||
// Make sure NOT to use a reference here because it would become invalid once we call
|
||||
// beginGroup()
|
||||
for (auto shortcut : UISettings::values.shortcuts) {
|
||||
for (const auto shortcut : UISettings::values.shortcuts) {
|
||||
Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
|
||||
if (!shortcut.shortcut.keyseq.isEmpty()) {
|
||||
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];
|
||||
|
||||
if (!hk.shortcut) {
|
||||
|
@ -47,7 +47,7 @@ public:
|
||||
* will be the same. Thus, you shouldn't rely on the caller really being the
|
||||
* 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.
|
||||
|
@ -198,7 +198,7 @@ void LoadingScreen::OnLoadProgress(VideoCore::LoadCallbackStage stage, std::size
|
||||
|
||||
void LoadingScreen::paintEvent(QPaintEvent* event) {
|
||||
QStyleOption opt;
|
||||
opt.init(this);
|
||||
opt.initFrom(this);
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
QWidget::paintEvent(event);
|
||||
|
@ -73,5 +73,3 @@ private:
|
||||
std::chrono::duration<double> rolling_average = {};
|
||||
bool eta_shown = false;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(VideoCore::LoadCallbackStage);
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include <clocale>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <QDesktopWidget>
|
||||
#include <QFileDialog>
|
||||
#include <QFutureWatcher>
|
||||
#include <QLabel>
|
||||
@ -200,6 +199,11 @@ GMainWindow::GMainWindow()
|
||||
qRegisterMetaType<std::size_t>("std::size_t");
|
||||
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();
|
||||
|
||||
Pica::g_debug_context = Pica::DebugContext::Construct();
|
||||
@ -647,7 +651,7 @@ void GMainWindow::ShowUpdaterWidgets() {
|
||||
|
||||
void GMainWindow::SetDefaultUIGeometry() {
|
||||
// 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 h = screenRect.height() / 2;
|
||||
@ -1284,8 +1288,6 @@ void GMainWindow::ShutdownGame() {
|
||||
|
||||
discord_rpc->Update();
|
||||
|
||||
Camera::QtMultimediaCameraHandler::ReleaseHandlers();
|
||||
|
||||
// The emulation is stopped, so closing the window or not does not matter anymore
|
||||
disconnect(render_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() {
|
||||
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++) {
|
||||
const QString text = QStringLiteral("&%1. %2").arg(i + 1).arg(
|
||||
@ -1648,7 +1650,7 @@ void GMainWindow::InstallCIA(QStringList filepaths) {
|
||||
progress_bar->show();
|
||||
progress_bar->setMaximum(INT_MAX);
|
||||
|
||||
QtConcurrent::run([&, filepaths] {
|
||||
(void)QtConcurrent::run([&, filepaths] {
|
||||
Service::AM::InstallStatus status;
|
||||
const auto cia_progress = [&](std::size_t written, std::size_t total) {
|
||||
emit UpdateProgress(written, total);
|
||||
@ -1724,7 +1726,7 @@ void GMainWindow::OnMenuRecentFile() {
|
||||
}
|
||||
|
||||
void GMainWindow::OnStartGame() {
|
||||
Camera::QtMultimediaCameraHandler::ResumeCameras();
|
||||
qt_cameras->ResumeCameras();
|
||||
|
||||
PreventOSSleep();
|
||||
|
||||
@ -1751,7 +1753,7 @@ void GMainWindow::OnRestartGame() {
|
||||
|
||||
void GMainWindow::OnPauseGame() {
|
||||
emu_thread->SetRunning(false);
|
||||
Camera::QtMultimediaCameraHandler::StopCameras();
|
||||
qt_cameras->PauseCameras();
|
||||
|
||||
UpdateMenuState();
|
||||
AllowOSSleep();
|
||||
@ -2690,7 +2692,7 @@ void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
|
||||
#undef main
|
||||
#endif
|
||||
|
||||
static void SetHighDPIAttributes() {
|
||||
static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() {
|
||||
#ifdef _WIN32
|
||||
// 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.
|
||||
@ -2703,40 +2705,34 @@ static void SetHighDPIAttributes() {
|
||||
// Get the current screen geometry.
|
||||
const QScreen* primary_screen = QGuiApplication::primaryScreen();
|
||||
if (primary_screen == nullptr) {
|
||||
return;
|
||||
return Qt::HighDpiScaleFactorRoundingPolicy::PassThrough;
|
||||
}
|
||||
|
||||
const QRect screen_rect = primary_screen->geometry();
|
||||
const int real_width = screen_rect.width();
|
||||
const int real_height = screen_rect.height();
|
||||
const float real_ratio = primary_screen->logicalDotsPerInch() / 96.0f;
|
||||
const qreal real_ratio = primary_screen->devicePixelRatio();
|
||||
const qreal real_width = std::trunc(screen_rect.width() * real_ratio);
|
||||
const qreal real_height = std::trunc(screen_rect.height() * real_ratio);
|
||||
|
||||
// Recommended minimum width and height for proper window fit.
|
||||
// Any screen with a lower resolution than this will still have a scale of 1.
|
||||
constexpr float minimum_width = 1350.0f;
|
||||
constexpr float minimum_height = 900.0f;
|
||||
constexpr qreal minimum_width = 1350.0;
|
||||
constexpr qreal minimum_height = 900.0;
|
||||
|
||||
const float width_ratio = std::max(1.0f, real_width / minimum_width);
|
||||
const float height_ratio = std::max(1.0f, real_height / minimum_height);
|
||||
const qreal width_ratio = std::max(1.0, real_width / minimum_width);
|
||||
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.
|
||||
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) {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::Round);
|
||||
return Qt::HighDpiScaleFactorRoundingPolicy::Round;
|
||||
} else {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::Floor);
|
||||
return Qt::HighDpiScaleFactorRoundingPolicy::Floor;
|
||||
}
|
||||
#else
|
||||
// Other OSes should be better than Windows at fractional scaling.
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(
|
||||
Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
|
||||
return Qt::HighDpiScaleFactorRoundingPolicy::PassThrough;
|
||||
#endif
|
||||
|
||||
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
@ -2748,16 +2744,14 @@ int main(int argc, char* argv[]) {
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("Citra team"));
|
||||
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
|
||||
|
||||
SetHighDPIAttributes();
|
||||
auto rounding_policy = GetHighDpiRoundingPolicy();
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
|
||||
|
||||
#ifdef __APPLE__
|
||||
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
||||
chdir(bin_path.c_str());
|
||||
#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_ShareOpenGLContexts);
|
||||
QApplication app(argc, argv);
|
||||
@ -2768,11 +2762,6 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
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
|
||||
Frontend::RegisterDefaultApplets();
|
||||
|
||||
|
@ -49,6 +49,10 @@ class RegistersWidget;
|
||||
class Updater;
|
||||
class WaitTreeWidget;
|
||||
|
||||
namespace Camera {
|
||||
class QtMultimediaCameraHandlerFactory;
|
||||
}
|
||||
|
||||
namespace DiscordRPC {
|
||||
class DiscordInterface;
|
||||
}
|
||||
@ -335,6 +339,8 @@ private:
|
||||
|
||||
HotkeyRegistry hotkey_registry;
|
||||
|
||||
std::shared_ptr<Camera::QtMultimediaCameraHandlerFactory> qt_cameras;
|
||||
|
||||
#ifdef __unix__
|
||||
QDBusObjectPath wake_lock{};
|
||||
#endif
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include <QComboBox>
|
||||
#include <QFuture>
|
||||
#include <QIntValidator>
|
||||
#include <QRegExpValidator>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "citra_qt/main.h"
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QRegExp>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QValidator>
|
||||
|
||||
@ -30,15 +30,17 @@ public:
|
||||
|
||||
private:
|
||||
/// 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}$"));
|
||||
QRegExpValidator room_name;
|
||||
QRegularExpression room_name_regex =
|
||||
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
|
||||
QRegExp nickname_regex = QRegExp(QStringLiteral("^[a-zA-Z0-9._- ]{4,20}$"));
|
||||
QRegExpValidator nickname;
|
||||
QRegularExpression nickname_regex =
|
||||
QRegularExpression(QStringLiteral("^[a-zA-Z0-9._\\- ]{4,20}$"));
|
||||
QRegularExpressionValidator nickname;
|
||||
|
||||
/// ipv4 / ipv6 / hostnames
|
||||
QRegExp ip_regex = QRegExp(QStringLiteral(
|
||||
QRegularExpression ip_regex = QRegularExpression(QStringLiteral(
|
||||
// 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]?)$|"
|
||||
// IPv6 regex
|
||||
@ -59,7 +61,7 @@ private:
|
||||
"\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?$|"
|
||||
// Hostname regex
|
||||
"^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}$"));
|
||||
QRegExpValidator ip;
|
||||
QRegularExpressionValidator ip;
|
||||
|
||||
/// port must be between 0 and 65535
|
||||
QIntValidator port;
|
||||
|
@ -30,7 +30,7 @@
|
||||
|
||||
#include <cstdlib>
|
||||
#include <QLineEdit>
|
||||
#include <QRegExpValidator>
|
||||
#include <QRegularExpression>
|
||||
#include "citra_qt/util/spinbox.h"
|
||||
#include "common/assert.h"
|
||||
|
||||
@ -244,14 +244,15 @@ QValidator::State CSpinBox::validate(QString& input, int& pos) const {
|
||||
}
|
||||
|
||||
// Match string
|
||||
QRegExp num_regexp(regexp);
|
||||
QRegularExpression num_regexp(QRegularExpression::anchoredPattern(regexp));
|
||||
int num_pos = strpos;
|
||||
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;
|
||||
|
||||
sub_input = sub_input.left(num_regexp.matchedLength());
|
||||
sub_input = sub_input.left(match.capturedLength());
|
||||
bool ok;
|
||||
qint64 val = sub_input.toLongLong(&ok, base);
|
||||
|
||||
@ -263,7 +264,7 @@ QValidator::State CSpinBox::validate(QString& input, int& pos) const {
|
||||
return QValidator::Invalid;
|
||||
|
||||
// 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) {
|
||||
return QValidator::Invalid;
|
||||
|
@ -44,11 +44,11 @@ std::vector<u16> SMDH::GetIcon(bool large) const {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -26,9 +26,9 @@ struct SMDH {
|
||||
INSERT_PADDING_BYTES(2);
|
||||
|
||||
struct Title {
|
||||
std::array<u16, 0x40> short_title;
|
||||
std::array<u16, 0x80> long_title;
|
||||
std::array<u16, 0x40> publisher;
|
||||
std::array<char16_t, 0x40> short_title;
|
||||
std::array<char16_t, 0x80> long_title;
|
||||
std::array<char16_t, 0x40> publisher;
|
||||
};
|
||||
std::array<Title, 16> titles;
|
||||
|
||||
@ -88,14 +88,14 @@ struct SMDH {
|
||||
* @param language title language
|
||||
* @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
|
||||
* @param language title language
|
||||
* @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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user