File indexing completed on 2025-02-09 05:01:49
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