1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-19 17:12:06 +02:00

[launcher] build for Android

also embeds icons and translations as Qt resources instead of reading from disk
This commit is contained in:
Andrey Filipenkov 2024-03-03 00:24:00 +03:00
parent cbc418fc62
commit 8cee8b72a6
18 changed files with 345 additions and 212 deletions

View File

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

View File

@ -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 "$<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 "$<CONFIG>" --prefix "${androidQtBuildDir}"
COMMAND "${androidDeployQt}" --input "${CMAKE_BINARY_DIR}/androiddeployqt.json" --output "${androidQtBuildDir}" --android-platform "android-${ANDROID_TARGET_SDK_VERSION}" --verbose $<$<NOT:$<CONFIG:Debug>>:--release> ${ANDROIDDEPLOYQT_OPTIONS}
COMMAND_EXPAND_LISTS
VERBATIM
COMMENT "Create android package"
)
add_dependencies(android_deploy vcmiclient)
else()
install(TARGETS vcmiclient DESTINATION ${BIN_DIR})
endif()

View File

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

View File

@ -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 "<file>${qmFile}</file>\n")
endforeach()
file(WRITE "${translationsResource}"
"<!DOCTYPE RCC>
<RCC version=\"1.0\">
<qresource prefix=\"/\">
${rccQmFiles}
</qresource>
</RCC>"
)
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 "<file>." "<file>${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 <QtPlugin>\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()

View File

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

View File

@ -96,5 +96,4 @@ private slots:
private:
Ui::FirstLaunchView * ui;
};

View File

@ -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 <QApplication>
#include <QProcess>
#include <QMessageBox>
// 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 <QAndroidJniObject>
# include <QtAndroid>
#else
# include <QMessageBox>
# include <QProcess>
#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<void>("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

View File

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

View File

@ -19,10 +19,6 @@
<property name="windowTitle">
<string>VCMI Launcher</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>icons:menu-game.png</normaloff>icons:menu-game.png</iconset>
</property>
<property name="iconSize">
<size>
<width>64</width>
@ -56,10 +52,6 @@
<property name="text">
<string>Mods</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-mods.png</normaloff>icons:menu-mods.png</iconset>
</property>
<property name="iconSize">
<size>
<width>64</width>
@ -106,10 +98,6 @@
<property name="text">
<string>Settings</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-settings.png</normaloff>icons:menu-settings.png</iconset>
</property>
<property name="iconSize">
<size>
<width>64</width>
@ -156,10 +144,6 @@
<property name="text">
<string>Help</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:about-project.png</normaloff>icons:about-project.png</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
@ -225,10 +209,6 @@
<property name="text">
<string>Map Editor</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-editor.png</normaloff>icons:menu-editor.png</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
@ -278,10 +258,6 @@
<property name="text">
<string>Start game</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:menu-game.png</normaloff>icons:menu-game.png</iconset>
</property>
<property name="iconSize">
<size>
<width>64</width>

View File

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

View File

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

View File

@ -423,10 +423,6 @@ hr { height: 1px; border-width: 0; }
<property name="text">
<string>Uninstall</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:mod-delete.png</normaloff>icons:mod-delete.png</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
@ -458,10 +454,6 @@ hr { height: 1px; border-width: 0; }
<property name="text">
<string>Enable</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:mod-enabled.png</normaloff>icons:mod-enabled.png</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
@ -493,10 +485,6 @@ hr { height: 1px; border-width: 0; }
<property name="text">
<string>Disable</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:mod-disabled.png</normaloff>icons:mod-disabled.png</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
@ -528,10 +516,6 @@ hr { height: 1px; border-width: 0; }
<property name="text">
<string>Update</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:mod-update.png</normaloff>icons:mod-update.png</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
@ -563,10 +547,6 @@ hr { height: 1px; border-width: 0; }
<property name="text">
<string>Install</string>
</property>
<property name="icon">
<iconset>
<normaloff>icons:mod-download.png</normaloff>icons:mod-download.png</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>

View File

@ -20,6 +20,8 @@
#include "../jsonutils.h"
#include "../launcherdirs.h"
#include <future>
namespace
{
QString detectModArchive(QString path, QString modName, std::vector<std::string> & 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;

85
launcher/prepare.cpp Normal file
View File

@ -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 <QDir>
#include <QFile>
#include <QFileInfo>
#ifdef VCMI_ANDROID
#include "../lib/CAndroidVMHelper.h"
#include <QAndroidJniEnvironment>
#include <QAndroidJniObject>
#include <QtAndroid>
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 *>(jniEnv));
const bool justLaunched = QtAndroid::androidActivity().getField<jboolean>("justLaunched") == JNI_TRUE;
if(!justLaunched)
return;
// copy core data to internal directory
const auto vcmiDir = QAndroidJniObject::callStaticObjectMethod<jstring>("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();
}
}

15
launcher/prepare.h Normal file
View File

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

14
launcher/resources.qrc Normal file
View File

@ -0,0 +1,14 @@
<RCC>
<qresource prefix="/">
<file>icons/about-project.png</file>
<file>icons/menu-editor.png</file>
<file>icons/menu-game.png</file>
<file>icons/menu-mods.png</file>
<file>icons/menu-settings.png</file>
<file>icons/mod-delete.png</file>
<file>icons/mod-disabled.png</file>
<file>icons/mod-download.png</file>
<file>icons/mod-enabled.png</file>
<file>icons/mod-update.png</file>
</qresource>
</RCC>

View File

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