adding startup progress and style fix
This commit is contained in:
+203
@@ -0,0 +1,203 @@
|
||||
#include "javalocator.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFileInfo>
|
||||
#include <QHash>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QObject>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
constexpr auto kExecutable = "java.exe";
|
||||
constexpr auto kWindowless = "javaw.exe";
|
||||
#else
|
||||
constexpr auto kExecutable = "java";
|
||||
constexpr auto kWindowless = "java";
|
||||
#endif
|
||||
|
||||
// Корни, в которых Windows/Linux/macOS обычно держат JDK и JRE.
|
||||
QStringList installationRoots()
|
||||
{
|
||||
QStringList roots;
|
||||
#if defined(Q_OS_WIN)
|
||||
const QStringList programFiles = {qEnvironmentVariable("ProgramFiles"),
|
||||
qEnvironmentVariable("ProgramFiles(x86)"),
|
||||
qEnvironmentVariable("ProgramW6432")};
|
||||
const QStringList vendors = {QStringLiteral("Java"),
|
||||
QStringLiteral("Eclipse Adoptium"),
|
||||
QStringLiteral("Eclipse Foundation"),
|
||||
QStringLiteral("AdoptOpenJDK"),
|
||||
QStringLiteral("Amazon Corretto"),
|
||||
QStringLiteral("Zulu"),
|
||||
QStringLiteral("BellSoft"),
|
||||
QStringLiteral("Microsoft"),
|
||||
QStringLiteral("Semeru"),
|
||||
QStringLiteral("Android/Android Studio/jbr")};
|
||||
for (const QString &base : programFiles) {
|
||||
if (base.isEmpty())
|
||||
continue;
|
||||
for (const QString &vendor : vendors)
|
||||
roots << base + u'/' + vendor;
|
||||
}
|
||||
#elif defined(Q_OS_MACOS)
|
||||
roots << QStringLiteral("/Library/Java/JavaVirtualMachines")
|
||||
<< QDir::homePath() + QStringLiteral("/Library/Java/JavaVirtualMachines");
|
||||
#else
|
||||
roots << QStringLiteral("/usr/lib/jvm") << QStringLiteral("/usr/java")
|
||||
<< QStringLiteral("/opt/java") << QStringLiteral("/opt/jdk");
|
||||
#endif
|
||||
return roots;
|
||||
}
|
||||
|
||||
// Ищем именно java.exe: у javaw.exe нет вывода, а версию мы определяем запуском.
|
||||
void appendIfExecutable(QStringList &target, const QString &binDir)
|
||||
{
|
||||
const QFileInfo info(binDir + u'/' + QString::fromLatin1(kExecutable));
|
||||
if (!info.isFile() || !info.isExecutable())
|
||||
return;
|
||||
const QString path = info.absoluteFilePath();
|
||||
if (!target.contains(path))
|
||||
target << path;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QStringList JavaLocator::findAll(const QString &gameDir)
|
||||
{
|
||||
QStringList result;
|
||||
|
||||
// 1. Рантайм, скачанный самим Minecraft: .minecraft/runtime/<component>/<os>/<component>/bin
|
||||
const QString runtime = gameDir + QStringLiteral("/runtime");
|
||||
if (QDir(runtime).exists()) {
|
||||
QDirIterator it(runtime, QStringList{QString::fromLatin1(kExecutable)},
|
||||
QDir::Files | QDir::Executable, QDirIterator::Subdirectories);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
appendIfExecutable(result, it.fileInfo().absolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. JAVA_HOME
|
||||
const QString javaHome = qEnvironmentVariable("JAVA_HOME");
|
||||
if (!javaHome.isEmpty())
|
||||
appendIfExecutable(result, javaHome + QStringLiteral("/bin"));
|
||||
|
||||
// 3. PATH
|
||||
const QString onPath = QStandardPaths::findExecutable(QString::fromLatin1(kExecutable));
|
||||
if (!onPath.isEmpty())
|
||||
appendIfExecutable(result, QFileInfo(onPath).absolutePath());
|
||||
|
||||
// 4. Типовые каталоги установки
|
||||
const QStringList roots = installationRoots();
|
||||
for (const QString &root : roots) {
|
||||
const QDir dir(root);
|
||||
if (!dir.exists())
|
||||
continue;
|
||||
const QStringList entries = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
|
||||
for (const QString &entry : entries) {
|
||||
const QString home = dir.absoluteFilePath(entry);
|
||||
appendIfExecutable(result, home + QStringLiteral("/bin"));
|
||||
appendIfExecutable(result, home + QStringLiteral("/Contents/Home/bin")); // macOS
|
||||
appendIfExecutable(result, home + QStringLiteral("/jre/bin"));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int JavaLocator::majorVersion(const QString &javaPath)
|
||||
{
|
||||
static QHash<QString, int> cache;
|
||||
const auto cached = cache.constFind(javaPath);
|
||||
if (cached != cache.constEnd())
|
||||
return cached.value();
|
||||
|
||||
int major = 0;
|
||||
QProcess process;
|
||||
process.start(javaPath, {QStringLiteral("-version")});
|
||||
if (process.waitForFinished(5000)) {
|
||||
// java печатает версию в stderr: openjdk version "17.0.2" 2022-01-18
|
||||
const QString output = QString::fromLocal8Bit(process.readAllStandardError())
|
||||
+ QString::fromLocal8Bit(process.readAllStandardOutput());
|
||||
static const QRegularExpression re(QStringLiteral("version\\s+\"(\\d+)(?:\\.(\\d+))?"));
|
||||
const QRegularExpressionMatch match = re.match(output);
|
||||
if (match.hasMatch()) {
|
||||
const int first = match.captured(1).toInt();
|
||||
// До Java 9 версия выглядела как 1.8.0_301 — мажорная тут вторая цифра.
|
||||
major = (first == 1) ? match.captured(2).toInt() : first;
|
||||
}
|
||||
}
|
||||
|
||||
cache.insert(javaPath, major);
|
||||
return major;
|
||||
}
|
||||
|
||||
QString JavaLocator::windowlessVariant(const QString &javaPath)
|
||||
{
|
||||
const QFileInfo info(javaPath);
|
||||
if (info.fileName() != QLatin1String(kExecutable))
|
||||
return javaPath;
|
||||
const QFileInfo windowless(info.absolutePath() + u'/' + QString::fromLatin1(kWindowless));
|
||||
return windowless.isFile() ? windowless.absoluteFilePath() : javaPath;
|
||||
}
|
||||
|
||||
QString JavaLocator::select(const QString &gameDir,
|
||||
int requiredMajor,
|
||||
const QString &preferred,
|
||||
QString *error)
|
||||
{
|
||||
QStringList candidates;
|
||||
if (!preferred.isEmpty()) {
|
||||
QFileInfo info(preferred);
|
||||
// В настройках можно указать как сам java.exe, так и корень JDK.
|
||||
if (info.isDir())
|
||||
appendIfExecutable(candidates, info.absoluteFilePath() + QStringLiteral("/bin"));
|
||||
else if (info.isFile())
|
||||
candidates << info.absoluteFilePath();
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
if (error)
|
||||
*error = QObject::tr("Java по указанному пути не найдена: %1")
|
||||
.arg(QDir::toNativeSeparators(preferred));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
candidates += findAll(gameDir);
|
||||
|
||||
if (candidates.isEmpty()) {
|
||||
if (error)
|
||||
*error = QObject::tr("Java не найдена. Установите JDK %1 или укажите путь в настройках.")
|
||||
.arg(requiredMajor);
|
||||
return {};
|
||||
}
|
||||
|
||||
QString bestBelow;
|
||||
int bestBelowMajor = 0;
|
||||
for (const QString &candidate : std::as_const(candidates)) {
|
||||
const int major = majorVersion(candidate);
|
||||
if (major >= requiredMajor)
|
||||
return candidate;
|
||||
if (major > bestBelowMajor) {
|
||||
bestBelowMajor = major;
|
||||
bestBelow = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
*error = bestBelowMajor > 0
|
||||
? QObject::tr("Для этой версии нужна Java %1 или новее, найдена только Java %2 (%3).")
|
||||
.arg(requiredMajor)
|
||||
.arg(bestBelowMajor)
|
||||
.arg(QDir::toNativeSeparators(bestBelow))
|
||||
: QObject::tr("Не удалось определить версию установленной Java. "
|
||||
"Укажите путь к JDK %1 в настройках.")
|
||||
.arg(requiredMajor);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
Reference in New Issue
Block a user