Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0a91579f2 | ||
|
|
b68f404c3c | ||
|
|
c1f50f9ba3 | ||
|
|
d8bb2131f2 | ||
|
|
aca761385b | ||
|
|
15faccf376 | ||
|
|
241c9c419c | ||
|
|
4b36f8ce03 | ||
|
|
5c0baad782 | ||
|
|
850a56405c | ||
|
|
e8487c467b | ||
|
|
9866d88a16 | ||
|
|
af2d46ae89 | ||
|
|
6f7c561ecb | ||
|
|
93abe31acf | ||
|
|
a6f8659349 | ||
|
|
8728c89093 |
No files matched your search
@@ -1,3 +0,0 @@
|
||||
code_review:
|
||||
pull_request_opened:
|
||||
code_review: false
|
||||
@@ -0,0 +1,147 @@
|
||||
name: PR Size Label
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
workflow_dispatch:
|
||||
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
Label:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Label PR by filtered changed lines
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const owner = context.repo.owner
|
||||
const repo = context.repo.repo
|
||||
const ignoredFilePatterns = [
|
||||
/(?:^|\/)\.cnb\/.+\.ya?ml$/i,
|
||||
/(?:^|\/)\.gemini\/.+\.ya?ml$/i,
|
||||
/(?:^|\/)\.gitee\/.+\.ya?ml$/i,
|
||||
/(?:^|\/)\.github\/.+\.ya?ml$/i,
|
||||
/(?:^|\/)\.idea\/.+\.xml$/i,
|
||||
/(?:^|\/)\.editorconfig$/i,
|
||||
/(?:^|\/)\.gitattributes$/i,
|
||||
/(?:^|\/)\.gitignore$/i,
|
||||
/(?:^|\/).+\.kts$/i,
|
||||
/(?:^|\/).+\.md$/i,
|
||||
/(?:^|\/)LICENSE(?:_.*)?$/i,
|
||||
/(?:^|\/)gradlew(?:\.bat|\.ps1)?$/i,
|
||||
/(?:^|\/)gradle\/wrapper\/gradle-wrapper\.properties$/i,
|
||||
/(?:^|\/)lang\/.+\.properties$/i,
|
||||
/(?:^|\/)l10n\/.+\.properties$/i,
|
||||
/(?:^|\/)checkstyle\.xml$/i,
|
||||
/(?:^|\/).+\.(?:png|jpe?g|gif|ico|svg|json|ttf|woff2?|webp|avif|jxl|heic|mp4|webm|mp3|ogg|wav|txt|toml)$/i
|
||||
]
|
||||
|
||||
async function processPR(issue_number) {
|
||||
const files = await github.paginate(github.rest.pulls.listFiles, {
|
||||
owner,
|
||||
repo,
|
||||
pull_number: issue_number,
|
||||
per_page: 100,
|
||||
})
|
||||
|
||||
const countedFiles = []
|
||||
const ignoredFiles = []
|
||||
let totalChanges = 0
|
||||
|
||||
for (const file of files) {
|
||||
const ignored = ignoredFilePatterns.some(pattern => pattern.test(file.filename))
|
||||
if (ignored) {
|
||||
ignoredFiles.push(file.filename)
|
||||
continue
|
||||
}
|
||||
|
||||
const fileChanges = file.changes ?? ((file.additions || 0) + (file.deletions || 0))
|
||||
totalChanges += fileChanges
|
||||
countedFiles.push(`${file.filename} (${fileChanges})`)
|
||||
}
|
||||
|
||||
let targetLabel
|
||||
if (totalChanges <= 9) targetLabel = '1+'
|
||||
else if (totalChanges <= 39) targetLabel = '10+'
|
||||
else if (totalChanges <= 99) targetLabel = '40+'
|
||||
else if (totalChanges <= 499) targetLabel = '100+'
|
||||
else if (totalChanges <= 999) targetLabel = '500+'
|
||||
else if (totalChanges <= 1999) targetLabel = '1000+'
|
||||
else if (totalChanges <= 4999) targetLabel = '2000+'
|
||||
else targetLabel = '5000+'
|
||||
|
||||
const currentLabels = await github.paginate(github.rest.issues.listLabelsOnIssue, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
per_page: 100,
|
||||
})
|
||||
|
||||
const sizeLabels = currentLabels
|
||||
.map(label => label.name)
|
||||
.filter(name => /^\d+\+$/.test(name))
|
||||
|
||||
for (const name of sizeLabels) {
|
||||
if (name !== targetLabel) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
name,
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
if (!currentLabels.some(label => label.name === targetLabel)) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
labels: [targetLabel],
|
||||
})
|
||||
}
|
||||
|
||||
core.info(`Pull request #${issue_number} has ${totalChanges} counted changed lines, applied ${targetLabel}.`)
|
||||
core.info(`Ignored files: ${ignoredFiles.length ? ignoredFiles.join(', ') : 'none'}`)
|
||||
core.info(`Counted files: ${countedFiles.length ? countedFiles.join(', ') : 'none'}`)
|
||||
}
|
||||
|
||||
if (context.eventName === 'workflow_dispatch') {
|
||||
core.info('Manual trigger detected. Fetching all open pull requests...')
|
||||
|
||||
const openPRs = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
})
|
||||
|
||||
const unlabelledPRs = openPRs.filter(pr => {
|
||||
return !pr.labels.some(label => /^\d+\+$/.test(label.name))
|
||||
})
|
||||
|
||||
core.info(`Found ${openPRs.length} open pull request(s), ${unlabelledPRs.length} of them lack a size label.`)
|
||||
|
||||
for (const pr of unlabelledPRs) {
|
||||
core.info(`----------------------------------------`)
|
||||
core.info(`Processing unlabelled pull request #${pr.number}: ${pr.title}`)
|
||||
await processPR(pr.number)
|
||||
}
|
||||
} else {
|
||||
const pr = context.payload.pull_request
|
||||
if (!pr) {
|
||||
core.info('No pull request found in payload, skipping.')
|
||||
return
|
||||
}
|
||||
await processPR(pr.number)
|
||||
}
|
||||
@@ -52,7 +52,7 @@ if (buildNumber != null) {
|
||||
}
|
||||
}
|
||||
|
||||
val embedResources by configurations.registering
|
||||
val embedResources = configurations.register("embedResources")
|
||||
|
||||
dependencies {
|
||||
implementation(project(":HMCLCore"))
|
||||
@@ -166,7 +166,7 @@ val hmclProperties = buildList {
|
||||
}
|
||||
|
||||
val hmclPropertiesFile = layout.buildDirectory.file("hmcl.properties")
|
||||
val createPropertiesFile by tasks.registering {
|
||||
val createPropertiesFile = tasks.register("createPropertiesFile") {
|
||||
outputs.file(hmclPropertiesFile)
|
||||
hmclProperties.forEach { (k, v) -> inputs.property(k, v) }
|
||||
|
||||
@@ -258,7 +258,7 @@ tasks.processResources {
|
||||
|
||||
fun artifactFile(ext: String) = jarPath.resolveSibling(jarPath.nameWithoutExtension + '.' + ext)
|
||||
|
||||
val makeExecutables by tasks.registering {
|
||||
val makeExecutables = tasks.register("makeExecutables") {
|
||||
val extensions = listOf("exe", "sh")
|
||||
|
||||
dependsOn(tasks.jar)
|
||||
@@ -286,7 +286,7 @@ val makeExecutables by tasks.registering {
|
||||
}
|
||||
}
|
||||
|
||||
val makeDeb by tasks.registering(CreateDeb::class) {
|
||||
val makeDeb = tasks.register("makeDeb", CreateDeb::class) {
|
||||
dependsOn(makeExecutables)
|
||||
|
||||
val debFile = layout.file(provider { artifactFile("deb") })
|
||||
@@ -438,19 +438,19 @@ tasks.register<CheckTranslations>("checkTranslations") {
|
||||
|
||||
val generatedDir = layout.buildDirectory.dir("generated")
|
||||
|
||||
val upsideDownTranslate by tasks.registering(UpsideDownTranslate::class) {
|
||||
val upsideDownTranslate = tasks.register<UpsideDownTranslate>("upsideDownTranslate") {
|
||||
inputFile.set(layout.projectDirectory.file("src/main/resources/assets/lang/I18N.properties"))
|
||||
outputFile.set(generatedDir.map { it.file("generated/i18n/I18N_en_Qabs.properties") })
|
||||
}
|
||||
|
||||
val createLanguageList by tasks.registering(CreateLanguageList::class) {
|
||||
val createLanguageList = tasks.register<CreateLanguageList>("createLanguageList") {
|
||||
resourceBundleDir.set(layout.projectDirectory.dir("src/main/resources/assets/lang"))
|
||||
resourceBundleBaseName.set("I18N")
|
||||
additionalLanguages.set(listOf("en-Qabs"))
|
||||
outputFile.set(generatedDir.map { it.file("languages.json") })
|
||||
}
|
||||
|
||||
val createLocaleNamesResourceBundle by tasks.registering(CreateLocaleNamesResourceBundle::class) {
|
||||
val createLocaleNamesResourceBundle = tasks.register<CreateLocaleNamesResourceBundle>("createLocaleNamesResourceBundle") {
|
||||
dependsOn(createLanguageList)
|
||||
|
||||
languagesFile.set(createLanguageList.flatMap { it.outputFile })
|
||||
|
||||
@@ -52,8 +52,6 @@ public final class EntryPoint {
|
||||
|
||||
checkWine();
|
||||
|
||||
setupJavaFXVMOptions();
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
System.getProperties().putIfAbsent("apple.awt.application.appearance", "system");
|
||||
if (!isInsideMacAppBundle())
|
||||
@@ -74,71 +72,6 @@ public final class EntryPoint {
|
||||
System.exit(exitCode);
|
||||
}
|
||||
|
||||
private static void setupJavaFXVMOptions() {
|
||||
if ("true".equalsIgnoreCase(System.getenv("HMCL_FORCE_GPU"))) {
|
||||
LOG.info("HMCL_FORCE_GPU: true");
|
||||
System.getProperties().putIfAbsent("prism.forceGPU", "true");
|
||||
}
|
||||
|
||||
String animationFrameRate = System.getenv("HMCL_ANIMATION_FRAME_RATE");
|
||||
if (animationFrameRate != null) {
|
||||
LOG.info("HMCL_ANIMATION_FRAME_RATE: " + animationFrameRate);
|
||||
|
||||
try {
|
||||
if (Integer.parseInt(animationFrameRate) <= 0)
|
||||
throw new NumberFormatException(animationFrameRate);
|
||||
|
||||
System.getProperties().putIfAbsent("javafx.animation.pulse", animationFrameRate);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.warning("Invalid animation frame rate: " + animationFrameRate);
|
||||
}
|
||||
}
|
||||
|
||||
String uiScale = System.getProperty("hmcl.uiScale", System.getenv("HMCL_UI_SCALE"));
|
||||
if (uiScale != null) {
|
||||
uiScale = uiScale.trim();
|
||||
|
||||
LOG.info("HMCL_UI_SCALE: " + uiScale);
|
||||
|
||||
try {
|
||||
float scaleValue;
|
||||
if (uiScale.endsWith("%")) {
|
||||
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 1)) / 100.0f;
|
||||
} else if (uiScale.endsWith("dpi") || uiScale.endsWith("DPI")) {
|
||||
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 3)) / 96.0f;
|
||||
} else {
|
||||
scaleValue = Float.parseFloat(uiScale);
|
||||
}
|
||||
|
||||
float lowerBound;
|
||||
float upperBound;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
// JavaFX behavior may be abnormal when the DPI scaling factor is too high
|
||||
lowerBound = 0.25f;
|
||||
upperBound = 4f;
|
||||
} else {
|
||||
lowerBound = 0.01f;
|
||||
upperBound = 10f;
|
||||
}
|
||||
|
||||
if (scaleValue >= lowerBound && scaleValue <= upperBound) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
System.getProperties().putIfAbsent("glass.win.uiScale", uiScale);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
LOG.warning("macOS does not support setting UI scale, so it will be ignored");
|
||||
} else {
|
||||
System.getProperties().putIfAbsent("glass.gtk.uiScale", uiScale);
|
||||
}
|
||||
} else {
|
||||
LOG.warning("UI scale out of range: " + uiScale);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Invalid UI scale: " + uiScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void createHMCLDirectories() {
|
||||
if (!Files.isDirectory(Metadata.HMCL_LOCAL_HOME)) {
|
||||
try {
|
||||
@@ -225,15 +158,8 @@ public final class EntryPoint {
|
||||
|
||||
private static void checkWine() {
|
||||
if (OperatingSystem.isRunningUnderWine()) {
|
||||
SwingUtils.initLookAndFeel();
|
||||
LOG.warning("HMCL is running under Wine or its distributions!");
|
||||
|
||||
int result = JOptionPane.showOptionDialog(null, i18n("fatal.wine_warning"), i18n("message.warning"), JOptionPane.OK_CANCEL_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE, null, null, null);
|
||||
|
||||
if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) {
|
||||
exit(1);
|
||||
}
|
||||
showWarning(i18n("fatal.wine_warning"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,10 +199,21 @@ public final class EntryPoint {
|
||||
}
|
||||
}
|
||||
|
||||
static void showWarning(String message) {
|
||||
SwingUtils.initLookAndFeel();
|
||||
|
||||
int result = JOptionPane.showOptionDialog(null, message, i18n("message.warning"), JOptionPane.OK_CANCEL_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE, null, null, null);
|
||||
|
||||
if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
private static void showErrorAndExit(String message) {
|
||||
static void showErrorAndExit(String message) {
|
||||
SwingUtils.showErrorDialog(message);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,6 @@ import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.DataFormat;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
@@ -49,6 +47,9 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.*;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.StringSelection;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
@@ -60,7 +61,6 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -98,18 +98,7 @@ public final class Launcher extends Application {
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
SettingsManager.init();
|
||||
initializeSettingsRuntime();
|
||||
} catch (SambaException e) {
|
||||
showAlert(AlertType.WARNING, i18n("fatal.samba"));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load config", e);
|
||||
checkConfigInTempDir();
|
||||
checkConfigOwner();
|
||||
showAlert(AlertType.ERROR, i18n("fatal.config_loading_failure", SettingsManager.localConfigDirectory()));
|
||||
EntryPoint.exit(1);
|
||||
}
|
||||
initializeSettingsRuntime();
|
||||
|
||||
// https://lapcatsoftware.com/articles/app-translocation.html
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS
|
||||
@@ -274,48 +263,6 @@ public final class Launcher extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkConfigOwner() {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
return;
|
||||
|
||||
String userName = System.getProperty("user.name");
|
||||
Path configDirectory = SettingsManager.localConfigDirectory();
|
||||
if (!Files.exists(configDirectory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String owner;
|
||||
try {
|
||||
owner = Files.getOwner(configDirectory).getName();
|
||||
} catch (IOException ioe) {
|
||||
LOG.warning("Failed to get file owner", ioe);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Files.isWritable(configDirectory) || userName.equals("root") || userName.equals(owner))
|
||||
return;
|
||||
|
||||
ArrayList<String> files = new ArrayList<>();
|
||||
files.add(configDirectory.toString());
|
||||
if (Files.exists(Metadata.HMCL_USER_HOME))
|
||||
files.add(Metadata.HMCL_USER_HOME.toString());
|
||||
|
||||
Path mcDir = Paths.get(".minecraft").toAbsolutePath().normalize();
|
||||
if (Files.exists(mcDir))
|
||||
files.add(mcDir.toString());
|
||||
|
||||
String command = new CommandBuilder().addAll("sudo", "chown", "-R", userName).addAll(files).toString();
|
||||
ButtonType copyAndExit = new ButtonType(i18n("button.copy_and_exit"));
|
||||
|
||||
if (showAlert(AlertType.ERROR,
|
||||
i18n("fatal.config_loading_failure.unix", owner, command),
|
||||
copyAndExit, ButtonType.CLOSE) == copyAndExit) {
|
||||
Clipboard.getSystemClipboard()
|
||||
.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, command));
|
||||
}
|
||||
EntryPoint.exit(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() throws Exception {
|
||||
Controllers.onApplicationStop();
|
||||
@@ -376,6 +323,19 @@ public final class Launcher extends Application {
|
||||
|
||||
Lang.thread(SystemInfo::initialize, "Detection System Information", true);
|
||||
|
||||
try {
|
||||
SettingsManager.init();
|
||||
} catch (SambaException e) {
|
||||
EntryPoint.showWarning(i18n("fatal.samba"));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to load config", e);
|
||||
checkConfigOwner();
|
||||
SwingUtils.showErrorDialog(i18n("fatal.config_loading_failure", SettingsManager.localConfigDirectory()));
|
||||
EntryPoint.exit(1);
|
||||
}
|
||||
|
||||
setupJavaFXVMOptions();
|
||||
|
||||
launch(Launcher.class, args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
@@ -408,5 +368,127 @@ public final class Launcher extends Application {
|
||||
});
|
||||
}
|
||||
|
||||
private static void setupJavaFXVMOptions() {
|
||||
if ("true".equalsIgnoreCase(System.getenv("HMCL_FORCE_GPU"))) {
|
||||
LOG.info("HMCL_FORCE_GPU: true");
|
||||
System.getProperties().putIfAbsent("prism.forceGPU", "true");
|
||||
}
|
||||
|
||||
setUpAnimationFrameRate:
|
||||
{
|
||||
String animationFrameRate = System.getenv("HMCL_ANIMATION_FRAME_RATE");
|
||||
if (animationFrameRate != null) {
|
||||
LOG.info("HMCL_ANIMATION_FRAME_RATE: " + animationFrameRate);
|
||||
|
||||
try {
|
||||
if (Integer.parseInt(animationFrameRate) <= 0)
|
||||
throw new NumberFormatException(animationFrameRate);
|
||||
|
||||
System.getProperties().putIfAbsent("javafx.animation.pulse", animationFrameRate);
|
||||
} catch (NumberFormatException e) {
|
||||
LOG.warning("Invalid animation frame rate: " + animationFrameRate);
|
||||
}
|
||||
break setUpAnimationFrameRate;
|
||||
}
|
||||
}
|
||||
|
||||
String uiScale = System.getProperty("hmcl.uiScale", System.getenv("HMCL_UI_SCALE"));
|
||||
if (uiScale != null) {
|
||||
uiScale = uiScale.trim();
|
||||
|
||||
LOG.info("HMCL_UI_SCALE: " + uiScale);
|
||||
|
||||
try {
|
||||
float scaleValue;
|
||||
if (uiScale.endsWith("%")) {
|
||||
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 1)) / 100.0f;
|
||||
} else if (uiScale.endsWith("dpi") || uiScale.endsWith("DPI")) {
|
||||
scaleValue = Integer.parseInt(uiScale.substring(0, uiScale.length() - 3)) / 96.0f;
|
||||
} else {
|
||||
scaleValue = Float.parseFloat(uiScale);
|
||||
}
|
||||
|
||||
float lowerBound;
|
||||
float upperBound;
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
// JavaFX behavior may be abnormal when the DPI scaling factor is too high
|
||||
lowerBound = 0.25f;
|
||||
upperBound = 4f;
|
||||
} else {
|
||||
lowerBound = 0.01f;
|
||||
upperBound = 10f;
|
||||
}
|
||||
|
||||
if (scaleValue >= lowerBound && scaleValue <= upperBound) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
System.getProperties().putIfAbsent("glass.win.uiScale", uiScale);
|
||||
} else if (OperatingSystem.CURRENT_OS == OperatingSystem.MACOS) {
|
||||
LOG.warning("macOS does not support setting UI scale, so it will be ignored");
|
||||
} else {
|
||||
System.getProperties().putIfAbsent("glass.gtk.uiScale", uiScale);
|
||||
}
|
||||
} else {
|
||||
LOG.warning("UI scale out of range: " + uiScale);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Invalid UI scale: " + uiScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkConfigOwner() {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
return;
|
||||
|
||||
String userName = System.getProperty("user.name");
|
||||
Path configDirectory = SettingsManager.localConfigDirectory();
|
||||
if (!Files.exists(configDirectory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
String owner;
|
||||
try {
|
||||
owner = Files.getOwner(configDirectory).getName();
|
||||
} catch (IOException ioe) {
|
||||
LOG.warning("Failed to get file owner", ioe);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Files.isWritable(configDirectory) || userName.equals("root") || userName.equals(owner))
|
||||
return;
|
||||
|
||||
ArrayList<String> files = new ArrayList<>();
|
||||
files.add(configDirectory.toString());
|
||||
if (Files.exists(Metadata.HMCL_USER_HOME))
|
||||
files.add(Metadata.HMCL_USER_HOME.toString());
|
||||
|
||||
Path mcDir = Paths.get(".minecraft").toAbsolutePath().normalize();
|
||||
if (Files.exists(mcDir))
|
||||
files.add(mcDir.toString());
|
||||
|
||||
String command = new CommandBuilder().addAll("sudo", "chown", "-R", userName).addAll(files).toString();
|
||||
SwingUtils.initLookAndFeel();
|
||||
|
||||
Object[] options = {i18n("button.copy_and_exit"), i18n("button.cancel")};
|
||||
int result = JOptionPane.showOptionDialog(null,
|
||||
i18n("fatal.config_loading_failure.unix", owner, command),
|
||||
i18n("message.error"),
|
||||
JOptionPane.DEFAULT_OPTION,
|
||||
JOptionPane.ERROR_MESSAGE,
|
||||
null,
|
||||
options,
|
||||
options[0]);
|
||||
|
||||
if (result == 0) {
|
||||
try {
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(command), null);
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to copy command to clipboard", e);
|
||||
}
|
||||
}
|
||||
EntryPoint.exit(1);
|
||||
}
|
||||
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter(true);
|
||||
}
|
||||
@@ -34,8 +34,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
|
||||
/// Persistent configuration for a game directory.
|
||||
@JsonAdapter(GameDirectory.Serializer.class)
|
||||
@NotNullByDefault
|
||||
@@ -118,7 +116,7 @@ public final class GameDirectory implements Observable {
|
||||
this.path = new SimpleObjectProperty<>(this, "path", Objects.requireNonNull(path));
|
||||
this.legacyGameSettings = new SimpleObjectProperty<>(this, "legacyGameSettings", legacyGameSettings);
|
||||
|
||||
addPropertyChangedListener(onInvalidating(this::invalidate));
|
||||
addPropertyChangedListener(arg -> invalidate());
|
||||
}
|
||||
|
||||
/// Returns a debug string containing the game directory path and display metadata.
|
||||
|
||||
@@ -480,17 +480,19 @@ public final class LauncherSettings extends ObservableSetting implements JsonSch
|
||||
|
||||
/// Whether UI animations are disabled.
|
||||
@SerializedName("animationDisabled")
|
||||
private final BooleanProperty animationDisabled = new SimpleBooleanProperty(
|
||||
FXUtils.REDUCED_MOTION == Boolean.TRUE
|
||||
|| !JavaRuntime.CURRENT_JIT_ENABLED
|
||||
|| !FXUtils.GPU_ACCELERATION_ENABLED
|
||||
);
|
||||
private final ObjectProperty<@Nullable Boolean> animationDisabled = new SimpleObjectProperty<>();
|
||||
|
||||
/// Returns the UI animation disable property.
|
||||
public BooleanProperty animationDisabledProperty() {
|
||||
public ObjectProperty<@Nullable Boolean> animationDisabledProperty() {
|
||||
return animationDisabled;
|
||||
}
|
||||
|
||||
public boolean isAnimationDisabled() {
|
||||
// Avoid accessing FXUtils too early during startup
|
||||
return Objects.requireNonNullElseGet(animationDisabled.get(), () ->
|
||||
FXUtils.REDUCED_MOTION == Boolean.TRUE || !JavaRuntime.CURRENT_JIT_ENABLED || !FXUtils.GPU_ACCELERATION_ENABLED);
|
||||
}
|
||||
|
||||
// Networks
|
||||
|
||||
/// Whether HMCL automatically selects the number of download threads.
|
||||
|
||||
@@ -22,6 +22,8 @@ import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
|
||||
import org.glavo.url.WebURL;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -34,8 +36,6 @@ import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Node;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import static org.jackhuang.hmcl.Metadata.CHANGELOG_URL;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onEscPressed;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
@@ -59,7 +59,7 @@ public final class UpgradeDialog extends JFXDialogLayout {
|
||||
// Downgrade update, no need to display changelog
|
||||
return null;
|
||||
|
||||
Document document = Jsoup.parse(new URL(url), 30 * 1000);
|
||||
Document document = Jsoup.parse(WebURL.toURL(url), 30 * 1000);
|
||||
Node node = document.selectFirst("h1[data-version=\"%s\"]".formatted(targetVersion));
|
||||
|
||||
if (node == null || !"h1".equals(node.nodeName())) {
|
||||
|
||||
@@ -37,7 +37,7 @@ public final class AnimationUtils {
|
||||
public static void init() {
|
||||
}
|
||||
|
||||
private static final boolean ENABLED = !settings().animationDisabledProperty().get();
|
||||
private static final boolean ENABLED = !settings().isAnimationDisabled();
|
||||
private static final boolean PLAY_WINDOW_ANIMATION = ENABLED && !OperatingSystem.CURRENT_OS.isLinuxOrBSD();
|
||||
|
||||
public static boolean isAnimationEnabled() {
|
||||
|
||||
@@ -85,8 +85,8 @@ public final class RadioChoiceList<T extends @UnknownNullability Object> extends
|
||||
|
||||
group.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Choice<T> choice = newValue != null ? choiceByToggle.get(newValue) : null;
|
||||
selectedChoice.set(choice);
|
||||
selectedValue.set(choice != null ? choice.getValue() : null);
|
||||
selectedChoice.set(choice);
|
||||
});
|
||||
|
||||
selectedValue.addListener((observable, oldValue, newValue) -> {
|
||||
|
||||
@@ -21,11 +21,12 @@ import com.jfoenix.controls.JFXButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.task.*;
|
||||
import javafx.scene.layout.*;
|
||||
import org.jackhuang.hmcl.task.FetchTask;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.TaskCancellationAction;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
@@ -67,16 +68,18 @@ public class TaskExecutorDialogPane extends BorderPane {
|
||||
center.getChildren().setAll(lblTitle, taskListPane);
|
||||
}
|
||||
|
||||
BorderPane bottom = new BorderPane();
|
||||
this.setBottom(bottom);
|
||||
HBox bottom = new HBox();
|
||||
bottom.setAlignment(Pos.CENTER_LEFT);
|
||||
bottom.setPadding(new Insets(0, 8, 8, 8));
|
||||
bottom.setSpacing(8);
|
||||
this.setBottom(bottom);
|
||||
{
|
||||
lblProgress = new Label();
|
||||
bottom.setLeft(lblProgress);
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
btnCancel = new JFXButton(i18n("button.cancel"));
|
||||
btnCancel.getStyleClass().add("dialog-cancel");
|
||||
bottom.setRight(btnCancel);
|
||||
bottom.getChildren().setAll(lblProgress, spacer, btnCancel);
|
||||
}
|
||||
|
||||
setCancel(cancel);
|
||||
|
||||
@@ -1171,7 +1171,8 @@ public class PersonalizationPage extends StackPane {
|
||||
|
||||
LineToggleButton animationButton = new LineToggleButton();
|
||||
appearanceList.getContent().add(animationButton);
|
||||
animationButton.selectedProperty().bindBidirectional(settings().animationDisabledProperty());
|
||||
animationButton.setSelected(settings().isAnimationDisabled());
|
||||
FXUtils.onChange(animationButton.selectedProperty(), value -> settings().animationDisabledProperty().set(value));
|
||||
animationButton.setTitle(i18n("settings.launcher.turn_off_animations"));
|
||||
animationButton.setSubtitle(i18n("settings.take_effect_after_restart"));
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.nio.file.Path;
|
||||
|
||||
/// @author Glavo
|
||||
public enum NBTFileType {
|
||||
COMPRESSED("dat", "dat_old") {
|
||||
COMPRESSED("nbt", "dat", "dat_old") {
|
||||
@Override
|
||||
public Tag read(Path file) throws IOException {
|
||||
return NBTCodec.of().readTag(file);
|
||||
|
||||
@@ -379,11 +379,14 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
ModInfoDialog(ModInfoObject modInfo) {
|
||||
HBox titleContainer = new HBox();
|
||||
titleContainer.setSpacing(8);
|
||||
titleContainer.setPadding(new Insets(0, 0, 12, 0));
|
||||
|
||||
Stage stage = Controllers.getStage();
|
||||
prefWidthProperty().bind(stage.widthProperty().multiply(0.7));
|
||||
maxWidthProperty().bind(stage.widthProperty().multiply(0.7));
|
||||
|
||||
var imageContainer = new ImageContainer(40);
|
||||
titleContainer.setAlignment(Pos.CENTER_LEFT);
|
||||
modInfo.loadIcon(imageContainer, null);
|
||||
|
||||
TwoLineListItem title = new TwoLineListItem();
|
||||
@@ -392,13 +395,13 @@ final class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
else
|
||||
title.setTitle(modInfo.getModInfo().getName());
|
||||
|
||||
StringJoiner subtitle = new StringJoiner(" | ");
|
||||
subtitle.add(FileUtils.getName(modInfo.getModInfo().getFile()));
|
||||
StringJoiner subtitle = new StringJoiner("\n");
|
||||
subtitle.add(i18n("archive.file.name") + ": " + FileUtils.getName(modInfo.getModInfo().getFile()));
|
||||
if (StringUtils.isNotBlank(modInfo.getModInfo().getGameVersion())) {
|
||||
subtitle.add(modInfo.getModInfo().getGameVersion());
|
||||
subtitle.add(i18n("mods.game.version") + ": " + modInfo.getModInfo().getGameVersion());
|
||||
}
|
||||
if (StringUtils.isNotBlank(modInfo.getModInfo().getVersion())) {
|
||||
subtitle.add(modInfo.getModInfo().getVersion());
|
||||
subtitle.add(i18n("archive.version") + ": " + modInfo.getModInfo().getVersion());
|
||||
}
|
||||
if (StringUtils.isNotBlank(modInfo.getModInfo().getAuthors())) {
|
||||
subtitle.add(i18n("archive.author") + ": " + modInfo.getModInfo().getAuthors());
|
||||
|
||||
@@ -902,6 +902,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1:natives-linux-arm64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-arm64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-arm64.jar",
|
||||
"sha1": "b5a62eb82113b0227077fb270128487a80a20299",
|
||||
"size": 1434288
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl.lwjgl:lwjgl-platform:2.9.0:natives": {
|
||||
"name": "org.glavo.hmcl:lwjgl2-natives:2.9.3",
|
||||
"downloads": {
|
||||
@@ -2637,6 +2648,7 @@
|
||||
"org.lwjgl:lwjgl-shaderc:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-spvc:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-vma:3.4.1:natives-linux": null,
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": null,
|
||||
"net.java.dev.jna:jna:5.8.0": {
|
||||
"name": "net.java.dev.jna:jna:5.13.0",
|
||||
"downloads": {
|
||||
@@ -4384,6 +4396,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"sha1": "ec28db6e949478044c649fea25eae43b1b161971",
|
||||
"size": 983498
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1:natives-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-riscv64.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-linux-riscv64.jar",
|
||||
"sha1": "d443cd6e73467f437d0be30a3ece7b6fe162a10a",
|
||||
"size": 1207531
|
||||
}
|
||||
}
|
||||
},
|
||||
"com.github.oshi:oshi-core:6.6.5": {
|
||||
"name": "com.github.oshi:oshi-core:6.8.0",
|
||||
"downloads": {
|
||||
@@ -6427,6 +6461,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1.jar",
|
||||
"sha1": "ec28db6e949478044c649fea25eae43b1b161971",
|
||||
"size": 983498
|
||||
}
|
||||
}
|
||||
},
|
||||
"org.lwjgl:lwjgl-sdl:3.4.1:natives-linux": {
|
||||
"name": "org.lwjgl:lwjgl-sdl:3.4.1:natives-freebsd",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-freebsd.jar",
|
||||
"url": "https://repo1.maven.org/maven2/org/lwjgl/lwjgl-sdl/3.4.1/lwjgl-sdl-3.4.1-natives-freebsd.jar",
|
||||
"sha1": "1616d4bd1f5ae03faa9c815c728f74d186526eee",
|
||||
"size": 1241210
|
||||
}
|
||||
}
|
||||
},
|
||||
"net.java.jinput:jinput-platform:2.0.5:natives": null,
|
||||
"com.mojang:text2speech:1.10.3:natives": null,
|
||||
"com.mojang:text2speech:1.11.3:natives": null,
|
||||
|
||||
@@ -1,82 +1,119 @@
|
||||
{
|
||||
"$schema" : "https://schemas.glavo.site/hmcl/theme-pack/1.0.0",
|
||||
"id" : "hmcl.classic",
|
||||
"version" : "1.0.0",
|
||||
"name" : {
|
||||
"default" : "Classic",
|
||||
"zh-Hans" : "经典主题",
|
||||
"zh-Hant" : "經典主題"
|
||||
"$schema": "https://schemas.glavo.site/hmcl/theme-pack/1.0.0",
|
||||
"id": "hmcl.classic",
|
||||
"version": "1.0.0",
|
||||
"name": {
|
||||
"default": "Classic",
|
||||
"es": "Clásico",
|
||||
"ja": "クラシック",
|
||||
"ru": "Классическая",
|
||||
"uk": "Класична",
|
||||
"zh-Hans": "经典主题",
|
||||
"zh-Hant": "經典主題",
|
||||
"lzh": "舊觀"
|
||||
},
|
||||
"description" : {
|
||||
"default" : "Classic HMCL launcher themes",
|
||||
"zh-Hans" : "HMCL 启动器经典主题",
|
||||
"zh-Hant" : "HMCL 啟動器經典主題"
|
||||
"description": {
|
||||
"default": "Classic HMCL launcher themes",
|
||||
"es": "Temas clásicos del launcher HMCL",
|
||||
"ja": "HMCL ランチャーのクラシックテーマ",
|
||||
"ru": "Классические темы лаунчера HMCL",
|
||||
"uk": "Класичні теми лаунчера HMCL",
|
||||
"zh-Hans": "HMCL 启动器经典主题",
|
||||
"zh-Hant": "HMCL 啟動器經典主題",
|
||||
"lzh": "HMCL 啟動器之舊觀"
|
||||
},
|
||||
"authors" : [
|
||||
"authors": [
|
||||
{
|
||||
"name" : "HMCL"
|
||||
"name": {
|
||||
"default": "HMCL",
|
||||
"en-Qabs": "ꞀƆWH"
|
||||
}
|
||||
}
|
||||
],
|
||||
"themes" : [
|
||||
"themes": [
|
||||
{
|
||||
"id" : "2021-08-26",
|
||||
"name" : "HMCL 3.15",
|
||||
"description" : {
|
||||
"default" : "HMCL 3.15 classic appearance",
|
||||
"zh-Hans" : "HMCL 3.15 经典外观",
|
||||
"zh-Hant" : "HMCL 3.15 經典外觀"
|
||||
"id": "2021-08-26",
|
||||
"name": {
|
||||
"default": "Current Theme",
|
||||
"es": "Tema actual",
|
||||
"ja": "現在のテーマ",
|
||||
"ru": "Текущая тема",
|
||||
"uk": "Поточна тема",
|
||||
"zh-Hans": "当前主题",
|
||||
"zh-Hant": "當前主題",
|
||||
"lzh": "今觀"
|
||||
},
|
||||
"color" : "#5C6BC0",
|
||||
"brightness" : "light",
|
||||
"colorStyle" : "fidelity",
|
||||
"background" : {
|
||||
"type" : "builtin",
|
||||
"id" : "2021-08-26",
|
||||
"opacity" : 1.0
|
||||
"description": {
|
||||
"default": "HMCL current theme",
|
||||
"es": "Tema actual de HMCL",
|
||||
"ja": "HMCL の現在のテーマ",
|
||||
"ru": "Текущая тема HMCL",
|
||||
"uk": "Поточна тема HMCL",
|
||||
"zh-Hans": "HMCL 当前主题",
|
||||
"zh-Hant": "HMCL 當前主題",
|
||||
"lzh": "HMCL 之今觀"
|
||||
},
|
||||
"titleBar" : {
|
||||
"transparent" : false
|
||||
"color": "#5C6BC0",
|
||||
"brightness": "light",
|
||||
"colorStyle": "fidelity",
|
||||
"background": {
|
||||
"type": "builtin",
|
||||
"id": "2021-08-26",
|
||||
"opacity": 1.0
|
||||
},
|
||||
"titleBar": {
|
||||
"transparent": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id" : "2016-02-25",
|
||||
"name" : "HMCL 3.3",
|
||||
"description" : {
|
||||
"default" : "HMCL 3.3 classic appearance",
|
||||
"zh-Hans" : "HMCL 3.3 经典外观",
|
||||
"zh-Hant" : "HMCL 3.3 經典外觀"
|
||||
"id": "2016-02-25",
|
||||
"name": "HMCL 3.3",
|
||||
"description": {
|
||||
"default": "HMCL 3.3 classic appearance",
|
||||
"es": "Apariencia clásica de HMCL 3.3",
|
||||
"ja": "HMCL 3.3 のクラシックな外観",
|
||||
"ru": "Классический вид HMCL 3.3",
|
||||
"uk": "Класичний вигляд HMCL 3.3",
|
||||
"zh-Hans": "HMCL 3.3 经典外观",
|
||||
"zh-Hant": "HMCL 3.3 經典外觀",
|
||||
"lzh": "HMCL 3.3 之舊觀"
|
||||
},
|
||||
"color" : "#5C6BC0",
|
||||
"brightness" : "light",
|
||||
"colorStyle" : "fidelity",
|
||||
"background" : {
|
||||
"type" : "builtin",
|
||||
"id" : "2016-02-25",
|
||||
"opacity" : 1.0
|
||||
"color": "#5C6BC0",
|
||||
"brightness": "light",
|
||||
"colorStyle": "fidelity",
|
||||
"background": {
|
||||
"type": "builtin",
|
||||
"id": "2016-02-25",
|
||||
"opacity": 1.0
|
||||
},
|
||||
"titleBar" : {
|
||||
"transparent" : true
|
||||
"titleBar": {
|
||||
"transparent": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id" : "2015-06-22",
|
||||
"name" : {
|
||||
"default" : "HMCL 2.3"
|
||||
"id": "2015-06-22",
|
||||
"name": {
|
||||
"default": "HMCL 2.3"
|
||||
},
|
||||
"description" : {
|
||||
"default" : "HMCL 2.3 classic appearance",
|
||||
"zh-Hans" : "HMCL 2.3 经典外观",
|
||||
"zh-Hant" : "HMCL 2.3 經典外觀"
|
||||
"description": {
|
||||
"default": "HMCL 2.3 classic appearance",
|
||||
"es": "Apariencia clásica de HMCL 2.3",
|
||||
"ja": "HMCL 2.3 のクラシックな外観",
|
||||
"ru": "Классический вид HMCL 2.3",
|
||||
"uk": "Класичний вигляд HMCL 2.3",
|
||||
"zh-Hans": "HMCL 2.3 经典外观",
|
||||
"zh-Hant": "HMCL 2.3 經典外觀",
|
||||
"lzh": "HMCL 2.3 之舊觀"
|
||||
},
|
||||
"color" : "#E67E22",
|
||||
"brightness" : "light",
|
||||
"colorStyle" : "fidelity",
|
||||
"background" : {
|
||||
"type" : "builtin",
|
||||
"id" : "2015-06-22"
|
||||
"color": "#E67E22",
|
||||
"brightness": "light",
|
||||
"colorStyle": "fidelity",
|
||||
"background": {
|
||||
"type": "builtin",
|
||||
"id": "2015-06-22"
|
||||
},
|
||||
"titleBar" : {
|
||||
"transparent" : false
|
||||
"titleBar": {
|
||||
"transparent": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,17 +4,31 @@
|
||||
"version": "1.0.0",
|
||||
"name": {
|
||||
"default": "Default",
|
||||
"en-Qabs": "ʇꞁnɐɟǝᗡ",
|
||||
"es": "Predeterminado",
|
||||
"ja": "デフォルト",
|
||||
"ru": "По умолчанию",
|
||||
"uk": "За замовчуванням",
|
||||
"zh-Hans": "默认主题",
|
||||
"zh-Hant": "預設主題"
|
||||
"zh-Hant": "預設主題",
|
||||
"lzh": "預設主題"
|
||||
},
|
||||
"description": {
|
||||
"default": "HMCL Default",
|
||||
"es": "HMCL Predeterminado",
|
||||
"ja": "HMCL デフォルト",
|
||||
"ru": "HMCL По умолчанию",
|
||||
"uk": "HMCL За замовчуванням",
|
||||
"zh-Hans": "HMCL 默认主题",
|
||||
"zh-Hant": "HMCL 預設主題"
|
||||
"zh-Hant": "HMCL 預設主題",
|
||||
"lzh": "HMCL 預設主題"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "HMCL"
|
||||
"name": {
|
||||
"default": "HMCL",
|
||||
"en-Qabs": "ꞀƆWH"
|
||||
}
|
||||
}
|
||||
],
|
||||
"theme": {}
|
||||
|
||||
@@ -15,4 +15,4 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
versionRoot=3.16
|
||||
versionRoot=3.17
|
||||
Reference in new issue
Block a user