Files
qt-creator/src/app/main.cpp
Thomas Hartmann 1bd967badb Avoid parsing the commandline arguments twice
We parse the arguments independently from QApplication
and initialize the settings before we create QApplication.

Therefore we can parse any setting before creating
QApplication which is required for high DPI support.

We use the QApplication argument parsing to filter out Qt
specific commandline arguments like style.

Change-Id: I83ead87476d99351de6fe4f86c96c39a721af47b
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
2018-04-13 12:18:13 +00:00

600 lines
24 KiB
C++

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
#include "../tools/qtcreatorcrashhandler/crashhandlersetup.h"
#include <app/app_version.h>
#include <extensionsystem/iplugin.h>
#include <extensionsystem/pluginerroroverview.h>
#include <extensionsystem/pluginmanager.h>
#include <extensionsystem/pluginspec.h>
#include <qtsingleapplication.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/temporarydirectory.h>
#include <QDebug>
#include <QDir>
#include <QFontDatabase>
#include <QFileInfo>
#include <QLibraryInfo>
#include <QLoggingCategory>
#include <QSettings>
#include <QStyle>
#include <QTextStream>
#include <QThreadPool>
#include <QTimer>
#include <QTranslator>
#include <QUrl>
#include <QVariant>
#include <QNetworkProxyFactory>
#include <QApplication>
#include <QMessageBox>
#include <QStandardPaths>
#include <QTemporaryDir>
#include <string>
#include <vector>
#include <iterator>
#ifdef ENABLE_QT_BREAKPAD
#include <qtsystemexceptionhandler.h>
#endif
using namespace ExtensionSystem;
enum { OptionIndent = 4, DescriptionIndent = 34 };
const char corePluginNameC[] = "Core";
const char fixedOptionsC[] =
" [OPTION]... [FILE]...\n"
"Options:\n"
" -help Display this help\n"
" -version Display program version\n"
" -client Attempt to connect to already running first instance\n"
" -settingspath <path> Override the default path where user settings are stored\n"
" -installsettingspath <path> Override the default path from where user-independent settings are read\n"
" -pid <pid> Attempt to connect to instance given by pid\n"
" -block Block until editor is closed\n"
" -pluginpath <path> Add a custom search path for plugins\n";
const char HELP_OPTION1[] = "-h";
const char HELP_OPTION2[] = "-help";
const char HELP_OPTION3[] = "/h";
const char HELP_OPTION4[] = "--help";
const char VERSION_OPTION[] = "-version";
const char CLIENT_OPTION[] = "-client";
const char SETTINGS_OPTION[] = "-settingspath";
const char INSTALL_SETTINGS_OPTION[] = "-installsettingspath";
const char TEST_OPTION[] = "-test";
const char PID_OPTION[] = "-pid";
const char BLOCK_OPTION[] = "-block";
const char PLUGINPATH_OPTION[] = "-pluginpath";
typedef QList<PluginSpec *> PluginSpecSet;
// Helpers for displaying messages. Note that there is no console on Windows.
// Format as <pre> HTML
static inline QString toHtml(const QString &t)
{
QString res = t;
res.replace(QLatin1Char('&'), QLatin1String("&amp;"));
res.replace(QLatin1Char('<'), QLatin1String("&lt;"));
res.replace(QLatin1Char('>'), QLatin1String("&gt;"));
res.insert(0, QLatin1String("<html><pre>"));
res.append(QLatin1String("</pre></html>"));
return res;
}
static void displayHelpText(const QString &t)
{
if (Utils::HostOsInfo::isWindowsHost())
QMessageBox::information(0, QLatin1String(Core::Constants::IDE_DISPLAY_NAME), toHtml(t));
else
qWarning("%s", qPrintable(t));
}
static void displayError(const QString &t)
{
if (Utils::HostOsInfo::isWindowsHost())
QMessageBox::critical(0, QLatin1String(Core::Constants::IDE_DISPLAY_NAME), t);
else
qCritical("%s", qPrintable(t));
}
static void printVersion(const PluginSpec *coreplugin)
{
QString version;
QTextStream str(&version);
str << '\n' << Core::Constants::IDE_DISPLAY_NAME << ' ' << coreplugin->version()<< " based on Qt " << qVersion() << "\n\n";
PluginManager::formatPluginVersions(str);
str << '\n' << coreplugin->copyright() << '\n';
displayHelpText(version);
}
static void printHelp(const QString &a0)
{
QString help;
QTextStream str(&help);
str << "Usage: " << a0 << fixedOptionsC;
PluginManager::formatOptions(str, OptionIndent, DescriptionIndent);
PluginManager::formatPluginOptions(str, OptionIndent, DescriptionIndent);
displayHelpText(help);
}
QString applicationDirPath(char *arg = 0)
{
static QString dir;
if (arg)
dir = QFileInfo(QString::fromLocal8Bit(arg)).dir().absolutePath();
if (QCoreApplication::instance())
return QApplication::applicationDirPath();
return dir;
}
static QString resourcePath()
{
return QDir::cleanPath(applicationDirPath() + '/' + RELATIVE_DATA_PATH);
}
static inline QString msgCoreLoadFailure(const QString &why)
{
return QCoreApplication::translate("Application", "Failed to load core: %1").arg(why);
}
static inline int askMsgSendFailed()
{
return QMessageBox::question(0, QApplication::translate("Application","Could not send message"),
QCoreApplication::translate("Application", "Unable to send command line arguments "
"to the already running instance. It does not appear to "
"be responding. Do you want to start a new instance of "
"%1?").arg(Core::Constants::IDE_DISPLAY_NAME),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Retry,
QMessageBox::Retry);
}
// taken from utils/fileutils.cpp. We can not use utils here since that depends app_version.h.
static bool copyRecursively(const QString &srcFilePath,
const QString &tgtFilePath)
{
QFileInfo srcFileInfo(srcFilePath);
if (srcFileInfo.isDir()) {
QDir targetDir(tgtFilePath);
targetDir.cdUp();
if (!targetDir.mkdir(Utils::FileName::fromString(tgtFilePath).fileName()))
return false;
QDir sourceDir(srcFilePath);
QStringList fileNames = sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
foreach (const QString &fileName, fileNames) {
const QString newSrcFilePath
= srcFilePath + QLatin1Char('/') + fileName;
const QString newTgtFilePath
= tgtFilePath + QLatin1Char('/') + fileName;
if (!copyRecursively(newSrcFilePath, newTgtFilePath))
return false;
}
} else {
if (!QFile::copy(srcFilePath, tgtFilePath))
return false;
}
return true;
}
static inline QStringList getPluginPaths()
{
QStringList rc(QDir::cleanPath(QApplication::applicationDirPath()
+ '/' + RELATIVE_PLUGIN_PATH));
// Local plugin path: <localappdata>/plugins/<ideversion>
// where <localappdata> is e.g.
// "%LOCALAPPDATA%\QtProject\qtcreator" on Windows Vista and later
// "$XDG_DATA_HOME/data/QtProject/qtcreator" or "~/.local/share/data/QtProject/qtcreator" on Linux
// "~/Library/Application Support/QtProject/Qt Creator" on Mac
QString pluginPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation);
if (Utils::HostOsInfo::isAnyUnixHost() && !Utils::HostOsInfo::isMacHost())
pluginPath += QLatin1String("/data");
pluginPath += QLatin1Char('/')
+ QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR)
+ QLatin1Char('/');
pluginPath += QLatin1String(Utils::HostOsInfo::isMacHost() ?
Core::Constants::IDE_DISPLAY_NAME :
Core::Constants::IDE_ID);
pluginPath += QLatin1String("/plugins/");
pluginPath += QLatin1String(Core::Constants::IDE_VERSION_LONG);
rc.push_back(pluginPath);
return rc;
}
static void setupInstallSettings(QString &installSettingspath)
{
if (!installSettingspath.isEmpty() && !QFileInfo(installSettingspath).isDir()) {
displayHelpText(QString("-installsettingspath needs to be the path where a %1/%2.ini exist.").arg(
QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR), QLatin1String(Core::Constants::IDE_CASED_ID)));
installSettingspath.clear();
}
// Check if the default install settings contain a setting for the actual install settings.
// This can be an absolute path, or a path relative to applicationDirPath().
// The result is interpreted like -settingspath, but for SystemScope
static const char kInstallSettingsKey[] = "Settings/InstallSettings";
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope,
installSettingspath.isEmpty() ? resourcePath() : installSettingspath);
QSettings installSettings(QSettings::IniFormat, QSettings::UserScope,
QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR),
QLatin1String(Core::Constants::IDE_CASED_ID));
if (installSettings.contains(kInstallSettingsKey)) {
QString installSettingsPath = installSettings.value(kInstallSettingsKey).toString();
if (QDir::isRelativePath(installSettingsPath))
installSettingsPath = applicationDirPath() + '/' + installSettingsPath;
QSettings::setPath(QSettings::IniFormat, QSettings::SystemScope, installSettingsPath);
}
}
static QSettings *createUserSettings()
{
return new QSettings(QSettings::IniFormat, QSettings::UserScope,
QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR),
QLatin1String(Core::Constants::IDE_CASED_ID));
}
static inline QSettings *userSettings()
{
QSettings *settings = createUserSettings();
const QString fromVariant = QLatin1String(Core::Constants::IDE_COPY_SETTINGS_FROM_VARIANT_STR);
if (fromVariant.isEmpty())
return settings;
// Copy old settings to new ones:
QFileInfo pathFi = QFileInfo(settings->fileName());
if (pathFi.exists()) // already copied.
return settings;
QDir destDir = pathFi.absolutePath();
if (!destDir.exists())
destDir.mkpath(pathFi.absolutePath());
QDir srcDir = destDir;
srcDir.cdUp();
if (!srcDir.cd(fromVariant))
return settings;
if (srcDir == destDir) // Nothing to copy and no settings yet
return settings;
QStringList entries = srcDir.entryList();
foreach (const QString &file, entries) {
const QString lowerFile = file.toLower();
if (lowerFile.startsWith(QLatin1String("profiles.xml"))
|| lowerFile.startsWith(QLatin1String("toolchains.xml"))
|| lowerFile.startsWith(QLatin1String("qtversion.xml"))
|| lowerFile.startsWith(QLatin1String("devices.xml"))
|| lowerFile.startsWith(QLatin1String("debuggers.xml"))
|| lowerFile.startsWith(QLatin1String(Core::Constants::IDE_ID) + "."))
QFile::copy(srcDir.absoluteFilePath(file), destDir.absoluteFilePath(file));
if (file == QLatin1String(Core::Constants::IDE_ID))
copyRecursively(srcDir.absoluteFilePath(file), destDir.absoluteFilePath(file));
}
// Make sure to use the copied settings:
delete settings;
return createUserSettings();
}
static void setHighDpiEnvironmentVariable()
{
if (Utils::HostOsInfo().isMacHost())
return;
std::unique_ptr<QSettings> settings(createUserSettings());
const bool defaultValue = Utils::HostOsInfo().isWindowsHost();
const bool enableHighDpiScaling = settings->value("Core/EnableHighDpiScaling", defaultValue).toBool();
static const char ENV_VAR_QT_DEVICE_PIXEL_RATIO[] = "QT_DEVICE_PIXEL_RATIO";
if (enableHighDpiScaling
&& !qEnvironmentVariableIsSet(ENV_VAR_QT_DEVICE_PIXEL_RATIO) // legacy in 5.6, but still functional
&& !qEnvironmentVariableIsSet("QT_AUTO_SCREEN_SCALE_FACTOR")
&& !qEnvironmentVariableIsSet("QT_SCALE_FACTOR")
&& !qEnvironmentVariableIsSet("QT_SCREEN_SCALE_FACTORS")) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
}
}
void loadFonts()
{
const QDir dir(resourcePath() + "/fonts/");
foreach (const QFileInfo &fileInfo, dir.entryInfoList(QStringList("*.ttf"), QDir::Files))
QFontDatabase::addApplicationFont(fileInfo.absoluteFilePath());
}
int main(int argc, char **argv)
{
#ifdef Q_OS_WIN
if (!qEnvironmentVariableIsSet("QT_OPENGL"))
qputenv("QT_OPENGL", "angle");
#endif
if (qEnvironmentVariableIsSet("QTCREATOR_DISABLE_NATIVE_MENUBAR")
|| qgetenv("XDG_CURRENT_DESKTOP").startsWith("Unity")) {
QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
}
Utils::TemporaryDirectory::setMasterTemporaryDirectory(QDir::tempPath() + "/" + Core::Constants::IDE_CASED_ID + "-XXXXXX");
QLoggingCategory::setFilterRules(QLatin1String("qtc.*.debug=false\nqtc.*.info=false"));
#ifdef Q_OS_MAC
// increase the number of file that can be opened in Qt Creator.
struct rlimit rl;
getrlimit(RLIMIT_NOFILE, &rl);
rl.rlim_cur = qMin((rlim_t)OPEN_MAX, rl.rlim_max);
setrlimit(RLIMIT_NOFILE, &rl);
#endif
// Manually determine -settingspath and -installsettingspath command line options
// We can't use the regular way of the plugin manager, because that needs to parse plugin meta data
// but the settings path can influence which plugins are enabled
QString settingsPath;
QString installSettingsPath;
QStringList customPluginPaths;
std::vector<char *> appArguments;
bool hasTestOption = false;
auto it = argv;
const auto end = argv + argc;
while (it != end) {
const auto arg = QString::fromLocal8Bit(*it);
const bool hasNext = it + 1 != end;
if (arg == SETTINGS_OPTION && hasNext) {
++it;
settingsPath = QDir::fromNativeSeparators(QString::fromLocal8Bit(*it));
} else if (arg == INSTALL_SETTINGS_OPTION && hasNext) {
++it;
installSettingsPath = QDir::fromNativeSeparators(QString::fromLocal8Bit(*it));
} else if (arg == PLUGINPATH_OPTION && hasNext) {
++it;
customPluginPaths += QDir::fromNativeSeparators(QString::fromLocal8Bit(*it));
} else {
if (arg == TEST_OPTION)
hasTestOption = true;
appArguments.push_back(*it);
}
++it;
}
applicationDirPath(argv[0]);
QScopedPointer<Utils::TemporaryDirectory> temporaryCleanSettingsDir;
if (settingsPath.isEmpty() && hasTestOption) {
temporaryCleanSettingsDir.reset(new Utils::TemporaryDirectory("qtc-test-settings"));
if (!temporaryCleanSettingsDir->isValid())
return 1;
settingsPath = temporaryCleanSettingsDir->path();
}
if (!settingsPath.isEmpty())
QSettings::setPath(QSettings::IniFormat, QSettings::UserScope, settingsPath);
// Must be done before any QSettings class is created
QSettings::setDefaultFormat(QSettings::IniFormat);
setupInstallSettings(installSettingsPath);
// plugin manager takes control of this settings object
setHighDpiEnvironmentVariable();
SharedTools::QtSingleApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
int numberofArguments = static_cast<int>(appArguments.size());
SharedTools::QtSingleApplication app((QLatin1String(Core::Constants::IDE_DISPLAY_NAME)),
numberofArguments,
appArguments.data());
const QStringList pluginArguments = app.arguments();
/*Initialize global settings and resetup install settings with QApplication::applicationDirPath */
setupInstallSettings(installSettingsPath);
QSettings *settings = userSettings();
QSettings *globalSettings = new QSettings(QSettings::IniFormat, QSettings::SystemScope,
QLatin1String(Core::Constants::IDE_SETTINGSVARIANT_STR),
QLatin1String(Core::Constants::IDE_CASED_ID));
loadFonts();
if (Utils::HostOsInfo().isWindowsHost()
&& !qFuzzyCompare(qApp->devicePixelRatio(), 1.0)
&& QApplication::style()->objectName().startsWith(
QLatin1String("windows"), Qt::CaseInsensitive)) {
QApplication::setStyle(QLatin1String("fusion"));
}
const int threadCount = QThreadPool::globalInstance()->maxThreadCount();
QThreadPool::globalInstance()->setMaxThreadCount(qMax(4, 2 * threadCount));
const QString libexecPath = QCoreApplication::applicationDirPath()
+ '/' + RELATIVE_LIBEXEC_PATH;
#ifdef ENABLE_QT_BREAKPAD
QtSystemExceptionHandler systemExceptionHandler(libexecPath);
#else
// Display a backtrace once a serious signal is delivered (Linux only).
CrashHandlerSetup setupCrashHandler(Core::Constants::IDE_DISPLAY_NAME,
CrashHandlerSetup::EnableRestart, libexecPath);
#endif
app.setAttribute(Qt::AA_UseHighDpiPixmaps);
PluginManager pluginManager;
PluginManager::setPluginIID(QLatin1String("org.qt-project.Qt.QtCreatorPlugin"));
PluginManager::setGlobalSettings(globalSettings);
PluginManager::setSettings(settings);
QTranslator translator;
QTranslator qtTranslator;
QStringList uiLanguages;
uiLanguages = QLocale::system().uiLanguages();
QString overrideLanguage = settings->value(QLatin1String("General/OverrideLanguage")).toString();
if (!overrideLanguage.isEmpty())
uiLanguages.prepend(overrideLanguage);
const QString &creatorTrPath = resourcePath() + "/translations";
foreach (QString locale, uiLanguages) {
locale = QLocale(locale).name();
if (translator.load("qtcreator_" + locale, creatorTrPath)) {
const QString &qtTrPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
const QString &qtTrFile = QLatin1String("qt_") + locale;
// Binary installer puts Qt tr files into creatorTrPath
if (qtTranslator.load(qtTrFile, qtTrPath) || qtTranslator.load(qtTrFile, creatorTrPath)) {
app.installTranslator(&translator);
app.installTranslator(&qtTranslator);
app.setProperty("qtc_locale", locale);
break;
}
translator.load(QString()); // unload()
} else if (locale == QLatin1String("C") /* overrideLanguage == "English" */) {
// use built-in
break;
} else if (locale.startsWith(QLatin1String("en")) /* "English" is built-in */) {
// use built-in
break;
}
}
// Make sure we honor the system's proxy settings
QNetworkProxyFactory::setUseSystemConfiguration(true);
// Load
const QStringList pluginPaths = getPluginPaths() + customPluginPaths;
PluginManager::setPluginPaths(pluginPaths);
QMap<QString, QString> foundAppOptions;
if (pluginArguments.size() > 1) {
QMap<QString, bool> appOptions;
appOptions.insert(QLatin1String(HELP_OPTION1), false);
appOptions.insert(QLatin1String(HELP_OPTION2), false);
appOptions.insert(QLatin1String(HELP_OPTION3), false);
appOptions.insert(QLatin1String(HELP_OPTION4), false);
appOptions.insert(QLatin1String(VERSION_OPTION), false);
appOptions.insert(QLatin1String(CLIENT_OPTION), false);
appOptions.insert(QLatin1String(PID_OPTION), true);
appOptions.insert(QLatin1String(BLOCK_OPTION), false);
QString errorMessage;
if (!PluginManager::parseOptions(pluginArguments, appOptions, &foundAppOptions, &errorMessage)) {
displayError(errorMessage);
printHelp(QFileInfo(app.applicationFilePath()).baseName());
return -1;
}
}
const PluginSpecSet plugins = PluginManager::plugins();
PluginSpec *coreplugin = 0;
foreach (PluginSpec *spec, plugins) {
if (spec->name() == QLatin1String(corePluginNameC)) {
coreplugin = spec;
break;
}
}
if (!coreplugin) {
QString nativePaths = QDir::toNativeSeparators(pluginPaths.join(QLatin1Char(',')));
const QString reason = QCoreApplication::translate("Application", "Could not find Core plugin in %1").arg(nativePaths);
displayError(msgCoreLoadFailure(reason));
return 1;
}
if (!coreplugin->isEffectivelyEnabled()) {
const QString reason = QCoreApplication::translate("Application", "Core plugin is disabled.");
displayError(msgCoreLoadFailure(reason));
return 1;
}
if (coreplugin->hasError()) {
displayError(msgCoreLoadFailure(coreplugin->errorString()));
return 1;
}
if (foundAppOptions.contains(QLatin1String(VERSION_OPTION))) {
printVersion(coreplugin);
return 0;
}
if (foundAppOptions.contains(QLatin1String(HELP_OPTION1))
|| foundAppOptions.contains(QLatin1String(HELP_OPTION2))
|| foundAppOptions.contains(QLatin1String(HELP_OPTION3))
|| foundAppOptions.contains(QLatin1String(HELP_OPTION4))) {
printHelp(QFileInfo(app.applicationFilePath()).baseName());
return 0;
}
qint64 pid = -1;
if (foundAppOptions.contains(QLatin1String(PID_OPTION))) {
QString pidString = foundAppOptions.value(QLatin1String(PID_OPTION));
bool pidOk;
qint64 tmpPid = pidString.toInt(&pidOk);
if (pidOk)
pid = tmpPid;
}
bool isBlock = foundAppOptions.contains(QLatin1String(BLOCK_OPTION));
if (app.isRunning() && (pid != -1 || isBlock
|| foundAppOptions.contains(QLatin1String(CLIENT_OPTION)))) {
app.setBlock(isBlock);
if (app.sendMessage(PluginManager::serializedArguments(), 5000 /*timeout*/, pid))
return 0;
// Message could not be send, maybe it was in the process of quitting
if (app.isRunning(pid)) {
// Nah app is still running, ask the user
int button = askMsgSendFailed();
while (button == QMessageBox::Retry) {
if (app.sendMessage(PluginManager::serializedArguments(), 5000 /*timeout*/, pid))
return 0;
if (!app.isRunning(pid)) // App quit while we were trying so start a new creator
button = QMessageBox::Yes;
else
button = askMsgSendFailed();
}
if (button == QMessageBox::No)
return -1;
}
}
PluginManager::loadPlugins();
if (coreplugin->hasError()) {
displayError(msgCoreLoadFailure(coreplugin->errorString()));
return 1;
}
// Set up remote arguments.
QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived,
&pluginManager, &PluginManager::remoteArguments);
QObject::connect(&app, SIGNAL(fileOpenRequest(QString)), coreplugin->plugin(),
SLOT(fileOpenRequest(QString)));
// shutdown plugin manager on the exit
QObject::connect(&app, &QCoreApplication::aboutToQuit, &pluginManager, &PluginManager::shutdown);
return app.exec();
}