adding startup progress and style fix

This commit is contained in:
2026-08-22 18:19:13 +03:00
parent 56e56a370f
commit 2c4b594c90
13 changed files with 2699 additions and 67 deletions
+203
View File
@@ -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 {};
}