diff --git a/CMakeLists.txt b/CMakeLists.txt index df7da6311..00a101324 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,13 +58,22 @@ option(ENABLE_CCACHE "Speed up recompilation by caching previous compilations" O # Platform-specific options if(ANDROID) + set(ANDROID_TARGET_SDK_VERSION "33" CACHE STRING "Android target SDK version") + set(ANDROIDDEPLOYQT_OPTIONS "" CACHE STRING "Additional androiddeployqt options separated by semi-colon") + set(ANDROID_GRADLE_PROPERTIES "" CACHE STRING "Additional Gradle properties separated by semi-colon") + set(ENABLE_STATIC_LIBS ON) - set(ENABLE_LAUNCHER OFF) + set(ENABLE_LAUNCHER ON) else() option(ENABLE_STATIC_LIBS "Build library and all components such as AI statically" OFF) option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) endif() +if(APPLE_IOS) + set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix") + set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen") +endif() + if(APPLE_IOS OR ANDROID) set(ENABLE_MONOLITHIC_INSTALL OFF) set(ENABLE_SINGLE_APP_BUILD ON) @@ -100,11 +109,6 @@ if (ENABLE_STATIC_LIBS AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() -if(APPLE_IOS) - set(BUNDLE_IDENTIFIER_PREFIX "" CACHE STRING "Bundle identifier prefix") - set(APP_DISPLAY_NAME "VCMI" CACHE STRING "App name on the home screen") -endif() - if(ENABLE_COLORIZED_COMPILER_OUTPUT) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") add_compile_options(-fcolor-diagnostics) @@ -147,10 +151,6 @@ set(CMAKE_MODULE_PATH ${CMAKE_HOME_DIRECTORY}/cmake_modules ${PROJECT_SOURCE_DIR include(VCMIUtils) include(VersionDefinition) -if(ANDROID) - set(VCMI_VERSION "${APP_SHORT_VERSION}") - configure_file("android/GeneratedVersion.java.in" "${CMAKE_SOURCE_DIR}/android/vcmi-app/src/main/java/eu/vcmi/vcmi/util/GeneratedVersion.java" @ONLY) -endif() vcmi_print_important_variables() @@ -575,8 +575,12 @@ elseif(APPLE) endif() elseif(ANDROID) include(GNUInstallDirs) - set(LIB_DIR "jniLibs/${ANDROID_ABI}") - set(DATA_DIR "assets") + set(LIB_DIR "libs/${ANDROID_ABI}") + + # required by Qt + set(androidPackageSourceDir "${CMAKE_SOURCE_DIR}/android") + set(androidQtBuildDir "${CMAKE_BINARY_DIR}/android-build") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${androidQtBuildDir}/${LIB_DIR}") else() # includes lib path which determines where to install shared libraries (either /lib or /lib64) include(GNUInstallDirs) @@ -621,6 +625,13 @@ else() set(SCRIPTING_LIB_DIR "${LIB_DIR}/scripting") endif() +# common Qt paths +if(ENABLE_LAUNCHER OR ENABLE_EDITOR) + get_target_property(qmakePath Qt${QT_VERSION_MAJOR}::qmake IMPORTED_LOCATION) + get_filename_component(qtDir "${qmakePath}/../../" ABSOLUTE) + set(qtBinDir "${qtDir}/bin") +endif() + ####################################### # Add subdirectories # ####################################### @@ -682,32 +693,15 @@ endif() ####################################### if(ANDROID) + string(REPLACE ";" "\n" ANDROID_GRADLE_PROPERTIES_MULTILINE "${ANDROID_GRADLE_PROPERTIES}") + file(WRITE "${androidPackageSourceDir}/vcmi-app/gradle.properties" "signingRoot=${CMAKE_SOURCE_DIR}/CI/android\n${ANDROID_GRADLE_PROPERTIES_MULTILINE}") + if(ANDROID_STL MATCHES "_shared$") set(stlLibName "${CMAKE_SHARED_LIBRARY_PREFIX}${ANDROID_STL}${CMAKE_SHARED_LIBRARY_SUFFIX}") install(FILES "${CMAKE_SYSROOT}/usr/lib/${ANDROID_SYSROOT_LIB_SUBDIR}/${stlLibName}" DESTINATION ${LIB_DIR} ) endif() - - # zip internal assets - 'config' and 'Mods' dirs, save md5 of the zip - install(CODE " - cmake_path(ABSOLUTE_PATH CMAKE_INSTALL_PREFIX - OUTPUT_VARIABLE absolute_install_prefix - ) - set(absolute_data_dir \"\${absolute_install_prefix}/${DATA_DIR}\") - file(MAKE_DIRECTORY \"\${absolute_data_dir}\") - - set(internal_data_zip \"\${absolute_data_dir}/internalData.zip\") - execute_process(COMMAND - \"${CMAKE_COMMAND}\" -E tar c \"\${internal_data_zip}\" --format=zip -- config Mods - WORKING_DIRECTORY \"${CMAKE_SOURCE_DIR}\" - ) - - file(MD5 \"\${internal_data_zip}\" internal_data_zip_md5) - file(WRITE \"\${absolute_data_dir}/internalDataHash.txt\" - \${internal_data_zip_md5} - ) - ") else() install(DIRECTORY config DESTINATION ${DATA_DIR}) if (ENABLE_CLIENT OR ENABLE_SERVER) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 2357b445e..93d5ab564 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -395,6 +395,9 @@ assign_source_group(${client_SRCS} ${client_HEADERS} VCMI_client.rc) if(ANDROID) add_library(vcmiclient SHARED ${client_SRCS} ${client_HEADERS}) + set_target_properties(vcmiclient PROPERTIES + OUTPUT_NAME "vcmiclient_${ANDROID_ABI}" # required by Qt + ) else() add_executable(vcmiclient ${client_SRCS} ${client_HEADERS}) endif() @@ -517,11 +520,19 @@ if(APPLE_IOS) ) install(TARGETS vcmiclient DESTINATION Payload COMPONENT app) # for ipa generation with cpack elseif(ANDROID) - vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}/${LIB_DIR}") - add_custom_command(TARGET vcmiclient POST_BUILD - COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --config "$" --prefix "${CMAKE_SOURCE_DIR}/android/vcmi-app/src/main" + find_program(androidDeployQt androiddeployqt + PATHS "${qtBinDir}" ) - install(TARGETS vcmiclient DESTINATION ${LIB_DIR}) + vcmi_install_conan_deps("\${CMAKE_INSTALL_PREFIX}/${LIB_DIR}") + + add_custom_target(android_deploy ALL + COMMAND ${CMAKE_COMMAND} --install "${CMAKE_BINARY_DIR}" --config "$" --prefix "${androidQtBuildDir}" + COMMAND "${androidDeployQt}" --input "${CMAKE_BINARY_DIR}/androiddeployqt.json" --output "${androidQtBuildDir}" --android-platform "android-${ANDROID_TARGET_SDK_VERSION}" --verbose $<$>:--release> ${ANDROIDDEPLOYQT_OPTIONS} + COMMAND_EXPAND_LISTS + VERBATIM + COMMENT "Create android package" + ) + add_dependencies(android_deploy vcmiclient) else() install(TARGETS vcmiclient DESTINATION ${BIN_DIR}) endif() diff --git a/cmake_modules/VCMIUtils.cmake b/cmake_modules/VCMIUtils.cmake index 7ed85ab9d..ca0726fe4 100644 --- a/cmake_modules/VCMIUtils.cmake +++ b/cmake_modules/VCMIUtils.cmake @@ -7,16 +7,22 @@ macro(vcmi_set_output_dir name dir) # Multi-config builds for Visual Studio, Xcode foreach(OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES}) - string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIGUPPERCASE) - set_target_properties(${name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIGUPPERCASE} ${CMAKE_BINARY_DIR}/bin/${OUTPUTCONFIG}/${dir}) - set_target_properties(${name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIGUPPERCASE} ${CMAKE_BINARY_DIR}/bin/${OUTPUTCONFIG}/${dir}) - set_target_properties(${name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIGUPPERCASE} ${CMAKE_BINARY_DIR}/bin/${OUTPUTCONFIG}/${dir}) + string(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIGUPPERCASE) + set_target_properties(${name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIGUPPERCASE} ${CMAKE_BINARY_DIR}/bin/${OUTPUTCONFIG}/${dir}) + if(ANDROID) + set_target_properties(${name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIGUPPERCASE} "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + else() + set_target_properties(${name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIGUPPERCASE} ${CMAKE_BINARY_DIR}/bin/${OUTPUTCONFIG}/${dir}) + endif() + set_target_properties(${name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIGUPPERCASE} ${CMAKE_BINARY_DIR}/bin/${OUTPUTCONFIG}/${dir}) endforeach() # Generic no-config case for Makefiles, Ninja. # This is what Qt Creator is using set_target_properties(${name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/${dir}) - set_target_properties(${name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/${dir}) + if(NOT ANDROID) + set_target_properties(${name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/${dir}) + endif() set_target_properties(${name} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/${dir}) endmacro() diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 301cbe3fd..70e575b6e 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -21,7 +21,13 @@ set(launcher_SRCS launcherdirs.cpp jsonutils.cpp updatedialog_moc.cpp + prepare.cpp ) +if(APPLE_IOS) + list(APPEND launcher_SRCS + ios/launchGame.m + ) +endif() set(launcher_HEADERS StdInc.h @@ -40,7 +46,8 @@ set(launcher_HEADERS jsonutils.h updatedialog_moc.h main.h - helper.cpp + helper.h + prepare.h ) set(launcher_FORMS @@ -53,30 +60,60 @@ set(launcher_FORMS updatedialog_moc.ui ) -set(launcher_TS - translation/chinese.ts - translation/czech.ts - translation/english.ts - translation/french.ts - translation/german.ts - translation/polish.ts - translation/portuguese.ts - translation/russian.ts - translation/spanish.ts - translation/ukrainian.ts - translation/vietnamese.ts +set(launcher_RESOURCES + resources.qrc ) -if(APPLE_IOS) - list(APPEND launcher_SRCS - ios/main.m - ) +set(translationsDir "translation") +set(launcher_TS + "${translationsDir}/chinese.ts" + "${translationsDir}/czech.ts" + "${translationsDir}/english.ts" + "${translationsDir}/french.ts" + "${translationsDir}/german.ts" + "${translationsDir}/polish.ts" + "${translationsDir}/portuguese.ts" + "${translationsDir}/russian.ts" + "${translationsDir}/spanish.ts" + "${translationsDir}/ukrainian.ts" + "${translationsDir}/vietnamese.ts" +) +if(ENABLE_TRANSLATIONS) + if(TARGET Qt5::Core) + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${translationsDir}") + set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION "${translationsDir}") + qt5_add_translation(launcher_QM ${launcher_TS}) + + set(translationsResource "${CMAKE_CURRENT_BINARY_DIR}/translations.qrc") + list(APPEND launcher_RESOURCES "${translationsResource}") + + set(rccQmFiles "") + foreach(qmFile ${launcher_QM}) + string(APPEND rccQmFiles "${qmFile}\n") + endforeach() + file(WRITE "${translationsResource}" +" + + +${rccQmFiles} + +" + ) + endif() endif() -assign_source_group(${launcher_SRCS} ${launcher_HEADERS} VCMI_launcher.rc) +if(WIN32) + set(launcher_ICON VCMI_launcher.rc) +endif() -# Tell CMake to run moc when necessary: +assign_source_group(${launcher_SRCS} ${launcher_HEADERS} ${launcher_RESOURCES} ${launcher_TS} ${launcher_ICON}) + +# TODO: enabling AUTORCC breaks msvc build on CI set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +if(NOT (MSVC AND "$ENV{GITHUB_ACTIONS}" STREQUAL true)) + set(CMAKE_AUTORCC ON) +endif() if(POLICY CMP0071) cmake_policy(SET CMP0071 NEW) @@ -86,38 +123,55 @@ endif() # to always look for includes there: set(CMAKE_INCLUDE_CURRENT_DIR ON) -if(TARGET Qt6::Core) - qt_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) +if(ENABLE_SINGLE_APP_BUILD OR ANDROID) + add_library(vcmilauncher OBJECT ${launcher_QM}) else() - qt5_wrap_ui(launcher_UI_HEADERS ${launcher_FORMS}) - if(ENABLE_TRANSLATIONS) - set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/translation) - qt5_add_translation( launcher_QM ${launcher_TS} ) - endif() + add_executable(vcmilauncher WIN32 ${launcher_QM} ${launcher_ICON}) endif() - -if(WIN32) - set(launcher_ICON VCMI_launcher.rc) -endif() - -if(ENABLE_SINGLE_APP_BUILD) - add_library(vcmilauncher STATIC ${launcher_QM} ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS}) -else() - add_executable(vcmilauncher WIN32 ${launcher_QM} ${launcher_SRCS} ${launcher_HEADERS} ${launcher_UI_HEADERS} ${launcher_ICON}) -endif() - -if(TARGET Qt6::Core) - if(ENABLE_TRANSLATIONS) - set_source_files_properties(${launcher_TS} PROPERTIES OUTPUT_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/translation) +if(ENABLE_TRANSLATIONS) + if(TARGET Qt6::Core) qt_add_translations(vcmilauncher TS_FILES ${launcher_TS} - QM_FILES_OUTPUT_VARIABLE launcher_QM + RESOURCE_PREFIX "/${translationsDir}" INCLUDE_DIRECTORIES ${CMAKE_CURRENT_BINARY_DIR}) endif() endif() +if(ANDROID) + get_target_property(rccPath Qt${QT_VERSION_MAJOR}::rcc IMPORTED_LOCATION) + get_filename_component(qtDir "${rccPath}/../../" ABSOLUTE) + set(qtDir "${qtDir}" PARENT_SCOPE) + + function(generate_binary_resource resourceName resourceDir) + file(CREATE_LINK "${resourceDir}" "${CMAKE_CURRENT_BINARY_DIR}/${resourceName}" + COPY_ON_ERROR + SYMBOLIC + ) + set(qrcFile "${CMAKE_CURRENT_BINARY_DIR}/${resourceName}.qrc") + execute_process(COMMAND + "${rccPath}" --project + WORKING_DIRECTORY "${resourceDir}" + OUTPUT_VARIABLE rccOutput + ) + # add parent directory + string(REPLACE "." "${resourceName}" rccOutput "${rccOutput}") + file(WRITE "${qrcFile}" "${rccOutput}") + endfunction() + + generate_binary_resource("config" "${CMAKE_SOURCE_DIR}/config") + list(APPEND launcher_RESOURCES "${CMAKE_CURRENT_BINARY_DIR}/config.qrc") + generate_binary_resource("Mods" "${CMAKE_SOURCE_DIR}/Mods") + list(APPEND launcher_RESOURCES "${CMAKE_CURRENT_BINARY_DIR}/Mods.qrc") +endif() + +target_sources(vcmilauncher PRIVATE + ${launcher_SRCS} + ${launcher_HEADERS} + ${launcher_RESOURCES} +) + if(WIN32) set_target_properties(vcmilauncher PROPERTIES @@ -139,7 +193,9 @@ if(APPLE) set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmilauncher) endif() -if (NOT APPLE_IOS AND NOT ANDROID) +if(ANDROID) + target_link_libraries(vcmilauncher Qt${QT_VERSION_MAJOR}::AndroidExtras) +elseif(NOT APPLE_IOS) target_link_libraries(vcmilauncher SDL2::SDL2) endif() @@ -155,9 +211,7 @@ if(ENABLE_INNOEXTRACT) endif() if(APPLE_IOS) - set(RESOURCES_DESTINATION ${DATA_DIR}) - - # TODO: remove after fixing Conan's Qt recipe + # TODO: remove after switching prebuilt deps to a newer Conan's Qt recipe if(XCODE_VERSION VERSION_GREATER_EQUAL 14.0) target_link_libraries(vcmilauncher "-framework IOKit") endif() @@ -167,22 +221,19 @@ if(APPLE_IOS) file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/QIOSIntegrationPlugin.h "#include \nQ_IMPORT_PLUGIN(QIOSIntegrationPlugin)" ) - # target_include_directories(vcmilauncher PRIVATE ${CMAKE_BINARY_DIR}) target_link_libraries(vcmilauncher Qt${QT_VERSION_MAJOR}::QIOSIntegrationPlugin qt::QIOSIntegrationPlugin ) endif() -else() - set(RESOURCES_DESTINATION ${DATA_DIR}/launcher) - - # Link to build directory for easier debugging - add_custom_command(TARGET vcmilauncher POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${CMAKE_SOURCE_DIR}/launcher/icons ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/icons - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/cmake_modules/create_link.cmake ${CMAKE_CURRENT_BINARY_DIR}/translation ${CMAKE_BINARY_DIR}/bin/${CMAKE_CFG_INTDIR}/launcher/translation +elseif(ANDROID) + set(androidSdkDir "$ENV{ANDROID_HOME}") + configure_file( + "${androidPackageSourceDir}/androiddeployqt.json.in" + "${CMAKE_BINARY_DIR}/androiddeployqt.json" + @ONLY ) - +else() install(TARGETS vcmilauncher DESTINATION ${BIN_DIR}) # Install icons and desktop file on Linux @@ -191,8 +242,3 @@ else() install(FILES "eu.vcmi.VCMI.metainfo.xml" DESTINATION share/metainfo) endif() endif() - -install(DIRECTORY icons DESTINATION ${RESOURCES_DESTINATION}) -if(ENABLE_TRANSLATIONS) - install(FILES ${launcher_QM} DESTINATION ${RESOURCES_DESTINATION}/translation) -endif() diff --git a/launcher/firstLaunch/firstlaunch_moc.cpp b/launcher/firstLaunch/firstlaunch_moc.cpp index 7a1da8095..1c1e86386 100644 --- a/launcher/firstLaunch/firstlaunch_moc.cpp +++ b/launcher/firstLaunch/firstlaunch_moc.cpp @@ -363,7 +363,7 @@ void FirstLaunchView::extractGogData() void FirstLaunchView::copyHeroesData(const QString & path, bool move) { - QDir sourceRoot = QDir(path); + QDir sourceRoot{path}; if(path.isEmpty()) sourceRoot.setPath(QFileDialog::getExistingDirectory(this, {}, {}, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks)); @@ -387,9 +387,10 @@ void FirstLaunchView::copyHeroesData(const QString & path, bool move) QStringList dirMaps = sourceRoot.entryList({"maps"}, QDir::Filter::Dirs); QStringList dirMp3 = sourceRoot.entryList({"mp3"}, QDir::Filter::Dirs); + const auto noDataMessage = tr("Failed to detect valid Heroes III data in chosen directory.\nPlease select directory with installed Heroes III data."); if(dirData.empty()) { - QMessageBox::critical(this, tr("Heroes III data not found!"), tr("Failed to detect valid Heroes III data in chosen directory.\nPlease select directory with installed Heroes III data.")); + QMessageBox::critical(this, tr("Heroes III data not found!"), noDataMessage); return; } @@ -403,7 +404,7 @@ void FirstLaunchView::copyHeroesData(const QString & path, bool move) if (roeFiles.empty()) { // Directory structure is correct (Data/Maps/Mp3) but no .lod archives that should be present in any install - QMessageBox::critical(this, tr("Heroes III data not found!"), tr("Failed to detect valid Heroes III data in chosen directory.\nPlease select directory with installed Heroes III data.")); + QMessageBox::critical(this, tr("Heroes III data not found!"), noDataMessage); return; } diff --git a/launcher/firstLaunch/firstlaunch_moc.h b/launcher/firstLaunch/firstlaunch_moc.h index 3bd3be3cb..3edf2db71 100644 --- a/launcher/firstLaunch/firstlaunch_moc.h +++ b/launcher/firstLaunch/firstlaunch_moc.h @@ -96,5 +96,4 @@ private slots: private: Ui::FirstLaunchView * ui; - }; diff --git a/launcher/ios/main.m b/launcher/ios/launchGame.m similarity index 100% rename from launcher/ios/main.m rename to launcher/ios/launchGame.m diff --git a/launcher/main.cpp b/launcher/main.cpp index 7b4323cfe..90afbfb57 100644 --- a/launcher/main.cpp +++ b/launcher/main.cpp @@ -10,24 +10,35 @@ #include "StdInc.h" #include "main.h" #include "mainwindow_moc.h" -#include "launcherdirs.h" +#include "prepare.h" #include "../lib/VCMIDirs.h" #include -#include -#include // Conan workaround https://github.com/conan-io/conan-center-index/issues/13332 #ifdef VCMI_IOS -#if __has_include("QIOSIntegrationPlugin.h") -#include "QIOSIntegrationPlugin.h" -#endif +# if __has_include("QIOSIntegrationPlugin.h") +# include "QIOSIntegrationPlugin.h" +# endif int argcForClient; char ** argvForClient; -#endif +#elif defined(VCMI_ANDROID) +# include +# include +#else +# include +# include +#endif // VCMI_IOS -int main(int argc, char * argv[]) +// android must export main explicitly to make it visible in the shared library +#ifdef VCMI_ANDROID +# define MAIN_EXPORT ELF_VISIBILITY +#else +# define MAIN_EXPORT +#endif // VCMI_ANDROID + +int MAIN_EXPORT main(int argc, char * argv[]) { int result; #ifdef VCMI_IOS @@ -35,7 +46,7 @@ int main(int argc, char * argv[]) #endif QApplication vcmilauncher(argc, argv); - CLauncherDirs::prepare(); + launcher::prepare(); MainWindow mainWindow; mainWindow.show(); @@ -53,7 +64,7 @@ void startGame(const QStringList & args) { logGlobal->warn("Starting game with the arguments: %s", args.join(" ").toStdString()); -#ifdef Q_OS_IOS +#ifdef VCMI_IOS static const char clientName[] = "vcmiclient"; argcForClient = args.size() + 1; //first argument is omitted argvForClient = new char*[argcForClient]; @@ -61,11 +72,13 @@ void startGame(const QStringList & args) strcpy(argvForClient[0], clientName); for(int i = 1; i < argcForClient; ++i) { - std::string s = args.at(i - 1).toStdString(); - argvForClient[i] = new char[s.size() + 1]; - strcpy(argvForClient[i], s.c_str()); + std::string s = args.at(i - 1).toStdString(); + argvForClient[i] = new char[s.size() + 1]; + strcpy(argvForClient[i], s.c_str()); } qApp->quit(); +#elif defined(VCMI_ANDROID) + QtAndroid::androidActivity().callMethod("onLaunchGameBtnPressed"); #else startExecutable(pathToQString(VCMIDirs::get().clientPath()), args); #endif @@ -78,7 +91,7 @@ void startEditor(const QStringList & args) #endif } -#ifndef Q_OS_IOS +#ifndef VCMI_MOBILE void startExecutable(QString name, const QStringList & args) { QProcess process; @@ -91,11 +104,9 @@ void startExecutable(QString name, const QStringList & args) else { QMessageBox::critical(qApp->activeWindow(), - "Error starting executable", - "Failed to start " + name + "\n" - "Reason: " + process.errorString(), - QMessageBox::Ok, - QMessageBox::Ok); + QObject::tr("Error starting executable"), + QObject::tr("Failed to start %1\nReason: %2").arg(name, process.errorString()) + ); } } #endif diff --git a/launcher/mainwindow_moc.cpp b/launcher/mainwindow_moc.cpp index 6f8e239d3..e089fd220 100644 --- a/launcher/mainwindow_moc.cpp +++ b/launcher/mainwindow_moc.cpp @@ -29,7 +29,7 @@ void MainWindow::load() // This is important on Mac for relative paths to work inside DMG. QDir::setCurrent(QApplication::applicationDirPath()); -#ifndef VCMI_IOS +#ifndef VCMI_MOBILE console = new CConsoleHandler(); #endif CBasicLogConfigurator logConfig(VCMIDirs::get().userLogsPath() / "VCMI_Launcher_log.txt", console); @@ -38,14 +38,6 @@ void MainWindow::load() CResourceHandler::initialize(); CResourceHandler::load("config/filesystem.json"); -#ifdef Q_OS_IOS - QDir::addSearchPath("icons", pathToQString(VCMIDirs::get().binaryPath() / "icons")); -#else - for(auto & string : VCMIDirs::get().dataPaths()) - QDir::addSearchPath("icons", pathToQString(string / "launcher" / "icons")); - QDir::addSearchPath("icons", pathToQString(VCMIDirs::get().userDataPath() / "launcher" / "icons")); -#endif - Helper::loadSettings(); } @@ -85,7 +77,15 @@ MainWindow::MainWindow(QWidget * parent) updateTranslation(); // load translation ui->setupUi(this); - + + setWindowIcon(QIcon{":/icons/menu-game.png"}); + ui->modslistButton->setIcon(QIcon{":/icons/menu-mods.png"}); + ui->settingsButton->setIcon(QIcon{":/icons/menu-settings.png"}); + ui->aboutButton->setIcon(QIcon{":/icons/about-project.png"}); + ui->startEditorButton->setIcon(QIcon{":/icons/menu-editor.png"}); + ui->startGameButton->setIcon(QIcon{":/icons/menu-game.png"}); + +#ifndef VCMI_MOBILE //load window settings QSettings s(Ui::teamName, Ui::appName); @@ -99,6 +99,7 @@ MainWindow::MainWindow(QWidget * parent) { move(position); } +#endif #ifndef ENABLE_EDITOR ui->startEditorButton->hide(); @@ -183,10 +184,12 @@ void MainWindow::changeEvent(QEvent *event) MainWindow::~MainWindow() { +#ifndef VCMI_MOBILE //save window settings QSettings s(Ui::teamName, Ui::appName); s.setValue("MainWindow/Size", size()); s.setValue("MainWindow/Position", pos()); +#endif delete ui; } @@ -231,32 +234,16 @@ void MainWindow::on_aboutButton_clicked() void MainWindow::updateTranslation() { #ifdef ENABLE_QT_TRANSLATIONS - std::string translationFile = settings["general"]["language"].String() + ".qm"; + const std::string translationFile = settings["general"]["language"].String() + ".qm"; logGlobal->info("Loading translation '%s'", translationFile); - QVector searchPaths; - -#ifdef Q_OS_IOS - searchPaths.push_back(pathToQString(VCMIDirs::get().binaryPath() / "translation" / translationFile)); -#else - for(auto const & string : VCMIDirs::get().dataPaths()) - searchPaths.push_back(pathToQString(string / "launcher" / "translation" / translationFile)); - searchPaths.push_back(pathToQString(VCMIDirs::get().userDataPath() / "launcher" / "translation" / translationFile)); -#endif - - for(auto const & string : boost::adaptors::reverse(searchPaths)) + if (!translator.load(QString{":/translation/%1"}.arg(translationFile.c_str()))) { - logGlobal->info("Searching for translation at '%s'", string.toStdString()); - if (translator.load(string)) - { - logGlobal->info("Translation found"); - if (!qApp->installTranslator(&translator)) - logGlobal->error("Failed to install translator"); - return; - } + logGlobal->error("Failed to load translation"); + return; } - logGlobal->error("Failed to find translation"); - + if (!qApp->installTranslator(&translator)) + logGlobal->error("Failed to install translator"); #endif } diff --git a/launcher/mainwindow_moc.ui b/launcher/mainwindow_moc.ui index 2f7a56934..aab701f2c 100644 --- a/launcher/mainwindow_moc.ui +++ b/launcher/mainwindow_moc.ui @@ -19,10 +19,6 @@ VCMI Launcher - - - icons:menu-game.pngicons:menu-game.png - 64 @@ -56,10 +52,6 @@ Mods - - - icons:menu-mods.pngicons:menu-mods.png - 64 @@ -106,10 +98,6 @@ Settings - - - icons:menu-settings.pngicons:menu-settings.png - 64 @@ -156,10 +144,6 @@ Help - - - icons:about-project.pngicons:about-project.png - 32 @@ -225,10 +209,6 @@ Map Editor - - - icons:menu-editor.pngicons:menu-editor.png - 32 @@ -278,10 +258,6 @@ Start game - - - icons:menu-game.pngicons:menu-game.png - 64 diff --git a/launcher/modManager/cmodlistmodel_moc.cpp b/launcher/modManager/cmodlistmodel_moc.cpp index bdc07e937..e4c95a863 100644 --- a/launcher/modManager/cmodlistmodel_moc.cpp +++ b/launcher/modManager/cmodlistmodel_moc.cpp @@ -14,11 +14,11 @@ namespace ModStatus { -static const QString iconDelete = "icons:mod-delete.png"; -static const QString iconDisabled = "icons:mod-disabled.png"; -static const QString iconDownload = "icons:mod-download.png"; -static const QString iconEnabled = "icons:mod-enabled.png"; -static const QString iconUpdate = "icons:mod-update.png"; +static const QString iconDelete = ":/icons/mod-delete.png"; +static const QString iconDisabled = ":/icons/mod-disabled.png"; +static const QString iconDownload = ":/icons/mod-download.png"; +static const QString iconEnabled = ":/icons/mod-enabled.png"; +static const QString iconUpdate = ":/icons/mod-update.png"; } CModListModel::CModListModel(QObject * parent) diff --git a/launcher/modManager/cmodlistview_moc.cpp b/launcher/modManager/cmodlistview_moc.cpp index 97315794f..704727045 100644 --- a/launcher/modManager/cmodlistview_moc.cpp +++ b/launcher/modManager/cmodlistview_moc.cpp @@ -130,6 +130,12 @@ CModListView::CModListView(QWidget * parent) setAcceptDrops(true); + ui->uninstallButton->setIcon(QIcon{":/icons/mod-delete.png"}); + ui->enableButton->setIcon(QIcon{":/icons/mod-enabled.png"}); + ui->disableButton->setIcon(QIcon{":/icons/mod-disabled.png"}); + ui->updateButton->setIcon(QIcon{":/icons/mod-update.png"}); + ui->installButton->setIcon(QIcon{":/icons/mod-download.png"}); + setupModModel(); setupFilterModel(); setupModsView(); @@ -393,14 +399,15 @@ void CModListView::selectMod(const QModelIndex & index) } else { - auto mod = modModel->getMod(index.data(ModRoles::ModNameRole).toString()); + const auto modName = index.data(ModRoles::ModNameRole).toString(); + auto mod = modModel->getMod(modName); ui->modInfoBrowser->setHtml(genModInfoText(mod)); ui->changelogBrowser->setHtml(genChangelogText(mod)); - bool hasInvalidDeps = !findInvalidDependencies(index.data(ModRoles::ModNameRole).toString()).empty(); - bool hasBlockingMods = !findBlockingMods(index.data(ModRoles::ModNameRole).toString()).empty(); - bool hasDependentMods = !findDependentMods(index.data(ModRoles::ModNameRole).toString(), true).empty(); + bool hasInvalidDeps = !findInvalidDependencies(modName).empty(); + bool hasBlockingMods = !findBlockingMods(modName).empty(); + bool hasDependentMods = !findDependentMods(modName, true).empty(); ui->disableButton->setVisible(mod.isEnabled()); ui->enableButton->setVisible(mod.isDisabled()); diff --git a/launcher/modManager/cmodlistview_moc.ui b/launcher/modManager/cmodlistview_moc.ui index 4cdbf2291..2bfd81e0c 100644 --- a/launcher/modManager/cmodlistview_moc.ui +++ b/launcher/modManager/cmodlistview_moc.ui @@ -423,10 +423,6 @@ hr { height: 1px; border-width: 0; } Uninstall - - - icons:mod-delete.pngicons:mod-delete.png - 20 @@ -458,10 +454,6 @@ hr { height: 1px; border-width: 0; } Enable - - - icons:mod-enabled.pngicons:mod-enabled.png - 20 @@ -493,10 +485,6 @@ hr { height: 1px; border-width: 0; } Disable - - - icons:mod-disabled.pngicons:mod-disabled.png - 20 @@ -528,10 +516,6 @@ hr { height: 1px; border-width: 0; } Update - - - icons:mod-update.pngicons:mod-update.png - 20 @@ -563,10 +547,6 @@ hr { height: 1px; border-width: 0; } Install - - - icons:mod-download.pngicons:mod-download.png - 20 diff --git a/launcher/modManager/cmodmanager.cpp b/launcher/modManager/cmodmanager.cpp index a3a4839c4..363cbe94d 100644 --- a/launcher/modManager/cmodmanager.cpp +++ b/launcher/modManager/cmodmanager.cpp @@ -20,6 +20,8 @@ #include "../jsonutils.h" #include "../launcherdirs.h" +#include + namespace { QString detectModArchive(QString path, QString modName, std::vector & filesToExtract) @@ -360,7 +362,7 @@ bool CModManager::removeModDir(QString path) if(!checkDir.cdUp() || QString::compare("Mods", checkDir.dirName(), Qt::CaseInsensitive)) return false; -#ifndef VCMI_IOS //ios applications are stored in the isolated container +#ifndef VCMI_MOBILE // ios and android applications are stored in the isolated container if(!checkDir.cdUp() || QString::compare("vcmi", checkDir.dirName(), Qt::CaseInsensitive)) return false; diff --git a/launcher/prepare.cpp b/launcher/prepare.cpp new file mode 100644 index 000000000..c57f703eb --- /dev/null +++ b/launcher/prepare.cpp @@ -0,0 +1,85 @@ +/* + * prepare.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "prepare.h" +#include "launcherdirs.h" + +#include +#include +#include + +#ifdef VCMI_ANDROID +#include "../lib/CAndroidVMHelper.h" + +#include +#include +#include + +namespace +{ +// https://gist.github.com/ssendeavour/7324701 +bool copyRecursively(const QString &srcFilePath, const QString &tgtFilePath) +{ + QFileInfo srcFileInfo{srcFilePath}; + if(srcFileInfo.isDir()) { + QDir targetDir{tgtFilePath}; + targetDir.cdUp(); + if(!targetDir.mkpath(QFileInfo{tgtFilePath}.fileName())) + return false; + targetDir.setPath(tgtFilePath); + + QDir sourceDir{srcFilePath}; + const auto fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System); + for(const auto & fileName : fileNames) { + const auto newSrcFilePath = sourceDir.filePath(fileName); + const auto newTgtFilePath = targetDir.filePath(fileName); + if(!copyRecursively(newSrcFilePath, newTgtFilePath)) + return false; + } + } else { + if(!QFile::copy(srcFilePath, tgtFilePath)) + return false; + } + return true; +} + +void prepareAndroid() +{ + QAndroidJniEnvironment jniEnv; + CAndroidVMHelper::initClassloader(static_cast(jniEnv)); + + const bool justLaunched = QtAndroid::androidActivity().getField("justLaunched") == JNI_TRUE; + if(!justLaunched) + return; + + // copy core data to internal directory + const auto vcmiDir = QAndroidJniObject::callStaticObjectMethod("eu/vcmi/vcmi/NativeMethods", "internalDataRoot").toString(); + for(auto vcmiFilesResource : {QLatin1String{"config"}, QLatin1String{"Mods"}}) + { + QDir destDir = QString{"%1/%2"}.arg(vcmiDir, vcmiFilesResource); + destDir.removeRecursively(); + copyRecursively(QString{":/%1"}.arg(vcmiFilesResource), destDir.absolutePath()); + } +} +} +#endif + + +namespace launcher +{ +void prepare() +{ +#ifdef VCMI_ANDROID + prepareAndroid(); +#endif + + CLauncherDirs::prepare(); +} +} diff --git a/launcher/prepare.h b/launcher/prepare.h new file mode 100644 index 000000000..7a605fd9b --- /dev/null +++ b/launcher/prepare.h @@ -0,0 +1,15 @@ +/* + * prepare.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#pragma once + +namespace launcher +{ +void prepare(); +} diff --git a/launcher/resources.qrc b/launcher/resources.qrc new file mode 100644 index 000000000..139401504 --- /dev/null +++ b/launcher/resources.qrc @@ -0,0 +1,14 @@ + + + icons/about-project.png + icons/menu-editor.png + icons/menu-game.png + icons/menu-mods.png + icons/menu-settings.png + icons/mod-delete.png + icons/mod-disabled.png + icons/mod-download.png + icons/mod-enabled.png + icons/mod-update.png + + diff --git a/osx/CMakeLists.txt b/osx/CMakeLists.txt index 5ce47c1c4..4c5633919 100644 --- a/osx/CMakeLists.txt +++ b/osx/CMakeLists.txt @@ -7,7 +7,6 @@ if(APPLE_MACOS) if(ENABLE_LAUNCHER OR ENABLE_EDITOR) if(USING_CONAN) # simulate macdeployqt behavior, main Qt libs are copied by conan - get_target_property(qmakePath Qt5::qmake IMPORTED_LOCATION) execute_process(COMMAND "${qmakePath}" -query QT_INSTALL_PLUGINS OUTPUT_VARIABLE qtPluginsDir @@ -25,7 +24,7 @@ if(APPLE_MACOS) else() # note: cross-compiled Qt 5 builds macdeployqt for target platform instead of host # deploy Qt dylibs with macdeployqt - find_program(TOOL_MACDEPLOYQT NAMES macdeployqt PATHS ${qt_base_dir}/bin) + find_program(TOOL_MACDEPLOYQT NAMES macdeployqt PATHS "${qtBinDir}") if(TOOL_MACDEPLOYQT) install(CODE " execute_process(COMMAND