File indexing completed on 2024-04-28 04:21:36

0001 /*
0002 * SPDX-FileCopyrightText: 1999 Matthias Elter <me@kde.org>
0003 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
0004 * SPDX-FileCopyrightText: 2015 Boudewijn Rempt <boud@valdyas.org>
0005 *
0006 *  SPDX-License-Identifier: GPL-2.0-or-later
0007 *
0008 *  This program is distributed in the hope that it will be useful,
0009 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0010 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0011 *  GNU General Public License for more details.
0012 *
0013 *  You should have received a copy of the GNU General Public License
0014 *  along with this program; if not, write to the Free Software
0015 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0016 */
0017 
0018 #include <KLocalizedTranslator>
0019 #include <QByteArray>
0020 #include <QDate>
0021 #include <QDir>
0022 #include <QLibraryInfo>
0023 #include <QLocale>
0024 #include <QMessageBox>
0025 #include <QOperatingSystemVersion>
0026 #include <QPixmap>
0027 #include <QProcess>
0028 #include <QProcessEnvironment>
0029 #include <QSettings>
0030 #include <QStandardPaths>
0031 #include <QString>
0032 #include <QThread>
0033 #include <QTranslator>
0034 
0035 #include <random>
0036 
0037 #include <KisApplication.h>
0038 #include <KisMainWindow.h>
0039 #include <KisSupportedArchitectures.h>
0040 #include <KisUsageLogger.h>
0041 #include <KoConfig.h>
0042 #include <KoResourcePaths.h>
0043 #include <kis_config.h>
0044 #include <kis_debug.h>
0045 #include <kis_image_config.h>
0046 #include <opengl/kis_opengl.h>
0047 
0048 #include "KisApplicationArguments.h"
0049 #include "KisDocument.h"
0050 #include "KisPart.h"
0051 #include "KisUiFont.h"
0052 #include "input/KisQtWidgetsTweaker.h"
0053 #include "kis_splash_screen.h"
0054 
0055 #ifdef Q_OS_ANDROID
0056 #include <QtAndroid>
0057 #include <KisAndroidCrashHandler.h>
0058 #endif
0059 
0060 #if defined Q_OS_WIN
0061 #include "config_use_qt_tablet_windows.h"
0062 #include <windows.h>
0063 #include <winuser.h>
0064 #ifndef USE_QT_TABLET_WINDOWS
0065 #include <kis_tablet_support_win.h>
0066 #include <kis_tablet_support_win8.h>
0067 #else
0068 #include <dialogs/KisDlgCustomTabletResolution.h>
0069 #endif
0070 #include "config-high-dpi-scale-factor-rounding-policy.h"
0071 #include "config-set-has-border-in-full-screen-default.h"
0072 #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT
0073 #include <QtPlatformHeaders/QWindowsWindowFunctions>
0074 #endif
0075 #include <QLibrary>
0076 #endif
0077 
0078 #ifdef Q_OS_MACOS
0079 #include "libs/macosutils/KisMacosEntitlements.h"
0080 #include "libs/macosutils/KisMacosSystemProber.h"
0081 #endif
0082 
0083 #if defined HAVE_KCRASH
0084 #include <kcrash.h>
0085 #elif defined USE_DRMINGW
0086 namespace
0087 {
0088 template<typename T, typename U>
0089 inline T cast_to_function(U v) noexcept
0090 {
0091     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
0092     return reinterpret_cast<T>(reinterpret_cast<void *>(v));
0093 }
0094 
0095 void tryInitDrMingw()
0096 {
0097     const QString pathStr = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath("exchndl.dll");
0098 
0099     QLibrary hMod(pathStr);
0100     if (!hMod.load()) {
0101         return;
0102     }
0103 
0104     using ExcHndlSetLogFileNameA_type = BOOL(APIENTRY *)(const char *);
0105 
0106     // No need to call ExcHndlInit since the crash handler is installed on DllMain
0107     const auto myExcHndlSetLogFileNameA = cast_to_function<ExcHndlSetLogFileNameA_type>(hMod.resolve("ExcHndlSetLogFileNameA"));
0108     if (!myExcHndlSetLogFileNameA) {
0109         return;
0110     }
0111 
0112     // Set the log file path to %LocalAppData%\kritacrash.log
0113     const QString logFile = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)).absoluteFilePath("kritacrash.log");
0114     const QByteArray logFilePath = QDir::toNativeSeparators(logFile).toLocal8Bit();
0115     myExcHndlSetLogFileNameA(logFilePath.data());
0116 }
0117 } // namespace
0118 #endif
0119 
0120 namespace
0121 {
0122 
0123 void installTranslators(KisApplication &app);
0124 
0125 } // namespace
0126 
0127 #ifdef Q_OS_WIN
0128 namespace
0129 {
0130 using pSetDisplayAutoRotationPreferences_t = decltype(&SetDisplayAutoRotationPreferences);
0131 
0132 void resetRotation()
0133 {
0134     QLibrary user32Lib("user32");
0135     if (!user32Lib.load()) {
0136         qWarning() << "Failed to load user32.dll! This really should not happen.";
0137         return;
0138     }
0139     auto pSetDisplayAutoRotationPreferences = cast_to_function<pSetDisplayAutoRotationPreferences_t>(user32Lib.resolve("SetDisplayAutoRotationPreferences"));
0140     if (!pSetDisplayAutoRotationPreferences) {
0141         dbgKrita << "Failed to load function SetDisplayAutoRotationPreferences";
0142         return;
0143     }
0144     bool result = pSetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE);
0145     dbgKrita << "SetDisplayAutoRotationPreferences(ORIENTATION_PREFERENCE_NONE) returned" << result;
0146 }
0147 } // namespace
0148 #endif
0149 
0150 #ifdef Q_OS_ANDROID
0151 extern "C" JNIEXPORT void JNICALL
0152 Java_org_krita_android_JNIWrappers_saveState(JNIEnv* /*env*/,
0153                                              jobject /*obj*/,
0154                                              jint    /*n*/)
0155 {
0156     if (!KisPart::exists()) return;
0157 
0158     KisPart *kisPart = KisPart::instance();
0159     QList<QPointer<KisDocument>> list = kisPart->documents();
0160     for (QPointer<KisDocument> &doc: list)
0161     {
0162         doc->autoSaveOnPause();
0163     }
0164 
0165     const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0166     QSettings kritarc(configPath + QStringLiteral("/kritadisplayrc"), QSettings::IniFormat);
0167     kritarc.setValue("canvasState", "OPENGL_SUCCESS");
0168 }
0169 
0170 extern "C" JNIEXPORT jboolean JNICALL
0171 Java_org_krita_android_JNIWrappers_exitFullScreen(JNIEnv* /*env*/,
0172                                                   jobject /*obj*/,
0173                                                   jint    /*n*/)
0174 {
0175     if (!KisPart::exists()) {
0176         return false;
0177     }
0178 
0179     KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
0180     if (mainWindow && mainWindow->isFullScreen()) {
0181         // since, this calls KisConfig, we need to make sure it happens on that
0182         // thread (we get here from the Android Main thread)
0183         QMetaObject::invokeMethod(mainWindow, "viewFullscreen",
0184                                   Qt::QueuedConnection, Q_ARG(bool, false));
0185         return true;
0186     } else {
0187         return false;
0188     }
0189 }
0190 
0191 extern "C" JNIEXPORT jboolean JNICALL
0192 Java_org_krita_android_JNIWrappers_hasMainWindowLoaded(JNIEnv * /*env*/,
0193                                                        jobject /*obj*/,
0194                                                        jint /*n*/)
0195 {
0196     if (!KisPart::exists()) {
0197         return false;
0198     }
0199 
0200     KisMainWindow *mainWindow = KisPart::instance()->currentMainwindow();
0201     return (bool)mainWindow;
0202 }
0203 
0204 extern "C" JNIEXPORT void JNICALL
0205 Java_org_krita_android_JNIWrappers_openFileFromIntent(JNIEnv* /*env*/,
0206                                                       jobject /*obj*/,
0207                                                       jstring str)
0208 {
0209     QAndroidJniObject jUri(str);
0210     if (jUri.isValid()) {
0211         QString uri = jUri.toString();
0212         QMetaObject::invokeMethod(KisApplication::instance(), "fileOpenRequested",
0213                                   Qt::QueuedConnection, Q_ARG(QString, uri));
0214     }
0215 }
0216 
0217 #define MAIN_EXPORT __attribute__ ((visibility ("default")))
0218 #define MAIN_FN main
0219 #elif defined Q_OS_WIN
0220 #define MAIN_EXPORT __declspec(dllexport)
0221 #define MAIN_FN krita_main
0222 #else
0223 #define MAIN_EXPORT
0224 #define MAIN_FN main
0225 #endif
0226 
0227 extern "C" MAIN_EXPORT int MAIN_FN(int argc, char **argv)
0228 {
0229 #ifdef Q_OS_WIN
0230     // Fix QCommandLineParser help output with UTF-8 codepage:
0231     if (GetACP() == CP_UTF8) {
0232         SetConsoleOutputCP(CP_UTF8);
0233     }
0234 #endif
0235 
0236     // The global initialization of the random generator
0237     {
0238         std::random_device urandom;
0239         qsrand(urandom());
0240     }
0241     bool runningInKDE = !qgetenv("KDE_FULL_SESSION").isEmpty();
0242 
0243 #if defined HAVE_X11
0244     qputenv("QT_QPA_PLATFORM", "xcb");
0245 #elif defined Q_OS_WIN
0246     if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
0247         qputenv("QT_QPA_PLATFORM", "windows:darkmode=1");
0248     }
0249 #endif
0250 
0251     // Workaround a bug in QNetworkManager
0252     qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
0253 
0254     // A per-user unique string, without /, because QLocalServer cannot use names with a / in it
0255     QString key = "Krita5" + QStandardPaths::writableLocation(QStandardPaths::HomeLocation).replace("/", "_");
0256     key = key.replace(":", "_").replace("\\","_");
0257 
0258     QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true);
0259 
0260     QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
0261     QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
0262 
0263     QCoreApplication::setAttribute(Qt::AA_DisableShaderDiskCache, true);
0264 
0265 #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY
0266     // This rounding policy depends on a series of patches to Qt related to
0267     // https://bugreports.qt.io/browse/QTBUG-53022. These patches are applied
0268     // in ext_qt for WIndows (patches 0031-0036).
0269     //
0270     // The rounding policy can be set externally by setting the environment
0271     // variable `QT_SCALE_FACTOR_ROUNDING_POLICY` to one of the following:
0272     //   Round:            Round up for .5 and above.
0273     //   Ceil:             Always round up.
0274     //   Floor:            Always round down.
0275     //   RoundPreferFloor: Round up for .75 and above.
0276     //   PassThrough:      Don't round.
0277     //
0278     // The default is set to RoundPreferFloor for better behaviour than before,
0279     // but can be overridden by the above environment variable.
0280     QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor);
0281 #endif
0282 
0283 #ifdef Q_OS_ANDROID
0284     const QString write_permission = "android.permission.WRITE_EXTERNAL_STORAGE";
0285     const QStringList permissions = { write_permission };
0286     const QtAndroid::PermissionResultMap resultHash =
0287             QtAndroid::requestPermissionsSync(QStringList(permissions));
0288 
0289     if (resultHash[write_permission] == QtAndroid::PermissionResult::Denied) {
0290         // TODO: show a dialog and graciously exit
0291         dbgKrita << "Permission denied by the user";
0292     }
0293     else {
0294         dbgKrita << "Permission granted";
0295     }
0296 
0297     KisAndroidCrashHandler::handler_init();
0298     qputenv("QT_ANDROID_ENABLE_RIGHT_MOUSE_FROM_LONG_PRESS", "1");
0299 
0300     qputenv("FONTCONFIG_PATH",
0301             QFile::encodeName(KoResourcePaths::getApplicationRoot()) + "/share/etc/fonts/");
0302     qputenv("XDG_CACHE_HOME",
0303             QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
0304 #endif
0305 
0306 #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
0307 
0308     // APPIMAGE SOUND ADDITIONS
0309     // MLT needs a few environment variables set to properly function in an appimage context.
0310     // The following code should be configured to **only** run when we detect that Krita is being
0311     // run within an appimage. Checking for the presence of an APPDIR path env variable seems to be
0312     // enough to filter out this step for non-appimage krita builds.
0313     const bool isInAppimage = qEnvironmentVariableIsSet("APPIMAGE");
0314     if (isInAppimage) {
0315         QByteArray appimageMountDir = qgetenv("APPDIR");
0316 
0317         {   // MLT
0318             //Plugins Path is where mlt should expect to find its plugin libraries.
0319             qputenv("MLT_REPOSITORY", appimageMountDir + QFile::encodeName("/usr/lib/mlt/"));
0320             qputenv("MLT_DATA", appimageMountDir + QFile::encodeName("/usr/share/mlt/"));
0321             qputenv("MLT_ROOT_DIR", appimageMountDir + QFile::encodeName("/usr/"));
0322             qputenv("MLT_PROFILES_PATH", appimageMountDir + QFile::encodeName("/usr/share/mlt/profiles/"));
0323             qputenv("MLT_PRESETS_PATH", appimageMountDir + QFile::encodeName("/usr/share/mlt/presets/"));
0324         }
0325 
0326         {
0327             /**
0328              * Since we package our own fontconfig into AppImage we should explicitly add
0329              * system configuration file into the search list. Otherwise it will not be found.
0330              *
0331              * Please note that FONTCONFIG_PATH should be set **before** we create our first
0332              * instance of QApplication for openGL probing, because it will make fontconfig
0333              * to be loaded before this environment variable set.
0334              */
0335             if (qgetenv("FONTCONFIG_PATH").isEmpty()) {
0336                 const QString defaultFontsConfig = "/etc/fonts/fonts.conf";
0337                 const QFileInfo info(defaultFontsConfig);
0338                 if (info.exists()) {
0339                     qputenv("FONTCONFIG_PATH", QFile::encodeName(QDir::toNativeSeparators(info.absolutePath())));
0340                 }
0341             }
0342         }
0343     }
0344 #endif
0345 
0346     const QDir configPath(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation));
0347     QSettings kritarc(configPath.absoluteFilePath("kritadisplayrc"), QSettings::IniFormat);
0348 
0349     QString root;
0350     QString language;
0351     {
0352         // Create a temporary application to get the root
0353         QCoreApplication app(argc, argv);
0354         Q_UNUSED(app);
0355         root = KoResourcePaths::getApplicationRoot();
0356         QSettings languageoverride(configPath.absoluteFilePath("klanguageoverridesrc"), QSettings::IniFormat);
0357         languageoverride.beginGroup("Language");
0358         language = languageoverride.value(qAppName(), "").toString();
0359     }
0360 
0361     bool enableOpenGLDebug = false;
0362     bool openGLDebugSynchronous = false;
0363     bool logUsage = true;
0364     {
0365         if (kritarc.value("EnableHiDPI", true).toBool()) {
0366             QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
0367         }
0368         if (!qgetenv("KRITA_HIDPI").isEmpty()) {
0369             QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
0370         }
0371 #ifdef HAVE_HIGH_DPI_SCALE_FACTOR_ROUNDING_POLICY
0372         if (kritarc.value("EnableHiDPIFractionalScaling", false).toBool()) {
0373             QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
0374         }
0375 #endif
0376 
0377         if (!qEnvironmentVariableIsEmpty("KRITA_OPENGL_DEBUG")) {
0378             enableOpenGLDebug = true;
0379         } else {
0380             enableOpenGLDebug = kritarc.value("EnableOpenGLDebug", false).toBool();
0381         }
0382         if (enableOpenGLDebug && (qgetenv("KRITA_OPENGL_DEBUG") == "sync" || kritarc.value("OpenGLDebugSynchronous", false).toBool())) {
0383             openGLDebugSynchronous = true;
0384         }
0385 
0386         KisConfig::RootSurfaceFormat rootSurfaceFormat = KisConfig::rootSurfaceFormat(&kritarc);
0387         KisOpenGL::OpenGLRenderer preferredRenderer = KisOpenGL::RendererAuto;
0388 
0389         logUsage = kritarc.value("LogUsage", true).toBool();
0390 
0391 #ifdef Q_OS_WIN
0392         const QString preferredRendererString = kritarc.value("OpenGLRenderer", "angle").toString();
0393 #else
0394         const QString preferredRendererString = kritarc.value("OpenGLRenderer", "auto").toString();
0395 #endif
0396         preferredRenderer = KisOpenGL::convertConfigToOpenGLRenderer(preferredRendererString);
0397 
0398         const KisOpenGL::RendererConfig config =
0399             KisOpenGL::selectSurfaceConfig(preferredRenderer, rootSurfaceFormat, enableOpenGLDebug);
0400 
0401         KisOpenGL::setDefaultSurfaceConfig(config);
0402         KisOpenGL::setDebugSynchronous(openGLDebugSynchronous);
0403 
0404 #ifdef Q_OS_WIN
0405         // HACK: https://bugs.kde.org/show_bug.cgi?id=390651
0406         resetRotation();
0407 #endif
0408     }
0409 
0410     if (logUsage) {
0411         KisUsageLogger::initialize();
0412     }
0413 
0414 
0415 #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
0416     {
0417         QByteArray originalXdgDataDirs = qgetenv("XDG_DATA_DIRS");
0418         if (originalXdgDataDirs.isEmpty()) {
0419             // We don't want to completely override the default
0420             originalXdgDataDirs = "/usr/local/share/:/usr/share/";
0421         }
0422 
0423         // NOTE: This line helps also fontconfig have a user-accessible location on Android (see the commit).
0424         qputenv("XDG_DATA_DIRS", QFile::encodeName(root + "share") + ":" + originalXdgDataDirs);
0425     }
0426 #else
0427     qputenv("XDG_DATA_DIRS", QFile::encodeName(QDir(root + "share").absolutePath()));
0428 #endif
0429 
0430     dbgKrita << "Setting XDG_DATA_DIRS" << qgetenv("XDG_DATA_DIRS");
0431 
0432     // Now that the paths are set, set the language. First check the override from the language
0433     // selection dialog.
0434 
0435     dbgLocale << "Override language:" << language;
0436     bool rightToLeft = false;
0437     if (!language.isEmpty()) {
0438         KLocalizedString::setLanguages(language.split(":"));
0439 
0440         // And override Qt's locale, too
0441         QLocale locale(language.split(":").first());
0442         QLocale::setDefault(locale);
0443 #ifdef Q_OS_MAC
0444         // prevents python >=3.7 nl_langinfo(CODESET) fail bug 417312.
0445         qputenv("LANG", (locale.name() + ".UTF-8").toLocal8Bit());
0446 #else
0447         qputenv("LANG", locale.name().toLocal8Bit());
0448 #endif
0449 
0450         const QStringList rtlLanguages = QStringList()
0451                 << "ar" << "dv" << "he" << "ha" << "ku" << "fa" << "ps" << "ur" << "yi";
0452 
0453         if (rtlLanguages.contains(language.split(':').first())) {
0454             rightToLeft = true;
0455         }
0456     }
0457     else {
0458         dbgLocale << "Qt UI languages:" << QLocale::system().uiLanguages() << qgetenv("LANG");
0459 
0460         // And if there isn't one, check the one set by the system.
0461         QLocale locale = QLocale::system();
0462 
0463 #ifdef Q_OS_ANDROID
0464         // QLocale::uiLanguages() fails on Android, so if the fallback locale is being
0465         // used we, try to fetch the device's default locale.
0466         if (locale.name() == QLocale::c().name()) {
0467             QAndroidJniObject localeJniObj = QAndroidJniObject::callStaticObjectMethod(
0468                 "java/util/Locale", "getDefault", "()Ljava/util/Locale;");
0469 
0470             if (localeJniObj.isValid()) {
0471                 QAndroidJniObject tag = localeJniObj.callObjectMethod("toLanguageTag",
0472                                                                       "()Ljava/lang/String;");
0473                 if (tag.isValid()) {
0474                     locale = QLocale(tag.toString());
0475                 }
0476             }
0477         }
0478 #endif
0479         if (locale.name() != QStringLiteral("en")) {
0480             QStringList uiLanguages = locale.uiLanguages();
0481             for (QString &uiLanguage : uiLanguages) {
0482 
0483                 // This list of language codes that can have a specifier should
0484                 // be extended whenever we have translations that need it; right
0485                 // now, only en, pt, zh are in this situation.
0486 
0487                 if (uiLanguage.startsWith("en") || uiLanguage.startsWith("pt")) {
0488                     uiLanguage.replace(QChar('-'), QChar('_'));
0489                 }
0490                 else if (uiLanguage.startsWith("zh-Hant") || uiLanguage.startsWith("zh-TW")) {
0491                     uiLanguage = "zh_TW";
0492                 }
0493                 else if (uiLanguage.startsWith("zh-Hans") || uiLanguage.startsWith("zh-CN")) {
0494                     uiLanguage = "zh_CN";
0495                 }
0496             }
0497 
0498             if (!uiLanguages.empty()) {
0499                 QString envLanguage = uiLanguages.first();
0500                 envLanguage.replace(QChar('-'), QChar('_'));
0501 
0502                 for (int i = 0; i < uiLanguages.size(); i++) {
0503                     QString uiLanguage = uiLanguages[i];
0504                     // Strip the country code
0505                     int idx = uiLanguage.indexOf(QChar('-'));
0506 
0507                     if (idx != -1) {
0508                         uiLanguage = uiLanguage.left(idx);
0509                         uiLanguages.replace(i, uiLanguage);
0510                     }
0511                 }
0512                 dbgLocale << "Converted ui languages:" << uiLanguages;
0513 #ifdef Q_OS_MAC
0514                 // See https://bugs.kde.org/show_bug.cgi?id=396370
0515                 KLocalizedString::setLanguages(QStringList() << uiLanguages.first());
0516                 qputenv("LANG", (envLanguage + ".UTF-8").toLocal8Bit());
0517 #else
0518                 KLocalizedString::setLanguages(QStringList() << uiLanguages);
0519                 qputenv("LANG", envLanguage.toLocal8Bit());
0520 #endif
0521             }
0522         }
0523     }
0524 
0525     KisUsageLogger::writeLocaleSysInfo();
0526 
0527 #if defined Q_OS_WIN && defined USE_QT_TABLET_WINDOWS && defined QT_HAS_WINTAB_SWITCH
0528     const bool forceWinTab = !KisConfig::useWin8PointerInputNoApp(&kritarc);
0529     QCoreApplication::setAttribute(Qt::AA_MSWindowsUseWinTabAPI, forceWinTab);
0530 
0531     if (qEnvironmentVariableIsEmpty("QT_WINTAB_DESKTOP_RECT") &&
0532         qEnvironmentVariableIsEmpty("QT_IGNORE_WINTAB_MAPPING")) {
0533 
0534         QRect customTabletRect;
0535         KisDlgCustomTabletResolution::Mode tabletMode =
0536             KisDlgCustomTabletResolution::getTabletMode(&customTabletRect);
0537         KisDlgCustomTabletResolution::applyConfiguration(tabletMode, customTabletRect);
0538     }
0539 #endif
0540 
0541     // first create the application so we can create a pixmap
0542     KisApplication app(key, argc, argv);
0543 
0544     installTranslators(app);
0545 
0546     if (KisApplication::platformName() == "wayland") {
0547         QMessageBox::critical(nullptr,
0548                               i18nc("@title:window", "Fatal Error"),
0549                               i18n("Krita does not support the Wayland platform. Use XWayland to run Krita on Wayland. Krita will close now."));
0550         return -1;
0551     }
0552 
0553     KisUsageLogger::writeHeader();
0554     KisOpenGL::initialize();
0555 
0556 #ifdef HAVE_SET_HAS_BORDER_IN_FULL_SCREEN_DEFAULT
0557     if (QCoreApplication::testAttribute(Qt::AA_UseDesktopOpenGL)) {
0558         QWindowsWindowFunctions::setHasBorderInFullScreenDefault(true);
0559     }
0560 #endif
0561 
0562 
0563     if (!language.isEmpty()) {
0564         if (rightToLeft) {
0565             KisApplication::setLayoutDirection(Qt::RightToLeft);
0566         }
0567         else {
0568             KisApplication::setLayoutDirection(Qt::LeftToRight);
0569         }
0570     }
0571 #ifdef Q_OS_ANDROID
0572     KisApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
0573 #endif
0574     // Enable debugging translations from undeployed apps
0575     KLocalizedString::addDomainLocaleDir("krita", QDir(root + "share/locale").absolutePath());
0576 
0577     KLocalizedString::setApplicationDomain("krita");
0578 
0579     dbgLocale << "Available translations" << KLocalizedString::availableApplicationTranslations();
0580     dbgLocale << "Available domain translations" << KLocalizedString::availableDomainTranslations("krita");
0581 
0582 
0583 #ifdef Q_OS_WIN
0584     QDir appdir(KoResourcePaths::getApplicationRoot());
0585     QString path = qgetenv("PATH");
0586     qputenv("PATH", QFile::encodeName(appdir.absolutePath() + "/bin" + ";"
0587                                       + appdir.absolutePath() + "/lib" + ";"
0588                                       + appdir.absolutePath() + "/Frameworks" + ";"
0589                                       + appdir.absolutePath() + ";"
0590                                       + path));
0591 
0592     dbgKrita << "PATH" << qgetenv("PATH");
0593 #endif
0594 
0595     if (KisApplication::applicationDirPath().contains(KRITA_BUILD_DIR)) {
0596         qFatal("FATAL: You're trying to run krita from the build location. You can only run Krita from the installation location.");
0597     }
0598 
0599 #if defined HAVE_KCRASH
0600     KCrash::initialize();
0601 #elif defined USE_DRMINGW
0602     tryInitDrMingw();
0603 #endif
0604 #if defined Q_OS_ANDROID
0605     // because we need qApp
0606     qputenv("MLT_REPOSITORY", QFile::encodeName(qApp->applicationDirPath()));
0607 
0608     QString loc;
0609     if (QStandardPaths::standardLocations(QStandardPaths::HomeLocation).size() > 1) {
0610         loc = QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[1];
0611     } else {
0612         loc = QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0];
0613     }
0614     qputenv("MLT_DATA", QFile::encodeName(loc + "/share/mlt/"));
0615     qputenv("MLT_ROOT_DIR", QFile::encodeName(loc));
0616     qputenv("MLT_PROFILES_PATH", QFile::encodeName(loc + "/share/mlt/profiles/"));
0617     qputenv("MLT_PRESETS_PATH", QFile::encodeName(loc + "/share/mlt/presets/"));
0618 #endif
0619     KisApplicationArguments args(app);
0620 
0621     if (app.isRunning()) {
0622         // only pass arguments to main instance if they are not for batch processing
0623         // any batch processing would be done in this separate instance
0624         const bool batchRun = args.exportAs() || args.exportSequence();
0625 
0626         if (!batchRun) {
0627             if (app.sendMessage(args.serialize())) {
0628                 return 0;
0629             }
0630         }
0631     }
0632 #ifdef Q_OS_MACOS
0633     // HACK: Sandboxed macOS cannot use QSharedMemory on Qt<6
0634     else if (KisMacosEntitlements().sandbox()) {
0635         if(iskritaRunningActivate()) {
0636             return 0;
0637         }
0638     }
0639 #endif
0640 
0641     if (!runningInKDE) {
0642         // Icons in menus are ugly and distracting
0643         KisApplication::setAttribute(Qt::AA_DontShowIconsInMenus);
0644     }
0645 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
0646     KisApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton);
0647 #endif
0648     app.installEventFilter(KisQtWidgetsTweaker::instance());
0649 
0650     if (!args.noSplash()) {
0651         QWidget *splash = new KisSplashScreen();
0652         app.setSplashScreen(splash);
0653     }
0654 
0655 #if defined Q_OS_WIN
0656     KisConfig cfg(false);
0657     bool supportedWindowsVersion = true;
0658     QOperatingSystemVersion osVersion = QOperatingSystemVersion::current();
0659     if (osVersion.type() == QOperatingSystemVersion::Windows) {
0660         if (osVersion.majorVersion() >= QOperatingSystemVersion::Windows7.majorVersion()) {
0661             supportedWindowsVersion  = true;
0662         }
0663         else {
0664             supportedWindowsVersion  = false;
0665             if (cfg.readEntry("WarnedAboutUnsupportedWindows", false)) {
0666                 QMessageBox::information(nullptr,
0667                                          i18nc("@title:window", "Krita: Warning"),
0668                                          i18n("You are running an unsupported version of Windows: %1.\n"
0669                                               "This is not recommended. Do not report any bugs.\n"
0670                                               "Please update to a supported version of Windows: Windows 7, 8, 8.1 or 10.", osVersion.name()));
0671                 cfg.writeEntry("WarnedAboutUnsupportedWindows", true);
0672 
0673             }
0674         }
0675     }
0676 #ifndef USE_QT_TABLET_WINDOWS
0677     {
0678         if (cfg.useWin8PointerInput() && !KisTabletSupportWin8::isAvailable()) {
0679             cfg.setUseWin8PointerInput(false);
0680         }
0681         if (!cfg.useWin8PointerInput()) {
0682             bool hasWinTab = KisTabletSupportWin::init();
0683             if (!hasWinTab && supportedWindowsVersion) {
0684                 if (KisTabletSupportWin8::isPenDeviceAvailable()) {
0685                     // Use WinInk automatically
0686                     cfg.setUseWin8PointerInput(true);
0687                 } else if (!cfg.readEntry("WarnedAboutMissingWinTab", false)) {
0688                     if (KisTabletSupportWin8::isAvailable()) {
0689                         QMessageBox::information(nullptr,
0690                                                  i18n("Krita Tablet Support"),
0691                                                  i18n("Cannot load WinTab driver and no Windows Ink pen devices are found. If you have a drawing tablet, please make sure the tablet driver is properly installed."),
0692                                                  QMessageBox::Ok, QMessageBox::Ok);
0693                     } else {
0694                         QMessageBox::information(nullptr,
0695                                                  i18n("Krita Tablet Support"),
0696                                                  i18n("Cannot load WinTab driver. If you have a drawing tablet, please make sure the tablet driver is properly installed."),
0697                                                  QMessageBox::Ok, QMessageBox::Ok);
0698                     }
0699                     cfg.writeEntry("WarnedAboutMissingWinTab", true);
0700                 }
0701             }
0702         }
0703         if (cfg.useWin8PointerInput()) {
0704             KisTabletSupportWin8 *penFilter = new KisTabletSupportWin8();
0705             if (penFilter->init()) {
0706                 // penFilter.registerPointerDeviceNotifications();
0707                 app.installNativeEventFilter(penFilter);
0708                 dbgKrita << "Using Win8 Pointer Input for tablet support";
0709             } else {
0710                 dbgKrita << "No Win8 Pointer Input available";
0711                 delete penFilter;
0712             }
0713         }
0714     }
0715 #elif defined QT_HAS_WINTAB_SWITCH
0716     Q_UNUSED(supportedWindowsVersion);
0717 
0718     // Check if WinTab/WinInk has actually activated
0719     const bool useWinInkAPI = !KisApplication::testAttribute(Qt::AA_MSWindowsUseWinTabAPI);
0720 
0721     if (useWinInkAPI != cfg.useWin8PointerInput()) {
0722         KisUsageLogger::log("WARNING: WinTab tablet protocol is not supported on this device. Switching to WinInk...");
0723 
0724         cfg.setUseWin8PointerInput(useWinInkAPI);
0725         cfg.setUseRightMiddleTabletButtonWorkaround(true);
0726     }
0727 
0728 #endif
0729 #endif
0730     KisApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents, false);
0731 
0732     // Set up remote arguments.
0733     QObject::connect(&app, SIGNAL(messageReceived(QByteArray,QObject*)),
0734                      &app, SLOT(remoteArguments(QByteArray,QObject*)));
0735 
0736     QObject::connect(&app, SIGNAL(fileOpenRequest(QString)),
0737                      &app, SLOT(fileOpenRequested(QString)));
0738 
0739     // Hardware information
0740     KisUsageLogger::writeSysInfo("\nHardware Information\n");
0741     KisUsageLogger::writeSysInfo(QString("  GPU Acceleration: %1").arg(kritarc.value("OpenGLRenderer", "auto").toString()));
0742     KisUsageLogger::writeSysInfo(QString("  Memory: %1 Mb").arg(KisImageConfig::totalRAM()));
0743     KisUsageLogger::writeSysInfo(QString("  Number of Cores: %1").arg(QThread::idealThreadCount()));
0744     KisUsageLogger::writeSysInfo(QString("  Swap Location: %1").arg(KisImageConfig(true).swapDir()));
0745     KisUsageLogger::writeSysInfo(
0746         QString("  Built for: %1")
0747             .arg(KisSupportedArchitectures::baseArchName()));
0748     KisUsageLogger::writeSysInfo(
0749         QString("  Base instruction set: %1")
0750             .arg(KisSupportedArchitectures::bestArchName()));
0751     KisUsageLogger::writeSysInfo(
0752         QString("  Supported instruction sets: %1")
0753             .arg(KisSupportedArchitectures::supportedInstructionSets()));
0754 
0755     KisUsageLogger::writeSysInfo("");
0756 
0757     KisConfig(true).logImportantSettings();
0758 
0759     KisApplication::setFont(KisUiFont::normalFont());
0760 
0761     if (!app.start(args)) {
0762         KisUsageLogger::log("Could not start Krita Application");
0763         return 1;
0764     }
0765 
0766     int state = KisApplication::exec();
0767 
0768     {
0769         QSettings kritarc(configPath.absoluteFilePath("kritadisplayrc"), QSettings::IniFormat);
0770         kritarc.setValue("canvasState", "OPENGL_SUCCESS");
0771     }
0772 
0773     if (logUsage) {
0774         KisUsageLogger::close();
0775     }
0776 
0777     return state;
0778 }
0779 
0780 namespace
0781 {
0782 
0783 void removeInstalledTranslators(KisApplication &app)
0784 {
0785     // HACK: We try to remove all the translators installed by ECMQmLoader.
0786     // The reason is that it always load translations for the system locale
0787     // which interferes with our effort to handle override languages. Since
0788     // `en_US` (or `en`) strings are defined in code, the QTranslator doesn't
0789     // actually handle translations for them, so even if we try to install
0790     // a QTranslator loaded from `en`, the strings always get translated by
0791     // the system language QTranslator that ECMQmLoader installed instead
0792     // of the English one.
0793 
0794     // ECMQmLoader creates all QTranslator's parented to the active QApp.
0795     QList<QTranslator *> translators = app.findChildren<QTranslator *>(QString(), Qt::FindDirectChildrenOnly);
0796     Q_FOREACH(const auto &translator, translators) {
0797         KisApplication::removeTranslator(translator);
0798     }
0799     dbgLocale << "Removed" << translators.size() << "QTranslator's";
0800 }
0801 
0802 void installPythonPluginUITranslator(KisApplication &app)
0803 {
0804     // Install a KLocalizedTranslator, so that when the bundled Python plugins
0805     // load their UI files using uic.loadUi() it can be translated.
0806     // These UI files must specify "pykrita_plugin_ui" as their class names.
0807     KLocalizedTranslator *translator = new KLocalizedTranslator(&app);
0808     translator->setObjectName(QStringLiteral("KLocalizedTranslator.pykrita_plugin_ui"));
0809     translator->setTranslationDomain(QStringLiteral("krita"));
0810     translator->addContextToMonitor(QStringLiteral("pykrita_plugin_ui"));
0811     KisApplication::installTranslator(translator);
0812 }
0813 
0814 void installQtTranslations(KisApplication &app)
0815 {
0816     const QStringList qtCatalogs = {
0817         QStringLiteral("qt_"),
0818         QStringLiteral("qtbase_"),
0819         QStringLiteral("qtdeclarative_"),
0820     };
0821     // A list of locale to add, note that the last added one has the
0822     // highest precedence.
0823     QList<QLocale> localeList;
0824     // We always use English as the final fallback.
0825     localeList.append(QLocale(QLocale::English));
0826     QLocale defaultLocale;
0827     if (defaultLocale.language() != QLocale::English) {
0828         localeList.append(defaultLocale);
0829     }
0830 
0831     QString translationsPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
0832     dbgLocale << "Qt translations path:" << translationsPath;
0833 
0834     Q_FOREACH(const auto &localeToLoad, localeList) {
0835         Q_FOREACH(const auto &catalog, qtCatalogs) {
0836             QTranslator *translator = new QTranslator(&app);
0837             if (translator->load(localeToLoad, catalog, QString(), translationsPath)) {
0838                 dbgLocale << "Loaded Qt translations for" << localeToLoad << catalog;
0839                 translator->setObjectName(QStringLiteral("QTranslator.%1.%2").arg(localeToLoad.name(), catalog));
0840                 KisApplication::installTranslator(translator);
0841             } else {
0842                 delete translator;
0843             }
0844         }
0845     }
0846 }
0847 
0848 void installEcmTranslations(KisApplication &app)
0849 {
0850     // Load translations created using the ECMPoQmTools module.
0851     // This function is based on the code in:
0852     // https://invent.kde.org/frameworks/extra-cmake-modules/-/blob/master/modules/ECMQmLoader.cpp.in
0853 
0854     QStringList ecmCatalogs = {
0855         QStringLiteral("kcompletion5_qt"),
0856         QStringLiteral("kconfig5_qt"),
0857         QStringLiteral("kcoreaddons5_qt"),
0858         QStringLiteral("kitemviews5_qt"),
0859         QStringLiteral("kwidgetsaddons5_qt"),
0860         QStringLiteral("kwindowsystem5_qt"),
0861         QStringLiteral("seexpr2_qt"),
0862     };
0863 
0864     QStringList ki18nLangs = KLocalizedString::languages();
0865     const QString langEn = QStringLiteral("en");
0866     // Replace "en_US" with "en" because that's what we have in the locale dir.
0867     int indexOfEnUs = ki18nLangs.indexOf(QStringLiteral("en_US"));
0868     if (indexOfEnUs != -1) {
0869         ki18nLangs[indexOfEnUs] = langEn;
0870     }
0871     // We need to have "en" to the end of the list, because we explicitly
0872     // removed the "en" translators added by ECMQmLoader.
0873     // If "en" is already on the list, we truncate the ones after, because
0874     // "en" is the catch-all fallback that has the strings in code.
0875     int indexOfEn = ki18nLangs.indexOf(langEn);
0876     if (indexOfEn != -1) {
0877         for (int i = ki18nLangs.size() - indexOfEn - 1; i > 0; i--) {
0878             ki18nLangs.removeLast();
0879         }
0880     } else {
0881         ki18nLangs.append(langEn);
0882     }
0883 
0884     // The last added one has the highest precedence, so we iterate the
0885     // list backwards.
0886     QStringListIterator langIter(ki18nLangs);
0887     langIter.toBack();
0888 
0889     while (langIter.hasPrevious()) {
0890         const QString &localeDirName = langIter.previous();
0891         Q_FOREACH(const auto &catalog, ecmCatalogs) {
0892             QString subPath = QStringLiteral("locale/") % localeDirName % QStringLiteral("/LC_MESSAGES/") % catalog % QStringLiteral(".qm");
0893 #if defined(Q_OS_ANDROID)
0894             const QString fullPath = QStringLiteral("assets:/") + subPath;
0895 #else
0896             const QString root = QLibraryInfo::location(QLibraryInfo::PrefixPath);
0897 
0898             // Our patched k18n uses AppDataLocation (for AppImage). Not using
0899             // KoResourcePaths::getAppDataLocation is correct here, because we
0900             // need to look into the installation folder, not the configured appdata
0901             // folder.
0902             QString fullPath = QStandardPaths::locate(QStandardPaths::AppDataLocation, subPath);
0903 
0904             if (fullPath.isEmpty()) {
0905                 // ... but distro builds probably still use GenericDataLocation,
0906                 // so check that too.
0907                 fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, subPath);
0908             }
0909 
0910             if (fullPath.isEmpty()) {
0911                 // And, failing all, use the deps install folder
0912                 fullPath = root + "/share/" + subPath;
0913             }
0914 #endif
0915             if (!QFile::exists(fullPath)) {
0916                 continue;
0917             }
0918 
0919             QTranslator *translator = new QTranslator(&app);
0920             if (translator->load(fullPath)) {
0921                 dbgLocale << "Loaded ECM translations for" << localeDirName << catalog;
0922                 translator->setObjectName(QStringLiteral("QTranslator.%1.%2").arg(localeDirName, catalog));
0923                 KisApplication::installTranslator(translator);
0924             } else {
0925                 delete translator;
0926             }
0927         }
0928     }
0929 }
0930 
0931 void installTranslators(KisApplication &app)
0932 {
0933     removeInstalledTranslators(app);
0934     installPythonPluginUITranslator(app);
0935     installQtTranslations(app);
0936     installEcmTranslations(app);
0937 }
0938 
0939 } // namespace