File indexing completed on 2025-01-05 03:51:16

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-09-08
0007  * Description : global macros, variables and flags - Bundle functions.
0008  *
0009  * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "digikam_globals_p.h"
0016 
0017 namespace Digikam
0018 {
0019 
0020 bool isRunningInAppImageBundle()
0021 {
0022     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0023 
0024     if (env.contains(QLatin1String("APPIMAGE_ORIGINAL_LD_LIBRARY_PATH")) &&
0025         env.contains(QLatin1String("APPIMAGE_ORIGINAL_QT_PLUGIN_PATH"))  &&
0026         env.contains(QLatin1String("APPIMAGE_ORIGINAL_XDG_DATA_DIRS"))   &&
0027         env.contains(QLatin1String("APPIMAGE_ORIGINAL_PATH")))
0028     {
0029         return true;
0030     }
0031 
0032     return false;
0033 }
0034 
0035 QProcessEnvironment adjustedEnvironmentForAppImage()
0036 {
0037     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0038 
0039     // If we are running into AppImage bundle, switch env var to the right values.
0040 
0041     if (isRunningInAppImageBundle())
0042     {
0043         qCDebug(DIGIKAM_GENERAL_LOG) << "Adjusting environment variables for AppImage bundle";
0044 
0045         if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_LD_LIBRARY_PATH")).isEmpty())
0046         {
0047             env.insert(QLatin1String("LD_LIBRARY_PATH"),
0048                        env.value(QLatin1String("APPIMAGE_ORIGINAL_LD_LIBRARY_PATH")));
0049         }
0050         else
0051         {
0052             env.remove(QLatin1String("LD_LIBRARY_PATH"));
0053         }
0054 
0055         if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_QT_PLUGIN_PATH")).isEmpty())
0056         {
0057             env.insert(QLatin1String("QT_PLUGIN_PATH"),
0058                        env.value(QLatin1String("APPIMAGE_ORIGINAL_QT_PLUGIN_PATH")));
0059         }
0060         else
0061         {
0062             env.remove(QLatin1String("QT_PLUGIN_PATH"));
0063         }
0064 
0065         if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_XDG_DATA_DIRS")).isEmpty())
0066         {
0067             env.insert(QLatin1String("XDG_DATA_DIRS"),
0068                        env.value(QLatin1String("APPIMAGE_ORIGINAL_XDG_DATA_DIRS")));
0069         }
0070         else
0071         {
0072             env.remove(QLatin1String("XDG_DATA_DIRS"));
0073         }
0074 
0075         if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_LD_PRELOAD")).isEmpty())
0076         {
0077             env.insert(QLatin1String("LD_PRELOAD"),
0078                        env.value(QLatin1String("APPIMAGE_ORIGINAL_LD_PRELOAD")));
0079         }
0080         else
0081         {
0082             env.remove(QLatin1String("LD_PRELOAD"));
0083         }
0084 
0085         if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_PATH")).isEmpty())
0086         {
0087             env.insert(QLatin1String("PATH"),
0088                        env.value(QLatin1String("APPIMAGE_ORIGINAL_PATH")));
0089         }
0090         else
0091         {
0092             env.remove(QLatin1String("PATH"));
0093         }
0094 
0095         if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_KDE_FULL_SESSION")).isEmpty())
0096         {
0097             env.insert(QLatin1String("KDE_FULL_SESSION"),
0098                        env.value(QLatin1String("APPIMAGE_ORIGINAL_KDE_FULL_SESSION")));
0099         }
0100         else
0101         {
0102             env.remove(QLatin1String("KDE_FULL_SESSION"));
0103         }
0104 
0105         if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_DESKTOP_SESSION")).isEmpty())
0106         {
0107             env.insert(QLatin1String("DESKTOP_SESSION"),
0108                        env.value(QLatin1String("APPIMAGE_ORIGINAL_DESKTOP_SESSION")));
0109         }
0110         else
0111         {
0112             env.remove(QLatin1String("DESKTOP_SESSION"));
0113         }
0114 
0115         if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_XDG_CURRENT_DESKTOP")).isEmpty())
0116         {
0117             env.insert(QLatin1String("XDG_CURRENT_DESKTOP"),
0118                        env.value(QLatin1String("APPIMAGE_ORIGINAL_XDG_CURRENT_DESKTOP")));
0119         }
0120         else
0121         {
0122             env.remove(QLatin1String("XDG_CURRENT_DESKTOP"));
0123         }
0124 
0125         if (!env.value(QLatin1String("APPIMAGE_ORIGINAL_XDG_SESSION_DESKTOP")).isEmpty())
0126         {
0127             env.insert(QLatin1String("XDG_SESSION_DESKTOP"),
0128                        env.value(QLatin1String("APPIMAGE_ORIGINAL_XDG_SESSION_DESKTOP")));
0129         }
0130         else
0131         {
0132             env.remove(QLatin1String("XDG_SESSION_DESKTOP"));
0133         }
0134     }
0135 
0136     return env;
0137 }
0138 
0139 void tryInitDrMingw()
0140 {
0141 
0142 #ifdef HAVE_DRMINGW
0143 
0144     // Envirronnement variable under Windows to disable DrMinGw
0145 
0146     QByteArray drmingwEnv = qgetenv("DK_DISABLE_DRMINGW");
0147 
0148     if (drmingwEnv.isEmpty())
0149     {
0150 
0151         qCDebug(DIGIKAM_GENERAL_LOG) << "Loading DrMinGw run-time...";
0152 /*
0153         // Windows version check for DrMinGW 0.9.2. It's not necessary with new DrMinGW 0.9.4 version.
0154 
0155         QRegExp versionRegExp(QLatin1String("(\\d+[.]*\\d*)"));
0156         QSysInfo::productVersion().indexOf(versionRegExp);
0157         double version  = versionRegExp.capturedTexts().constFirst().toDouble();
0158 
0159         if  (
0160              ((version < 2000.0) && (version < 10.0)) ||
0161              ((version > 2000.0) && (version < 2016.0))
0162             )
0163         {
0164             qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw: unsupported Windows version" << version;
0165 
0166             return;
0167         }
0168 */
0169         QString appPath = QCoreApplication::applicationDirPath();
0170         QString excFile = QDir::toNativeSeparators(appPath + QLatin1String("/exchndl.dll"));
0171 
0172         HMODULE hModExc = LoadLibraryW((LPCWSTR)excFile.utf16());
0173 
0174         if (!hModExc)
0175         {
0176             qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw: cannot init crash handler dll.";
0177 
0178             return;
0179         }
0180 
0181         // No need to call ExcHndlInit since the crash handler is installed on DllMain
0182 
0183         auto myExcHndlSetLogFileNameA = reinterpret_cast<BOOL (APIENTRY*)(const char*)>
0184                                             (GetProcAddress(hModExc, "ExcHndlSetLogFileNameA"));
0185 
0186         if (!myExcHndlSetLogFileNameA)
0187         {
0188             qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw: cannot init customized crash file.";
0189 
0190             return;
0191         }
0192 
0193         // Set the log file path to %LocalAppData%\digikam_crash.log
0194 
0195         QString logPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0196         QString logFile = QDir::toNativeSeparators(logPath + QLatin1String("/digikam_crash.log"));
0197         myExcHndlSetLogFileNameA(logFile.toLocal8Bit().data());
0198 
0199         qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw run-time loaded.";
0200         qCDebug(DIGIKAM_GENERAL_LOG) << "DrMinGw crash-file will be located at: " << logFile;
0201     }
0202 
0203 #endif // HAVE_DRMINGW
0204 
0205 }
0206 
0207 QString macOSBundlePrefix()
0208 {
0209     return QString::fromUtf8("/Applications/digiKam.org/digikam.app/Contents/");
0210 }
0211 
0212 void unloadQtTranslationFiles(QApplication& app)
0213 {
0214     // HACK: We try to remove all the translators installed by ECMQmLoader.
0215     // The reason is that it always load translations for the system locale
0216     // which interferes with our effort to handle override languages. Since
0217     // `en_US` (or `en`) strings are defined in code, the QTranslator doesn't
0218     // actually handle translations for them, so even if we try to install
0219     // a QTranslator loaded from `en`, the strings always get translated by
0220     // the system language QTranslator that ECMQmLoader installed instead
0221     // of the English one.
0222 
0223     // ECMQmLoader creates all QTranslator's parented to the active QApplication instance.
0224 
0225     QList<QTranslator*> translators = app.findChildren<QTranslator*>(QString(), Qt::FindDirectChildrenOnly);
0226 
0227     Q_FOREACH (const auto& translator, translators)
0228     {
0229         app.removeTranslator(translator);
0230     }
0231 
0232     qCDebug(DIGIKAM_GENERAL_LOG) << "Qt standard translations removed:" << translators.size();
0233 }
0234 
0235 void loadStdQtTranslationFiles(QApplication& app)
0236 {
0237     QString transPath = QStandardPaths::locate(QStandardPaths::AppLocalDataLocation,
0238                                                QLatin1String("translations"),
0239                                                QStandardPaths::LocateDirectory);
0240 
0241     if (!transPath.isEmpty())
0242     {
0243         QString languagePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) +
0244                                QLatin1Char('/')                                                        +
0245                                QLatin1String("klanguageoverridesrc");
0246 
0247         qCDebug(DIGIKAM_GENERAL_LOG) << "Qt standard translations path:" << transPath;
0248 
0249         QLocale locale;
0250 
0251         if (!languagePath.isEmpty())
0252         {
0253             QSettings settings(languagePath, QSettings::IniFormat);
0254             settings.beginGroup(QLatin1String("Language"));
0255             QString language = settings.value(qApp->applicationName(), QString()).toString();
0256             settings.endGroup();
0257 
0258             if (!language.isEmpty())
0259             {
0260                 QString languageName = language.split(QLatin1Char(':')).first();
0261 
0262                 qCDebug(DIGIKAM_GENERAL_LOG) << "Language set to:" << languageName;
0263 
0264                 locale = QLocale(languageName);
0265             }
0266         }
0267 
0268         const QStringList qtCatalogs =
0269         {
0270             QLatin1String("qt"),
0271             QLatin1String("qtbase"),
0272             QLatin1String("qt_help"),
0273             QLatin1String("qtdeclarative"),
0274             QLatin1String("qtquickcontrols"),
0275             QLatin1String("qtquickcontrols2"),
0276             QLatin1String("qtmultimedia"),
0277 
0278 #ifdef HAVE_QWEBENGINE
0279 
0280             QLatin1String("qtwebengine"),
0281 
0282 #endif
0283 
0284 #ifdef HAVE_QTXMLPATTERNS
0285 
0286             QLatin1String("qtxmlpatterns"),
0287 
0288 #endif
0289 
0290         };
0291 
0292         Q_FOREACH (const QString& catalog, qtCatalogs)
0293         {
0294             QTranslator* const translator = new QTranslator(&app);
0295 
0296             if (translator->load(locale, catalog, QLatin1String("_"), transPath))
0297             {
0298                 qCDebug(DIGIKAM_GENERAL_LOG) << "Loaded Qt standard translations"
0299                                              << locale.name()
0300                                              << "from catalog"
0301                                              << catalog;
0302 
0303                 app.installTranslator(translator);
0304             }
0305             else
0306             {
0307                 delete translator;
0308             }
0309         }
0310     }
0311 }
0312 
0313 void loadEcmQtTranslationFiles(QApplication& app)
0314 {
0315     // Load translations created by the ECMPoQmTools module.
0316     // This function is based on the code in:
0317     // https://invent.kde.org/frameworks/extra-cmake-modules/-/blob/master/modules/ECMQmLoader.cpp.in
0318 
0319 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0320 
0321     const QStringList ecmCatalogs =
0322     {
0323 //        QLatin1String("kauth6_qt"),               Do not exists.
0324         QLatin1String("kbookmarks6_qt"),
0325         QLatin1String("kcodecs6_qt"),
0326         QLatin1String("kcompletion6_qt"),
0327         QLatin1String("kconfig6_qt"),
0328         QLatin1String("kcoreaddons6_qt"),
0329         QLatin1String("kdbusaddons6_qt"),
0330 //        QLatin1String("kde6_xml_mimetypes"),      Do not exists.
0331 //        QLatin1String("kglobalaccel6_qt"),        Do not exists.
0332         QLatin1String("kitemviews6_qt"),
0333         QLatin1String("kwidgetsaddons6_qt"),
0334         QLatin1String("kwindowsystem6_qt"),
0335         QLatin1String("solid6_qt"),
0336 
0337 #else
0338 
0339     const QStringList ecmCatalogs =
0340     {
0341         QLatin1String("kauth5_qt"),
0342         QLatin1String("kbookmarks5_qt"),
0343         QLatin1String("kcodecs5_qt"),
0344         QLatin1String("kcompletion5_qt"),
0345         QLatin1String("kconfig5_qt"),
0346         QLatin1String("kcoreaddons5_qt"),
0347         QLatin1String("kdbusaddons5_qt"),
0348         QLatin1String("kde5_xml_mimetypes"),
0349         QLatin1String("kglobalaccel5_qt"),
0350         QLatin1String("kitemviews5_qt"),
0351         QLatin1String("kwidgetsaddons5_qt"),
0352         QLatin1String("kwindowsystem5_qt"),
0353         QLatin1String("solid5_qt"),
0354 
0355 #endif
0356 
0357     };
0358 
0359     QStringList ecmLangs = KLocalizedString::languages();
0360     const QString langEn = QLatin1String("en");
0361 
0362     // Replace "en_US" with "en" because that's what we have in the locale dir.
0363 
0364     int indexOfEnUs      = ecmLangs.indexOf(QLatin1String("en_US"));
0365 
0366     if (indexOfEnUs != -1)
0367     {
0368         ecmLangs[indexOfEnUs] = langEn;
0369     }
0370 
0371     // We need to have "en" to the end of the list, because we explicitly
0372     // removed the "en" translators added by ECMQmLoader.
0373     // If "en" is already on the list, we truncate the ones after, because
0374     // "en" is the catch-all fallback that has the strings in code.
0375 
0376     int indexOfEn = ecmLangs.indexOf(langEn);
0377 
0378     if (indexOfEn != -1)
0379     {
0380         for (int i = (ecmLangs.size() - indexOfEn - 1) ; i > 0 ; i--)
0381         {
0382             ecmLangs.removeLast();
0383         }
0384     }
0385     else
0386     {
0387         ecmLangs.append(langEn);
0388     }
0389 
0390     // The last added one has the highest precedence, so we iterate the list backwards.
0391 
0392     QStringListIterator it(ecmLangs);
0393     it.toBack();
0394 
0395     while (it.hasPrevious())
0396     {
0397         const QString& localeDirName = it.previous();
0398 
0399         Q_FOREACH (const auto& catalog, ecmCatalogs)
0400         {
0401             QString subPath    = QLatin1String("locale/")       +
0402                                  localeDirName                  +
0403                                  QLatin1String("/LC_MESSAGES/") +
0404                                  catalog                        +
0405                                  QLatin1String(".qm");
0406 
0407 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0408 
0409             const QString root = QLibraryInfo::path(QLibraryInfo::PrefixPath);
0410 
0411 #else
0412 
0413             const QString root = QLibraryInfo::location(QLibraryInfo::PrefixPath);
0414 
0415 #endif
0416 
0417             // For AppImage transalotion files uses AppDataLocation.
0418 
0419             QString fullPath   = QStandardPaths::locate(QStandardPaths::AppDataLocation, subPath);
0420 
0421             if (fullPath.isEmpty())
0422             {
0423                 // For distro builds probably still use GenericDataLocation, so check that too.
0424 
0425                 fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, subPath);
0426             }
0427 
0428             if (fullPath.isEmpty())
0429             {
0430                 // And, failing all, use the deps install folder
0431 
0432                 fullPath = root + QLatin1String("/share/") + subPath;
0433             }
0434 
0435             if (!QFile::exists(fullPath))
0436             {
0437                 continue;
0438             }
0439 
0440             QTranslator* const translator = new QTranslator(&app);
0441 
0442             if (translator->load(fullPath))
0443             {
0444                 qCDebug(DIGIKAM_GENERAL_LOG) << "Loaded Qt ECM translations"
0445                                              << localeDirName
0446                                              << "from catalog"
0447                                              << catalog;
0448 
0449                 translator->setObjectName(QString::fromUtf8("QTranslator.%1.%2").arg(localeDirName, catalog));
0450                 app.installTranslator(translator);
0451             }
0452             else
0453             {
0454                 delete translator;
0455             }
0456         }
0457     }
0458 }
0459 
0460 void installQtTranslationFiles(QApplication& app)
0461 {
0462 
0463 #if defined Q_OS_WIN || defined Q_OS_MACOS
0464 
0465     bool installTranslations = true;
0466 
0467 #else
0468 
0469     bool installTranslations = isRunningInAppImageBundle();
0470 
0471 #endif
0472 
0473     if (installTranslations)
0474     {
0475         unloadQtTranslationFiles(app);
0476         loadStdQtTranslationFiles(app);
0477         loadEcmQtTranslationFiles(app);
0478     }
0479 }
0480 
0481 void setupKSycocaDatabaseFile()
0482 {
0483     if (isRunningInAppImageBundle())
0484     {
0485         QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
0486 
0487         if (!cachePath.isEmpty())
0488         {
0489             QDir dir(cachePath);
0490             QStringList searchList({ QLatin1String("ksycoca*") });
0491 
0492             QFileInfoList infoList = dir.entryInfoList(searchList, QDir::Files);
0493 
0494             while (!infoList.isEmpty())
0495             {
0496                 QFileInfo info = infoList.takeFirst();
0497 
0498                 if (info.suffix() == QLatin1String("lock"))
0499                 {
0500                     continue;
0501                 }
0502 
0503                 if ((info.fileName() != QLatin1String("ksycoca5_appimage")) &&
0504                     (info.fileName() != QLatin1String("ksycoca6_appimage")))
0505                 {
0506                     return;
0507                 }
0508             }
0509 
0510 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0511 
0512             QString ksycoca = cachePath + QLatin1String("/ksycoca6_appimage");
0513 
0514 #else
0515 
0516             QString ksycoca = cachePath + QLatin1String("/ksycoca5_appimage");
0517 
0518 #endif
0519 
0520             qputenv("KDESYCOCA", ksycoca.toUtf8());
0521 
0522             qCDebug(DIGIKAM_GENERAL_LOG) << "AppImage KSycoca file:" << ksycoca;
0523         }
0524     }
0525 }
0526 
0527 } // namespace Digikam