File indexing completed on 2024-04-21 14:43:50
0001 /* GCompris - main.cpp 0002 * 0003 * SPDX-FileCopyrightText: 2014 Bruno Coudoin <bruno.coudoin@gcompris.net> 0004 * 0005 * Authors: 0006 * Bruno Coudoin <bruno.coudoin@gcompris.net> 0007 * 0008 * SPDX-License-Identifier: GPL-3.0-or-later 0009 */ 0010 #include <QtDebug> 0011 #include <QApplication> 0012 #include <QQuickWindow> 0013 #include <QQmlApplicationEngine> 0014 #include <QQmlComponent> 0015 #include <QStandardPaths> 0016 #include <QObject> 0017 #include <QTranslator> 0018 #include <QCommandLineParser> 0019 #include <QCursor> 0020 #include <QPixmap> 0021 #include <QSettings> 0022 0023 #include "GComprisPlugin.h" 0024 #include "ApplicationInfo.h" 0025 #include "ActivityInfoTree.h" 0026 #include "DownloadManager.h" 0027 0028 bool loadAndroidTranslation(QTranslator &translator, const QString &locale) 0029 { 0030 QFile file("assets:/share/GCompris/gcompris_" + locale + ".qm"); 0031 0032 file.open(QIODevice::ReadOnly); 0033 QDataStream in(&file); 0034 uchar *data = (uchar *)malloc(file.size()); 0035 0036 if (!file.exists()) 0037 qDebug() << "file assets:/share/GCompris/gcompris_" << locale << ".qm does not exist"; 0038 0039 in.readRawData((char *)data, file.size()); 0040 0041 if (!translator.load(data, file.size())) { 0042 qDebug() << "Unable to load translation for locale " << locale << ", use en_US by default"; 0043 free(data); 0044 return false; 0045 } 0046 // Do not free data, it is still needed by translator 0047 return true; 0048 } 0049 0050 /** 0051 * Checks if the locale is supported. Locale may have been removed because 0052 * translation progress was not enough or invalid language put in configuration. 0053 */ 0054 bool isSupportedLocale(const QString &locale) 0055 { 0056 bool isSupported = false; 0057 QQmlEngine engine; 0058 QQmlComponent component(&engine, QUrl("qrc:/gcompris/src/core/LanguageList.qml")); 0059 QObject *object = component.create(); 0060 if (!object) { 0061 qWarning() << "isSupportedLocale:" << component.errors(); 0062 return false; 0063 } 0064 QVariant variant = object->property("languages"); 0065 QJSValue languagesList = variant.value<QJSValue>(); 0066 const int length = languagesList.property("length").toInt(); 0067 for (int i = 0; i < length; ++i) { 0068 if (languagesList.property(i).property("locale").toString() == locale) { 0069 isSupported = true; 0070 } 0071 } 0072 delete object; 0073 return isSupported; 0074 } 0075 // Return the locale 0076 QString loadTranslation(const QSettings &config, QTranslator &translator) 0077 { 0078 QString locale = config.value("General/locale", GC_DEFAULT_LOCALE).toString(); 0079 0080 if (!isSupportedLocale(locale)) { 0081 qDebug() << "locale" << locale << "not supported, defaulting to" << GC_DEFAULT_LOCALE; 0082 locale = GC_DEFAULT_LOCALE; 0083 ApplicationSettings::getInstance()->setLocale(locale); 0084 } 0085 0086 if (locale == GC_DEFAULT_LOCALE) 0087 locale = QString(QLocale::system().name() + ".UTF-8"); 0088 0089 if (locale == "C.UTF-8") 0090 locale = "en_US.UTF-8"; 0091 0092 // Load translation 0093 // Remove .UTF8 0094 locale.remove(".UTF-8"); 0095 0096 #if defined(Q_OS_ANDROID) 0097 if (!loadAndroidTranslation(translator, locale)) 0098 loadAndroidTranslation(translator, ApplicationInfo::localeShort(locale)); 0099 #else 0100 0101 #if (defined(Q_OS_LINUX) || defined(Q_OS_UNIX)) 0102 // only useful for translators: load from $application_dir/../share/... if exists as it is where kde scripts install translations 0103 if (translator.load("gcompris_qt.qm", QString("%1/../share/locale/%2/LC_MESSAGES").arg(QCoreApplication::applicationDirPath(), locale))) { 0104 qDebug() << "load translation for locale " << locale << " in " << QString("%1/../share/locale/%2/LC_MESSAGES").arg(QCoreApplication::applicationDirPath(), locale); 0105 } 0106 else if (translator.load("gcompris_qt.qm", QString("%1/../share/locale/%2/LC_MESSAGES").arg(QCoreApplication::applicationDirPath(), locale.split('_')[0]))) { 0107 qDebug() << "load translation for locale " << locale << " in " << QString("%1/../share/locale/%2/LC_MESSAGES").arg(QCoreApplication::applicationDirPath(), locale.split('_')[0]); 0108 } 0109 else 0110 #endif 0111 if (!translator.load("gcompris_" + locale, QString("%1/%2/translations").arg(QCoreApplication::applicationDirPath(), GCOMPRIS_DATA_FOLDER))) { 0112 qDebug() << "Unable to load translation for locale " << locale << ", use en_US by default"; 0113 } 0114 #endif 0115 return locale; 0116 } 0117 0118 int main(int argc, char *argv[]) 0119 { 0120 // Disable it because we already support HDPI display natively 0121 qunsetenv("QT_DEVICE_PIXEL_RATIO"); 0122 0123 QApplication app(argc, argv); 0124 #if defined(UBUNTUTOUCH) 0125 app.setOrganizationName("org.kde.gcompris"); 0126 #else 0127 app.setOrganizationName("KDE"); 0128 #endif 0129 app.setApplicationName(GCOMPRIS_APPLICATION_NAME); 0130 app.setOrganizationDomain("kde.org"); 0131 app.setApplicationVersion(ApplicationInfo::GCVersion()); 0132 // Set desktop file name, as the built-in (orgDomain + appName) is not 0133 // the one we use (because appName is gcompris-qt, not gcompris) 0134 QGuiApplication::setDesktopFileName("org.kde.gcompris"); 0135 0136 // add a variable to disable default fullscreen on Mac, see below.. 0137 #if defined(Q_OS_MAC) 0138 // Sandboxing on MacOSX as documented in: 0139 // https://doc.qt.io/qt-5/osx-deployment.html 0140 QDir dir(QGuiApplication::applicationDirPath()); 0141 dir.cdUp(); 0142 dir.cd("Plugins"); 0143 QGuiApplication::setLibraryPaths(QStringList(dir.absolutePath())); 0144 #endif 0145 0146 // Local scope for config 0147 #if defined(UBUNTUTOUCH) 0148 QSettings config(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/gcompris/" + GCOMPRIS_APPLICATION_NAME + ".conf", 0149 QSettings::IniFormat); 0150 #else 0151 QSettings config(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/gcompris/" + GCOMPRIS_APPLICATION_NAME + ".conf", 0152 QSettings::IniFormat); 0153 0154 #endif 0155 0156 QCommandLineParser parser; 0157 parser.setApplicationDescription("GCompris is an educational software for children 2 to 10"); 0158 parser.addHelpOption(); 0159 parser.addVersionOption(); 0160 QCommandLineOption exportActivitiesAsSQL("export-activities-as-sql", "Export activities as SQL"); 0161 parser.addOption(exportActivitiesAsSQL); 0162 QCommandLineOption clDefaultCursor(QStringList() << "c" 0163 << "cursor", 0164 QObject::tr("Run GCompris with the default system cursor.")); 0165 parser.addOption(clDefaultCursor); 0166 QCommandLineOption clNoCursor(QStringList() << "C" 0167 << "nocursor", 0168 QObject::tr("Run GCompris without cursor (touch screen mode).")); 0169 parser.addOption(clNoCursor); 0170 QCommandLineOption clFullscreen(QStringList() << "f" 0171 << "fullscreen", 0172 QObject::tr("Run GCompris in fullscreen mode.")); 0173 parser.addOption(clFullscreen); 0174 QCommandLineOption clWindow(QStringList() << "w" 0175 << "window", 0176 QObject::tr("Run GCompris in window mode.")); 0177 parser.addOption(clWindow); 0178 QCommandLineOption clSound(QStringList() << "s" 0179 << "sound", 0180 QObject::tr("Run GCompris with sound enabled.")); 0181 parser.addOption(clSound); 0182 QCommandLineOption clMute(QStringList() << "m" 0183 << "mute", 0184 QObject::tr("Run GCompris without sound.")); 0185 parser.addOption(clMute); 0186 QCommandLineOption clWithoutKioskMode(QStringList() << "disable-kioskmode", 0187 QObject::tr("Disable the kiosk mode (default).")); 0188 parser.addOption(clWithoutKioskMode); 0189 QCommandLineOption clWithKioskMode(QStringList() << "enable-kioskmode", 0190 QObject::tr("Enable the kiosk mode.")); 0191 parser.addOption(clWithKioskMode); 0192 0193 QCommandLineOption clSoftwareRenderer(QStringList() << "software-renderer", 0194 QObject::tr("Use software renderer instead of openGL (slower but should run with any graphical card).")); 0195 parser.addOption(clSoftwareRenderer); 0196 QCommandLineOption clOpenGLRenderer(QStringList() << "opengl-renderer", 0197 QObject::tr("Use openGL renderer instead of software (faster but crash potentially depending on your graphical card).")); 0198 parser.addOption(clOpenGLRenderer); 0199 0200 QCommandLineOption clStartOnActivity("launch", 0201 QObject::tr("Specify the activity when starting GCompris."), "activity"); 0202 parser.addOption(clStartOnActivity); 0203 0204 QCommandLineOption clListActivities(QStringList() << "l" 0205 << "list-activities", 0206 QObject::tr("Outputs all the available activities on the standard output.")); 0207 parser.addOption(clListActivities); 0208 0209 QCommandLineOption clDifficultyRange("difficulty", 0210 QObject::tr("Specify the range of activity difficulty to display for the session. Either a single value (2), or a range (3-6). Values must be between 1 and 6."), "difficulty"); 0211 parser.addOption(clDifficultyRange); 0212 0213 QCommandLineOption clStartOnLevel("start-level", 0214 QObject::tr("Specify on which level to start the activity. Only used when --launch option is used."), "startLevel"); 0215 parser.addOption(clStartOnLevel); 0216 0217 parser.process(app); 0218 0219 GComprisPlugin plugin; 0220 plugin.registerTypes("GCompris"); 0221 ActivityInfoTree::registerResources(); 0222 0223 // Load translations 0224 QTranslator translator; 0225 loadTranslation(config, translator); 0226 // Apply translation 0227 app.installTranslator(&translator); 0228 0229 // Tell media players to stop playing, it's GCompris time 0230 ApplicationInfo::getInstance()->requestAudioFocus(); 0231 0232 // For android, request the permissions if not already allowed 0233 ApplicationInfo::getInstance()->checkPermissions(); 0234 0235 // Disable default fullscreen launch on Mac as it's a bit broken, window is behind desktop bars 0236 #if defined(Q_OS_MAC) 0237 bool isFullscreen = false; 0238 #else 0239 // for other platforms, fullscreen is the default value 0240 bool isFullscreen = true; 0241 #endif 0242 { 0243 isFullscreen = config.value("General/fullscreen", isFullscreen).toBool(); 0244 0245 // Set the cursor image 0246 bool defaultCursor = config.value("General/defaultCursor", false).toBool(); 0247 0248 if (!defaultCursor && !parser.isSet(clDefaultCursor)) 0249 QGuiApplication::setOverrideCursor( 0250 QCursor(QPixmap(":/gcompris/src/core/resource/cursor.svg"), 0251 0, 0)); 0252 0253 // Hide the cursor 0254 bool noCursor = config.value("General/noCursor", false).toBool(); 0255 0256 if (noCursor || parser.isSet(clNoCursor)) 0257 QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); 0258 } 0259 0260 // Update execution counter 0261 ApplicationSettings::getInstance()->setExeCount(ApplicationSettings::getInstance()->exeCount() + 1); 0262 0263 if (parser.isSet(clFullscreen)) { 0264 isFullscreen = true; 0265 } 0266 if (parser.isSet(clWindow)) { 0267 isFullscreen = false; 0268 } 0269 if (parser.isSet(clMute)) { 0270 ApplicationSettings::getInstance()->setIsAudioEffectsEnabled(false); 0271 ApplicationSettings::getInstance()->setIsAudioVoicesEnabled(false); 0272 } 0273 if (parser.isSet(clSound)) { 0274 ApplicationSettings::getInstance()->setIsAudioEffectsEnabled(true); 0275 ApplicationSettings::getInstance()->setIsAudioVoicesEnabled(true); 0276 } 0277 if (parser.isSet(clWithoutKioskMode)) { 0278 ApplicationSettings::getInstance()->setKioskMode(false); 0279 } 0280 if (parser.isSet(clWithKioskMode)) { 0281 ApplicationSettings::getInstance()->setKioskMode(true); 0282 } 0283 if (parser.isSet(clSoftwareRenderer)) { 0284 ApplicationSettings::getInstance()->setRenderer(QStringLiteral("software")); 0285 } 0286 if (parser.isSet(clOpenGLRenderer)) { 0287 ApplicationSettings::getInstance()->setRenderer(QStringLiteral("opengl")); 0288 } 0289 0290 // Set the renderer used 0291 const QString &renderer = ApplicationSettings::getInstance()->renderer(); 0292 ApplicationInfo::getInstance()->setUseOpenGL(renderer != QLatin1String("software")); 0293 0294 if (renderer == QLatin1String("software")) { 0295 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0296 QQuickWindow::setGraphicsApi(QSGRendererInterface::Software); 0297 #else 0298 QQuickWindow::setSceneGraphBackend(QSGRendererInterface::Software); 0299 #endif 0300 } 0301 else if (renderer == QLatin1String("opengl")) { 0302 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0303 QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); 0304 #else 0305 QQuickWindow::setSceneGraphBackend(QSGRendererInterface::OpenGL); 0306 #endif 0307 } 0308 0309 // Start on specific activity 0310 if (parser.isSet(clStartOnActivity)) { 0311 QString startingActivity = parser.value(clStartOnActivity); 0312 int startingLevel = 0; 0313 if (parser.isSet(clStartOnLevel)) { 0314 bool isNumber = false; 0315 startingLevel = parser.value(clStartOnLevel).toInt(&isNumber); 0316 if (!isNumber || startingLevel < 0) { 0317 startingLevel = 0; 0318 } 0319 } 0320 // internally, levels start at 0 0321 ActivityInfoTree::setStartingActivity(startingActivity, startingLevel - 1); 0322 } 0323 0324 QQmlApplicationEngine engine(QUrl("qrc:/gcompris/src/core/main.qml")); 0325 QObject::connect(&engine, &QQmlApplicationEngine::quit, DownloadManager::getInstance(), 0326 &DownloadManager::shutdown); 0327 // add import path for shipped qml modules: 0328 engine.addImportPath(QStringLiteral("%1/../lib/qml") 0329 .arg(QCoreApplication::applicationDirPath())); 0330 0331 #if __ANDROID__ && QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) 0332 // Find box2d 0333 engine.addImportPath(QStringLiteral("assets:/")); 0334 #endif 0335 0336 ApplicationInfo::getInstance()->setBox2DInstalled(engine); 0337 0338 if (parser.isSet(exportActivitiesAsSQL)) { 0339 ActivityInfoTree *menuTree(qobject_cast<ActivityInfoTree *>(ActivityInfoTree::menuTreeProvider(&engine, nullptr))); 0340 menuTree->exportAsSQL(); 0341 return 0; 0342 } 0343 0344 if (parser.isSet(clListActivities)) { 0345 ActivityInfoTree *menuTree(qobject_cast<ActivityInfoTree *>(ActivityInfoTree::menuTreeProvider(&engine, nullptr))); 0346 menuTree->listActivities(); 0347 return 0; 0348 } 0349 // Start on specific difficulties 0350 if (parser.isSet(clDifficultyRange)) { 0351 QString difficultyRange = parser.value(clDifficultyRange); 0352 QStringList levels = difficultyRange.split(QStringLiteral("-")); 0353 quint32 minDifficulty = levels[0].toUInt(); 0354 quint32 maxDifficulty = minDifficulty; 0355 // If we have a range, take the second value as max difficulty 0356 if (levels.size() > 1) { 0357 maxDifficulty = levels[1].toUInt(); 0358 } 0359 if (minDifficulty > maxDifficulty) { 0360 qWarning() << "Minimal level must be lower than maximum level"; 0361 return -1; 0362 } 0363 if (minDifficulty < 1 || minDifficulty > 6) { 0364 qWarning() << "Minimal level must between 1 and 6"; 0365 return -1; 0366 } 0367 if (maxDifficulty < 1 || maxDifficulty > 6) { 0368 qWarning() << "Maximal level must between 1 and 6"; 0369 return -1; 0370 } 0371 0372 qDebug() << QStringLiteral("Setting difficulty between %1 and %2").arg(minDifficulty).arg(maxDifficulty); 0373 ApplicationSettings::getInstance()->setDifficultyFromCommandLine(minDifficulty, maxDifficulty); 0374 ActivityInfoTree::getInstance()->minMaxFiltersChanged(minDifficulty, maxDifficulty, false); 0375 ActivityInfoTree::getInstance()->filterByTag("favorite"); 0376 } 0377 0378 QObject *topLevel = engine.rootObjects().value(0); 0379 0380 QQuickWindow *window = qobject_cast<QQuickWindow *>(topLevel); 0381 if (window == nullptr) { 0382 qWarning("Error: Your root item has to be a Window."); 0383 return -1; 0384 } 0385 ApplicationInfo::setWindow(window); 0386 0387 window->setIcon(QIcon(QPixmap(QString::fromUtf8(":/gcompris/src/core/resource/gcompris-icon.png")))); 0388 0389 if (isFullscreen) { 0390 window->showFullScreen(); 0391 } 0392 else { 0393 window->show(); 0394 } 0395 0396 return app.exec(); 0397 }