File indexing completed on 2024-05-19 04:25:10

0001 /*
0002  *  SPDX-FileCopyrightText: 2019 Boudewijn Rempt <boud@valdyas.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 #include "KisUsageLogger.h"
0007 
0008 #include <QScreen>
0009 #include <QGlobalStatic>
0010 #include <QDebug>
0011 #include <QDateTime>
0012 #include <QSysInfo>
0013 #include <QStandardPaths>
0014 #include <QFile>
0015 #include <QFileInfo>
0016 #include <QDir>
0017 #include <QDesktopWidget>
0018 #include <QClipboard>
0019 #include <QThread>
0020 #include <QApplication>
0021 #include <klocalizedstring.h>
0022 #include <KritaVersionWrapper.h>
0023 #include <QGuiApplication>
0024 #include <QStyle>
0025 #include <QStyleFactory>
0026 #include <QTextCodec>
0027 
0028 #ifdef Q_OS_WIN
0029 #include "KisWindowsPackageUtils.h"
0030 #include <windows.h>
0031 #endif
0032 
0033 #ifdef Q_OS_ANDROID
0034 #include <QtAndroidExtras/QtAndroid>
0035 #endif
0036 
0037 #ifdef Q_OS_MACOS
0038 #include "KisMacosEntitlements.h"
0039 #endif
0040 
0041 #include <clocale>
0042 
0043 Q_GLOBAL_STATIC(KisUsageLogger, s_instance)
0044 
0045 const QString KisUsageLogger::s_sectionHeader("================================================================================\n");
0046 
0047 struct KisUsageLogger::Private {
0048     bool active {false};
0049     QFile logFile;
0050     QFile sysInfoFile;
0051 };
0052 
0053 KisUsageLogger::KisUsageLogger()
0054     : d(new Private)
0055 {
0056     if (!QFileInfo(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)).exists()) {
0057         QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation));
0058     }
0059     d->logFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita.log");
0060     d->sysInfoFile.setFileName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/krita-sysinfo.log");
0061 
0062     QFileInfo fi(d->logFile.fileName());
0063     if (fi.size() > 100 * 1000 * 1000) { // 100 mb seems a reasonable max
0064         d->logFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
0065         d->logFile.close();
0066     }
0067     else {
0068         rotateLog();
0069     }
0070 
0071     d->logFile.open(QFile::Append | QFile::Text);
0072     d->sysInfoFile.open(QFile::WriteOnly | QFile::Text);
0073 }
0074 
0075 KisUsageLogger::~KisUsageLogger()
0076 {
0077     if (d->active) {
0078         close();
0079     }
0080 }
0081 
0082 void KisUsageLogger::initialize()
0083 {
0084     s_instance->d->active = true;
0085 
0086     QString systemInfo = basicSystemInfo();
0087     s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
0088 }
0089 
0090 QString KisUsageLogger::basicSystemInfo()
0091 {
0092     QString systemInfo;
0093 
0094     // NOTE: This is intentionally not translated!
0095 
0096     // Krita version info
0097     systemInfo.append("Krita\n");
0098     systemInfo.append("\n Version: ").append(KritaVersionWrapper::versionString(true));
0099 #ifdef Q_OS_WIN
0100     {
0101         using namespace KisWindowsPackageUtils;
0102         QString packageFamilyName;
0103         QString packageFullName;
0104         systemInfo.append("\n Installation type: ");
0105         if (tryGetCurrentPackageFamilyName(&packageFamilyName) && tryGetCurrentPackageFullName(&packageFullName)) {
0106             systemInfo.append("Store / MSIX package\n    Family Name: ")
0107                 .append(packageFamilyName)
0108                 .append("\n    Full Name: ")
0109                 .append(packageFullName);
0110         } else {
0111             systemInfo.append("installer / portable package");
0112         }
0113     }
0114 #endif
0115     systemInfo.append("\n Hidpi: ").append(QCoreApplication::testAttribute(Qt::AA_EnableHighDpiScaling) ? "true" : "false");
0116 #ifdef Q_OS_MACOS
0117     KisMacosEntitlements entitlements;
0118     systemInfo.append("\n Sandbox: ").append((entitlements.sandbox()) ? "true" : "false");
0119 #endif
0120     systemInfo.append("\n\n");
0121 
0122     systemInfo.append("Qt\n");
0123     systemInfo.append("\n  Version (compiled): ").append(QT_VERSION_STR);
0124     systemInfo.append("\n  Version (loaded): ").append(qVersion());
0125     systemInfo.append("\n\n");
0126 
0127     // OS information
0128     systemInfo.append("OS Information\n");
0129     systemInfo.append("\n  Build ABI: ").append(QSysInfo::buildAbi());
0130     systemInfo.append("\n  Build CPU: ").append(QSysInfo::buildCpuArchitecture());
0131     systemInfo.append("\n  CPU: ").append(QSysInfo::currentCpuArchitecture());
0132     systemInfo.append("\n  Kernel Type: ").append(QSysInfo::kernelType());
0133     systemInfo.append("\n  Kernel Version: ").append(QSysInfo::kernelVersion());
0134     systemInfo.append("\n  Pretty Productname: ").append(QSysInfo::prettyProductName());
0135     systemInfo.append("\n  Product Type: ").append(QSysInfo::productType());
0136     systemInfo.append("\n  Product Version: ").append(QSysInfo::productVersion());
0137 
0138 #ifdef Q_OS_ANDROID
0139     QString manufacturer =
0140         QAndroidJniObject::getStaticObjectField("android/os/Build", "MANUFACTURER", "Ljava/lang/String;").toString();
0141     const QString model =
0142         QAndroidJniObject::getStaticObjectField("android/os/Build", "MODEL", "Ljava/lang/String;").toString();
0143     manufacturer[0] = manufacturer[0].toUpper();
0144     systemInfo.append("\n  Product Model: ").append(manufacturer + " " + model);
0145 #elif defined(Q_OS_LINUX)
0146     systemInfo.append("\n  Desktop: ").append(qgetenv("XDG_CURRENT_DESKTOP"));
0147 
0148     systemInfo.append("\n  Appimage build: ").append(qEnvironmentVariableIsSet("APPIMAGE") ? "Yes" : "No");
0149 #endif
0150     systemInfo.append("\n\n");
0151 
0152     return systemInfo;
0153 }
0154 
0155 void KisUsageLogger::writeLocaleSysInfo()
0156 {
0157     if (!s_instance->d->active) {
0158         return;
0159     }
0160     QString systemInfo;
0161     systemInfo.append("Locale\n");
0162     systemInfo.append("\n  Languages: ").append(KLocalizedString::languages().join(", "));
0163     systemInfo.append("\n  C locale: ").append(std::setlocale(LC_ALL, nullptr));
0164     systemInfo.append("\n  QLocale current: ").append(QLocale().bcp47Name());
0165     systemInfo.append("\n  QLocale system: ").append(QLocale::system().bcp47Name());
0166     const QTextCodec *codecForLocale = QTextCodec::codecForLocale();
0167     systemInfo.append("\n  QTextCodec for locale: ").append(codecForLocale->name());
0168 #ifdef Q_OS_WIN
0169     {
0170         systemInfo.append("\n  Process ACP: ");
0171         CPINFOEXW cpInfo {};
0172         if (GetCPInfoExW(CP_ACP, 0, &cpInfo)) {
0173             systemInfo.append(QString::fromWCharArray(cpInfo.CodePageName));
0174         } else {
0175             // Shouldn't happen, but just in case
0176             systemInfo.append(QString::number(GetACP()));
0177         }
0178         wchar_t lcData[2];
0179         int result = GetLocaleInfoEx(LOCALE_NAME_SYSTEM_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER, lcData, sizeof(lcData) / sizeof(lcData[0]));
0180         if (result == 2) {
0181             systemInfo.append("\n  System locale default ACP: ");
0182             int systemACP = lcData[1] << 16 | lcData[0];
0183             if (systemACP == CP_ACP) {
0184                 systemInfo.append("N/A");
0185             } else if (GetCPInfoExW(systemACP, 0, &cpInfo)) {
0186                 systemInfo.append(QString::fromWCharArray(cpInfo.CodePageName));
0187             } else {
0188                 // Shouldn't happen, but just in case
0189                 systemInfo.append(QString::number(systemACP));
0190             }
0191         }
0192     }
0193 #endif
0194     systemInfo.append("\n\n");
0195     s_instance->d->sysInfoFile.write(systemInfo.toUtf8());
0196 }
0197 
0198 void KisUsageLogger::close()
0199 {
0200     log("CLOSING SESSION");
0201     s_instance->d->active = false;
0202     s_instance->d->logFile.flush();
0203     s_instance->d->logFile.close();
0204     s_instance->d->sysInfoFile.flush();
0205     s_instance->d->sysInfoFile.close();
0206 }
0207 
0208 void KisUsageLogger::log(const QString &message)
0209 {
0210     if (!s_instance->d->active) return;
0211     if (!s_instance->d->logFile.isOpen()) return;
0212 
0213     s_instance->d->logFile.write(QDateTime::currentDateTime().toString(Qt::RFC2822Date).toUtf8());
0214     s_instance->d->logFile.write(": ");
0215     write(message);
0216 }
0217 
0218 void KisUsageLogger::write(const QString &message)
0219 {
0220     if (!s_instance->d->active) return;
0221     if (!s_instance->d->logFile.isOpen()) return;
0222 
0223     s_instance->d->logFile.write(message.toUtf8());
0224     s_instance->d->logFile.write("\n");
0225 
0226     s_instance->d->logFile.flush();
0227 }
0228 
0229 void KisUsageLogger::writeSysInfo(const QString &message)
0230 {
0231     if (!s_instance->d->active) return;
0232     if (!s_instance->d->sysInfoFile.isOpen()) return;
0233 
0234     s_instance->d->sysInfoFile.write(message.toUtf8());
0235     s_instance->d->sysInfoFile.write("\n");
0236 
0237     s_instance->d->sysInfoFile.flush();
0238 
0239 }
0240 
0241 
0242 void KisUsageLogger::writeHeader()
0243 {
0244     Q_ASSERT(s_instance->d->sysInfoFile.isOpen());
0245     s_instance->d->logFile.write(s_sectionHeader.toUtf8());
0246 
0247     QString sessionHeader = QString("SESSION: %1. Executing %2\n\n")
0248             .arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date))
0249             .arg(qApp->arguments().join(' '));
0250 
0251     s_instance->d->logFile.write(sessionHeader.toUtf8());
0252 
0253     QString KritaAndQtVersion;
0254     KritaAndQtVersion.append("Krita Version: ").append(KritaVersionWrapper::versionString(true))
0255             .append(", Qt version compiled: ").append(QT_VERSION_STR)
0256             .append(", loaded: ").append(qVersion())
0257             .append(". Process ID: ")
0258             .append(QString::number(qApp->applicationPid())).append("\n");
0259 
0260     KritaAndQtVersion.append("-- -- -- -- -- -- -- --\n");
0261     s_instance->d->logFile.write(KritaAndQtVersion.toUtf8());
0262     s_instance->d->logFile.flush();
0263     log(QString("Style: %1. Available styles: %2")
0264         .arg(qApp->style()->objectName(),
0265              QStyleFactory::keys().join(", ")));
0266 
0267 }
0268 
0269 QString KisUsageLogger::screenInformation()
0270 {
0271     QList<QScreen*> screens = qApp->screens();
0272 
0273     QString info;
0274     info.append("Display Information");
0275     info.append("\nNumber of screens: ").append(QString::number(screens.size()));
0276 
0277     for (int i = 0; i < screens.size(); ++i ) {
0278         QScreen *screen = screens[i];
0279         info.append("\n\tScreen: ").append(QString::number(i));
0280         info.append("\n\t\tName: ").append(screen->name());
0281         info.append("\n\t\tDepth: ").append(QString::number(screen->depth()));
0282         info.append("\n\t\tScale: ").append(QString::number(screen->devicePixelRatio()));
0283         info.append("\n\t\tPhysical DPI").append(QString::number(screen->physicalDotsPerInch()));
0284         info.append("\n\t\tLogical DPI").append(QString::number(screen->logicalDotsPerInch()));
0285         info.append("\n\t\tPhysical Size: ").append(QString::number(screen->physicalSize().width()))
0286                 .append(", ")
0287                 .append(QString::number(screen->physicalSize().height()));
0288         info.append("\n\t\tPosition: ").append(QString::number(screen->geometry().x()))
0289                 .append(", ")
0290                 .append(QString::number(screen->geometry().y()));
0291         info.append("\n\t\tResolution in pixels: ").append(QString::number(screen->geometry().width()))
0292                 .append("x")
0293                 .append(QString::number(screen->geometry().height()));
0294         info.append("\n\t\tManufacturer: ").append(screen->manufacturer());
0295         info.append("\n\t\tModel: ").append(screen->model());
0296         info.append("\n\t\tRefresh Rate: ").append(QString::number(screen->refreshRate()));
0297         info.append("\n\t\tSerial Number: ").append(screen->serialNumber());
0298 
0299     }
0300     info.append("\n");
0301     return info;
0302 }
0303 
0304 void KisUsageLogger::rotateLog()
0305 {
0306     if (d->logFile.exists()) {
0307         {
0308             // Check for CLOSING SESSION
0309             d->logFile.open(QFile::ReadOnly);
0310             QString log = QString::fromUtf8(d->logFile.readAll());
0311             if (!log.split(s_sectionHeader).last().contains("CLOSING SESSION")) {
0312                 log.append("\nKRITA DID NOT CLOSE CORRECTLY\n");
0313                 QString crashLog = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/kritacrash.log");
0314                 if (QFileInfo(crashLog).exists()) {
0315                     QFile f(crashLog);
0316                     f.open(QFile::ReadOnly);
0317                     QString crashes = QString::fromUtf8(f.readAll());
0318                     f.close();
0319 
0320                     QStringList crashlist = crashes.split("-------------------");
0321                     log.append(QString("\nThere were %1 crashes in total in the crash log.\n").arg(crashlist.size()));
0322 
0323                     if (crashes.size() > 0) {
0324                         log.append(crashlist.last());
0325                     }
0326                 }
0327                 d->logFile.close();
0328                 d->logFile.open(QFile::WriteOnly);
0329                 d->logFile.write(log.toUtf8());
0330             }
0331             d->logFile.flush();
0332             d->logFile.close();
0333         }
0334 
0335         {
0336             // Rotate
0337             d->logFile.open(QFile::ReadOnly);
0338             QString log = QString::fromUtf8(d->logFile.readAll());
0339             d->logFile.close();
0340             QStringList logItems = log.split("SESSION:");
0341             QStringList keptItems;
0342             int sectionCount = logItems.size();
0343             if (sectionCount > s_maxLogs) {
0344                 for (int i = sectionCount - s_maxLogs; i < sectionCount; ++i) {
0345                     if (logItems.size() > i ) {
0346                         keptItems.append(logItems[i]);
0347                     }
0348                 }
0349 
0350                 d->logFile.open(QFile::WriteOnly);
0351                 d->logFile.write(keptItems.join("\nSESSION:").toUtf8());
0352                 d->logFile.flush();
0353                 d->logFile.close();
0354             }
0355         }
0356 
0357 
0358     }
0359 }
0360