feat(box): 增强崩溃报告功能并添加单实例锁- 重新设计崩溃报告界面,增加更多详细信息
- 添加单实例锁机制,防止多个实例同时运行 - 更新UI样式,优化用户体验 - 修复一些小问题
This commit is contained in:
@@ -72,6 +72,7 @@ dependencies {
|
|||||||
implementation 'org.graalvm.python:python-embedding:24.2.1'
|
implementation 'org.graalvm.python:python-embedding:24.2.1'
|
||||||
|
|
||||||
implementation files('libs/JNC-1.0-jnc.jar')
|
implementation files('libs/JNC-1.0-jnc.jar')
|
||||||
|
implementation files('libs/dog api 1.3.jar')
|
||||||
|
|
||||||
implementation 'org.fxmisc.richtext:richtextfx:0.11.0' // 更新后的richtextfx
|
implementation 'org.fxmisc.richtext:richtextfx:0.11.0' // 更新后的richtextfx
|
||||||
implementation 'org.bitbucket.mstrobel:procyon-core:0.5.36' // 使用JitPack版本
|
implementation 'org.bitbucket.mstrobel:procyon-core:0.5.36' // 使用JitPack版本
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#Current Loaded Language
|
#Current Loaded Language
|
||||||
#Thu May 01 15:04:30 CST 2025
|
#Sat May 31 10:30:35 CST 2025
|
||||||
loadedLanguage=system\:zh_CN
|
loadedLanguage=system\:zh_CN
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ andShow.pluginInfo.writer=\u62A5\u544A\u5DF2\u4FDD\u5B58\u81F3:
|
|||||||
andShow.pluginInfo.writer.title=\u5BFC\u51FA\u6210\u529F
|
andShow.pluginInfo.writer.title=\u5BFC\u51FA\u6210\u529F
|
||||||
andShow.pluginInfo.writer.error=\u5BFC\u51FA\u5931\u8D25:
|
andShow.pluginInfo.writer.error=\u5BFC\u51FA\u5931\u8D25:
|
||||||
andShow.pluginInfo.error.writer.title=\u9519\u8BEF
|
andShow.pluginInfo.error.writer.title=\u9519\u8BEF
|
||||||
|
save.crash.report=\u4FDD\u5B58\u5D29\u6E83\u62A5\u544A
|
||||||
|
|
||||||
progressBarManager.title=\u52A0\u8F7D\u4E2D...
|
progressBarManager.title=\u52A0\u8F7D\u4E2D...
|
||||||
|
|
||||||
@@ -117,3 +118,7 @@ settings.4.load_theme_success.2=' \u5DF2\u6210\u529F\u52A0\u8F7D\uFF01
|
|||||||
settings.4.load_theme_success.3=\u6210\u529F
|
settings.4.load_theme_success.3=\u6210\u529F
|
||||||
settings.4.load_theme_error=\u52A0\u8F7D\u4E3B\u9898\u5931\u8D25:
|
settings.4.load_theme_error=\u52A0\u8F7D\u4E3B\u9898\u5931\u8D25:
|
||||||
settings.4.load_theme_error.title=\u9519\u8BEF
|
settings.4.load_theme_error.title=\u9519\u8BEF
|
||||||
|
|
||||||
|
core.plugins=\u6838\u5FC3\u63D2\u4EF6
|
||||||
|
plugin.info=\u63D2\u4EF6\u4FE1\u606F
|
||||||
|
error.details=\u9519\u8BEF\u8BE6\u60C5
|
||||||
BIN
library/DogAgent.dll
Normal file
BIN
library/DogAgent.dll
Normal file
Binary file not shown.
BIN
libs/dog api 1.3.jar
Normal file
BIN
libs/dog api 1.3.jar
Normal file
Binary file not shown.
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
#Obfuscation Mapping Table
|
#Obfuscation Mapping Table
|
||||||
#Sun Feb 23 18:01:01 CST 2025
|
#Sun Feb 23 18:01:01 CST 2025
|
||||||
com/axis/innovators/box/AxisInnovatorsBox=a/b/c/d/A
|
com/axis/innovators/box/AxisInnovatorsBox=a/b/c/d/A
|
||||||
|
|||||||
@@ -20,16 +20,29 @@ import com.axis.innovators.box.verification.UserTags;
|
|||||||
import com.formdev.flatlaf.FlatLightLaf;
|
import com.formdev.flatlaf.FlatLightLaf;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.apache.logging.log4j.core.Appender;
|
||||||
|
import org.apache.logging.log4j.core.LoggerContext;
|
||||||
|
import org.apache.logging.log4j.core.appender.FileAppender;
|
||||||
|
import org.apache.logging.log4j.core.appender.RollingFileAppender;
|
||||||
|
import org.apache.logging.log4j.core.config.Configuration;
|
||||||
|
import org.api.dog.agent.VirtualMachine;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.tzd.lm.LM;
|
import org.tzd.lm.LM;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.*;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.util.ArrayList;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.util.Arrays;
|
import java.lang.management.*;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主类
|
* 主类
|
||||||
@@ -69,6 +82,7 @@ public class AxisInnovatorsBox {
|
|||||||
try {
|
try {
|
||||||
LibraryLoad.loadLibrary("FridaNative");
|
LibraryLoad.loadLibrary("FridaNative");
|
||||||
LibraryLoad.loadLibrary("ThrowSafely");
|
LibraryLoad.loadLibrary("ThrowSafely");
|
||||||
|
LibraryLoad.loadLibrary("DogAgent");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to load the 'FridaNative' library", e);
|
logger.error("Failed to load the 'FridaNative' library", e);
|
||||||
}
|
}
|
||||||
@@ -103,6 +117,7 @@ public class AxisInnovatorsBox {
|
|||||||
* 组织崩溃报告
|
* 组织崩溃报告
|
||||||
*/
|
*/
|
||||||
public void organizingCrashReports(Exception e) {
|
public void organizingCrashReports(Exception e) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
String systemOut = Log4j2OutputStream.systemOutContent.toString();
|
String systemOut = Log4j2OutputStream.systemOutContent.toString();
|
||||||
String systemErr = Log4j2OutputStream.systemErrContent.toString();
|
String systemErr = Log4j2OutputStream.systemErrContent.toString();
|
||||||
|
|
||||||
@@ -115,117 +130,554 @@ public class AxisInnovatorsBox {
|
|||||||
"\n========== 异常堆栈跟踪 ==========\n" + stackTrace;
|
"\n========== 异常堆栈跟踪 ==========\n" + stackTrace;
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> createAndShowGUI(report));
|
SwingUtilities.invokeLater(() -> createAndShowGUI(report));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAndShowGUI(String reportContent) {
|
private void createAndShowGUI(String reportContent) {
|
||||||
JDialog dialog = new JDialog((Frame) null,
|
// 使用 JFrame 作为顶级窗口
|
||||||
LanguageManager.getLoadedLanguages().getText("andShow.title"), true);
|
JFrame dialog = new JFrame(LanguageManager.getLoadedLanguages().getText("andShow.title"));
|
||||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
|
dialog.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||||
dialog.setLayout(new BorderLayout(10, 10));
|
|
||||||
|
|
||||||
try {
|
// 1. 添加窗口关闭监听器,在关闭时调用 quit()
|
||||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
dialog.addWindowListener(new WindowAdapter() {
|
||||||
} catch (Exception ignored) {}
|
@Override
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
quit();
|
||||||
|
}
|
||||||
|
|
||||||
JPanel topPanel = new JPanel(new BorderLayout(10, 10));
|
@Override
|
||||||
|
public void windowClosing(WindowEvent e) {
|
||||||
|
quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 设置窗口置顶
|
||||||
|
dialog.setAlwaysOnTop(true);
|
||||||
|
|
||||||
|
dialog.setLayout(new BorderLayout(15, 15));
|
||||||
|
dialog.getRootPane().setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
|
||||||
|
|
||||||
|
// 顶部面板 - 使用卡片布局增强层次感
|
||||||
|
JPanel topPanel = new JPanel(new BorderLayout(10, 15));
|
||||||
|
topPanel.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(220, 220, 220)),
|
||||||
|
BorderFactory.createEmptyBorder(15, 15, 15, 15)
|
||||||
|
));
|
||||||
|
|
||||||
|
// 图标和标题
|
||||||
|
JPanel headerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 15, 0));
|
||||||
Icon errorIcon = UIManager.getIcon("OptionPane.errorIcon");
|
Icon errorIcon = UIManager.getIcon("OptionPane.errorIcon");
|
||||||
JLabel iconLabel = new JLabel(errorIcon);
|
JLabel iconLabel = new JLabel(errorIcon);
|
||||||
|
|
||||||
JLabel titleLabel = new JLabel(LanguageManager.getLoadedLanguages().getText("andShow.title.2"));
|
JLabel titleLabel = new JLabel(LanguageManager.getLoadedLanguages().getText("andShow.title.2"));
|
||||||
|
titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD, 16));
|
||||||
|
titleLabel.setForeground(new Color(200, 0, 0));
|
||||||
|
|
||||||
|
headerPanel.add(iconLabel);
|
||||||
|
headerPanel.add(titleLabel);
|
||||||
|
|
||||||
|
// 说明文本
|
||||||
JLabel feedbackLabel = new JLabel(LanguageManager.getLoadedLanguages().getText("andShow.title.3"));
|
JLabel feedbackLabel = new JLabel(LanguageManager.getLoadedLanguages().getText("andShow.title.3"));
|
||||||
feedbackLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
feedbackLabel.setFont(feedbackLabel.getFont().deriveFont(14f));
|
||||||
|
feedbackLabel.setForeground(new Color(80, 80, 80));
|
||||||
|
feedbackLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 10));
|
||||||
|
|
||||||
JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
|
topPanel.add(headerPanel, BorderLayout.NORTH);
|
||||||
titlePanel.add(iconLabel);
|
|
||||||
titlePanel.add(titleLabel);
|
|
||||||
|
|
||||||
topPanel.add(titlePanel, BorderLayout.NORTH);
|
|
||||||
topPanel.add(feedbackLabel, BorderLayout.CENTER);
|
topPanel.add(feedbackLabel, BorderLayout.CENTER);
|
||||||
topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
|
||||||
|
|
||||||
|
// 内容区域 - 使用现代样式
|
||||||
JTextArea contentArea = new JTextArea(reportContent);
|
JTextArea contentArea = new JTextArea(reportContent);
|
||||||
contentArea.setEditable(false);
|
contentArea.setEditable(false);
|
||||||
contentArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
|
contentArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 13));
|
||||||
|
contentArea.setBackground(new Color(30, 30, 35));
|
||||||
|
contentArea.setCaretColor(Color.WHITE);
|
||||||
|
contentArea.setBorder(BorderFactory.createEmptyBorder(10, 15, 10, 15));
|
||||||
|
|
||||||
|
// 插件信息
|
||||||
StringBuilder pluginInfo = new StringBuilder();
|
StringBuilder pluginInfo = new StringBuilder();
|
||||||
pluginInfo.append(
|
pluginInfo.append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title"))
|
||||||
LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title"))
|
.append("\n\n");
|
||||||
.append("\n");
|
|
||||||
|
|
||||||
List<PluginDescriptor> corePluginList = new ArrayList<>();
|
List<PluginDescriptor> corePluginList = new ArrayList<>();
|
||||||
for (PluginDescriptor plugin : PluginLoader.getLoadedPlugins()) {
|
for (PluginDescriptor plugin : PluginLoader.getLoadedPlugins()) {
|
||||||
pluginInfo.append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.1")).append(plugin.getName()).append("\n");
|
pluginInfo.append("• ").append(plugin.getName()).append("\n");
|
||||||
pluginInfo.append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.2")).append(plugin.getDescription()).append("\n");
|
pluginInfo.append(" ").append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.2"))
|
||||||
pluginInfo.append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.3")).append(plugin.getSupportedVersions()).append("\n\n");
|
.append(plugin.getDescription()).append("\n");
|
||||||
|
pluginInfo.append(" ").append(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.title.3"))
|
||||||
|
.append(plugin.getSupportedVersions()).append("\n\n");
|
||||||
if (plugin.getTransformers() != null) {
|
if (plugin.getTransformers() != null) {
|
||||||
corePluginList.add(plugin);
|
corePluginList.add(plugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginInfo.append("=== 核心插件 ===\n");
|
pluginInfo.append("=== ").append(LanguageManager.getLoadedLanguages().getText("core.plugins")).append(" ===\n\n");
|
||||||
for (PluginDescriptor corePlugin : corePluginList) {
|
for (PluginDescriptor corePlugin : corePluginList) {
|
||||||
pluginInfo.append("核心插件主类: ").append(corePlugin.getName()).append("\n");
|
pluginInfo.append("▶ ").append(corePlugin.getName()).append("\n");
|
||||||
pluginInfo.append("核心转换器位置: ").append(corePlugin.getTransformers()).append("\n");
|
pluginInfo.append(" ").append(LanguageManager.getLoadedLanguages().getText("transformer.location"))
|
||||||
|
.append(corePlugin.getTransformers()).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
contentArea.append("\n\n=== 插件信息 ===\n");
|
contentArea.append("\n\n=== " +
|
||||||
|
LanguageManager.getLoadedLanguages().getText("plugin.info") +
|
||||||
|
" ===\n");
|
||||||
contentArea.append(pluginInfo.toString());
|
contentArea.append(pluginInfo.toString());
|
||||||
|
|
||||||
JScrollPane scrollPane = new JScrollPane(contentArea);
|
JScrollPane scrollPane = new JScrollPane(contentArea);
|
||||||
scrollPane.setBorder(BorderFactory.createTitledBorder("错误详细信息"));
|
scrollPane.setBorder(BorderFactory.createCompoundBorder(
|
||||||
|
BorderFactory.createTitledBorder(
|
||||||
|
BorderFactory.createLineBorder(new Color(180, 180, 180)),
|
||||||
|
LanguageManager.getLoadedLanguages().getText("error.details")),
|
||||||
|
BorderFactory.createEmptyBorder(5, 5, 5, 5)
|
||||||
|
));
|
||||||
|
|
||||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 10));
|
// 按钮面板
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 15, 10));
|
||||||
|
buttonPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
|
||||||
|
|
||||||
|
// 创建按钮
|
||||||
JButton exportButton = new JButton(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.exportButton"));
|
JButton exportButton = new JButton(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.exportButton"));
|
||||||
JButton closeButton = new JButton(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.closeButton"));
|
JButton closeButton = new JButton(LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.closeButton"));
|
||||||
|
|
||||||
exportButton.setForeground(Color.BLACK);
|
// 设置按钮样式
|
||||||
closeButton.setForeground(Color.BLACK);
|
setupModernButton(exportButton, new Color(0, 115, 207), new Color(0, 95, 180));
|
||||||
|
setupModernButton(closeButton, new Color(100, 100, 100), new Color(70, 70, 70));
|
||||||
|
|
||||||
exportButton.setBackground(new Color(0, 120, 215));
|
exportButton.addActionListener(e -> {
|
||||||
exportButton.setFocusPainted(false);
|
|
||||||
closeButton.setBackground(new Color(79, 79, 79));
|
|
||||||
closeButton.setFocusPainted(false);
|
|
||||||
|
|
||||||
exportButton.addActionListener((ActionEvent e) -> {
|
|
||||||
JFileChooser fileChooser = new JFileChooser();
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
fileChooser.setDialogTitle("保存崩溃报告");
|
fileChooser.setDialogTitle(LanguageManager.getLoadedLanguages().getText("save.crash.report"));
|
||||||
fileChooser.setSelectedFile(new File("crash_report.txt"));
|
|
||||||
|
|
||||||
int userSelection = fileChooser.showSaveDialog(dialog);
|
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
|
||||||
if (userSelection == JFileChooser.APPROVE_OPTION) {
|
Date date = new Date(System.currentTimeMillis());
|
||||||
File fileToSave = fileChooser.getSelectedFile();
|
String timestamp = formatter.format(date);
|
||||||
try (FileWriter writer = new FileWriter(fileToSave)) {
|
|
||||||
writer.write(reportContent + "\n" + pluginInfo);
|
TimeZone tz = TimeZone.getDefault();
|
||||||
|
String timezone = tz.getDisplayName(tz.inDaylightTime(date), TimeZone.SHORT).replace(":", "");
|
||||||
|
|
||||||
|
String filename = "AxisInnovatorsBox崩溃诊断报告_" + timestamp + "_" + timezone + ".zip";
|
||||||
|
fileChooser.setSelectedFile(new File(filename));
|
||||||
|
|
||||||
|
// 设置ZIP文件过滤器
|
||||||
|
fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("ZIP Files", "zip"));
|
||||||
|
|
||||||
|
if (fileChooser.showSaveDialog(dialog) == JFileChooser.APPROVE_OPTION) {
|
||||||
|
File zipFile = fileChooser.getSelectedFile();
|
||||||
|
if (!zipFile.getName().toLowerCase().endsWith(".zip")) {
|
||||||
|
zipFile = new File(zipFile.getAbsolutePath() + ".zip");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
|
||||||
|
// 1. 添加崩溃报告文件
|
||||||
|
ZipEntry crashEntry = new ZipEntry("basic_information.txt");
|
||||||
|
zos.putNextEntry(crashEntry);
|
||||||
|
String reportData = reportContent + "\n\n" + pluginInfo;
|
||||||
|
zos.write(reportData.getBytes(StandardCharsets.UTF_8));
|
||||||
|
zos.closeEntry();
|
||||||
|
|
||||||
|
// 2. 添加log4j日志文件
|
||||||
|
addLog4jLogsToZip(zos);
|
||||||
|
|
||||||
|
// 3. 添加其他调试文件
|
||||||
|
addDebugFilesToZip(zos);
|
||||||
|
|
||||||
|
// 成功消息
|
||||||
|
String message = LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer") +
|
||||||
|
"\n" + zipFile.getAbsolutePath();
|
||||||
JOptionPane.showMessageDialog(dialog,
|
JOptionPane.showMessageDialog(dialog,
|
||||||
LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer")
|
message,
|
||||||
+ "\n" + fileToSave.getAbsolutePath(),
|
|
||||||
LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer.title"),
|
LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer.title"),
|
||||||
JOptionPane.INFORMATION_MESSAGE);
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
|
String errorMessage = LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer.error") +
|
||||||
|
"\n" + ex.getMessage();
|
||||||
JOptionPane.showMessageDialog(dialog,
|
JOptionPane.showMessageDialog(dialog,
|
||||||
LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.writer.error")
|
errorMessage,
|
||||||
+ "\n" + ex.getMessage(),
|
|
||||||
LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.error.writer.title"),
|
LanguageManager.getLoadedLanguages().getText("andShow.pluginInfo.error.writer.title"),
|
||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
closeButton.addActionListener(e -> quit());
|
// 3. 添加错误音效并关闭窗口
|
||||||
|
closeButton.addActionListener(e -> {
|
||||||
|
playErrorSound(); // 播放错误音效
|
||||||
|
dialog.dispose(); // 关闭窗口
|
||||||
|
quit(); // 调用退出函数
|
||||||
|
});
|
||||||
|
|
||||||
buttonPanel.add(exportButton);
|
buttonPanel.add(exportButton);
|
||||||
buttonPanel.add(closeButton);
|
buttonPanel.add(closeButton);
|
||||||
|
|
||||||
|
// 组装主界面
|
||||||
dialog.add(topPanel, BorderLayout.NORTH);
|
dialog.add(topPanel, BorderLayout.NORTH);
|
||||||
dialog.add(scrollPane, BorderLayout.CENTER);
|
dialog.add(scrollPane, BorderLayout.CENTER);
|
||||||
dialog.add(buttonPanel, BorderLayout.SOUTH);
|
dialog.add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
dialog.setSize(800, 600);
|
dialog.setSize(900, 650);
|
||||||
dialog.setLocationRelativeTo(null);
|
dialog.setLocationRelativeTo(null);
|
||||||
|
|
||||||
|
// 播放错误音效
|
||||||
|
playErrorSound();
|
||||||
|
|
||||||
|
// 显示窗口
|
||||||
dialog.setVisible(true);
|
dialog.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 播放错误音效的方法
|
||||||
|
private void playErrorSound() {
|
||||||
|
try {
|
||||||
|
// 使用系统默认的错误音效
|
||||||
|
Toolkit.getDefaultToolkit().beep();
|
||||||
|
|
||||||
|
// 或者播放自定义音效
|
||||||
|
/*
|
||||||
|
File soundFile = new File("error_sound.wav");
|
||||||
|
if (soundFile.exists()) {
|
||||||
|
AudioInputStream audioIn = AudioSystem.getAudioInputStream(soundFile);
|
||||||
|
Clip clip = AudioSystem.getClip();
|
||||||
|
clip.open(audioIn);
|
||||||
|
clip.start();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// 忽略音效播放错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLog4jLogsToZip(ZipOutputStream zos) throws IOException {
|
||||||
|
LoggerContext context = (LoggerContext) LogManager.getContext(false);
|
||||||
|
Configuration config = context.getConfiguration();
|
||||||
|
Set<String> addedFiles = new HashSet<>();
|
||||||
|
|
||||||
|
// 获取所有配置的附加器
|
||||||
|
Collection<Appender> appenders = config.getAppenders().values();
|
||||||
|
for (Appender appender : appenders) {
|
||||||
|
processAppender(zos, addedFiles, appender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processAppender(ZipOutputStream zos, Set<String> addedFiles,
|
||||||
|
Appender appender) throws IOException {
|
||||||
|
if (appender instanceof FileAppender) {
|
||||||
|
FileAppender fileAppender = (FileAppender) appender;
|
||||||
|
String fileName = fileAppender.getFileName();
|
||||||
|
|
||||||
|
if (fileName != null && !addedFiles.contains(fileName)) {
|
||||||
|
addFileToZip(zos, new File(fileName), "logs/");
|
||||||
|
addedFiles.add(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appender instanceof RollingFileAppender rollingAppender) {
|
||||||
|
addRollingLogFiles(zos, rollingAppender, addedFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRollingLogFiles(ZipOutputStream zos, RollingFileAppender appender,
|
||||||
|
Set<String> addedFiles) throws IOException {
|
||||||
|
String fileName = appender.getFileName();
|
||||||
|
if (fileName == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File logFile = new File(fileName);
|
||||||
|
File logDir = logFile.getParentFile();
|
||||||
|
String baseName = getString(appender);
|
||||||
|
|
||||||
|
if (logDir != null && logDir.exists()) {
|
||||||
|
File[] logFiles = logDir.listFiles((dir, name) ->
|
||||||
|
name.startsWith(baseName) || name.startsWith("box"));
|
||||||
|
|
||||||
|
if (logFiles != null) {
|
||||||
|
for (File file : logFiles) {
|
||||||
|
String absolutePath = file.getAbsolutePath();
|
||||||
|
if (!addedFiles.contains(absolutePath)) {
|
||||||
|
addFileToZip(zos, file, "logs/");
|
||||||
|
addedFiles.add(absolutePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull String getString(RollingFileAppender appender) {
|
||||||
|
String baseName;
|
||||||
|
|
||||||
|
String filePattern = appender.getFilePattern();
|
||||||
|
if (filePattern != null) {
|
||||||
|
// 移除目录部分
|
||||||
|
int lastSlash = Math.max(filePattern.lastIndexOf('/'), filePattern.lastIndexOf('\\'));
|
||||||
|
if (lastSlash > 0) {
|
||||||
|
filePattern = filePattern.substring(lastSlash + 1);
|
||||||
|
}
|
||||||
|
int patternStart = filePattern.indexOf('%');
|
||||||
|
if (patternStart == -1) patternStart = filePattern.indexOf('$');
|
||||||
|
|
||||||
|
if (patternStart > 0) {
|
||||||
|
baseName = filePattern.substring(0, patternStart);
|
||||||
|
} else {
|
||||||
|
baseName = "box";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
baseName = "box";
|
||||||
|
}
|
||||||
|
return baseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDebugFilesToZip(ZipOutputStream zos) throws IOException {
|
||||||
|
// 1. 添加系统信息文件
|
||||||
|
addFileToZip(zos, generateSystemInfoFile(), "debug_files/system_info.txt");
|
||||||
|
// 2. 添加JVM加载的类信息
|
||||||
|
addFileToZip(zos, generateClassLoaderInfo(), "debug_files/class_loader_info.txt");
|
||||||
|
// 3. 添加内存状态信息
|
||||||
|
addFileToZip(zos, generateMemoryInfo(), "debug_files/memory_info.txt");
|
||||||
|
// 4. 添加线程堆栈信息
|
||||||
|
addFileToZip(zos, generateThreadDump(), "debug_files/thread_dump.txt");
|
||||||
|
// 5. 添加GC信息
|
||||||
|
addFileToZip(zos, generateGCInfo(), "debug_files/gc_info.txt");
|
||||||
|
// 6. 添加系统属性
|
||||||
|
addFileToZip(zos, generateSystemProperties(), "debug_files/system_properties.txt");
|
||||||
|
// 7. 添加环境变量
|
||||||
|
addFileToZip(zos, generateEnvironmentVariables(), "debug_files/environment_variables.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
private File generateClassLoaderInfo() throws IOException {
|
||||||
|
File tempFile = File.createTempFile("class_loader", ".txt");
|
||||||
|
try (PrintWriter writer = new PrintWriter(tempFile)) {
|
||||||
|
writer.println("===== Class Loader Hierarchy =====");
|
||||||
|
Instrumentation instrumentation = null;
|
||||||
|
try {
|
||||||
|
|
||||||
|
VirtualMachine vm = VirtualMachine.getVirtualMachine(ProcessHandle.current().pid(), true);
|
||||||
|
instrumentation = vm.getInstrumentation();
|
||||||
|
} catch (Exception e) {
|
||||||
|
writer.println("Failed to attach to VM: " + e.getMessage());
|
||||||
|
e.printStackTrace(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instrumentation != null) {
|
||||||
|
writer.println("\n===== Loaded Classes =====");
|
||||||
|
Class<?>[] allClasses = instrumentation.getAllLoadedClasses();
|
||||||
|
writer.println("Total Classes: " + allClasses.length);
|
||||||
|
|
||||||
|
Map<ClassLoader, List<Class<?>>> classesByLoader = Arrays.stream(allClasses)
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
cls -> cls.getClassLoader() == null ?
|
||||||
|
BootstrapClassLoader.INSTANCE :
|
||||||
|
cls.getClassLoader()
|
||||||
|
));
|
||||||
|
|
||||||
|
for (Map.Entry<ClassLoader, List<Class<?>>> entry : classesByLoader.entrySet()) {
|
||||||
|
ClassLoader loader = entry.getKey();
|
||||||
|
List<Class<?>> classes = entry.getValue();
|
||||||
|
|
||||||
|
String loaderName = (loader == BootstrapClassLoader.INSTANCE) ?
|
||||||
|
"Bootstrap ClassLoader" :
|
||||||
|
loader.toString();
|
||||||
|
|
||||||
|
writer.println("\nClassLoader: " + loaderName);
|
||||||
|
writer.println("Classes Count: " + classes.size());
|
||||||
|
|
||||||
|
for (Class<?> cls : classes) {
|
||||||
|
writer.println(" - " + cls.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BootstrapClassLoader extends ClassLoader {
|
||||||
|
static final BootstrapClassLoader INSTANCE = new BootstrapClassLoader();
|
||||||
|
private BootstrapClassLoader() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Bootstrap ClassLoader";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File generateMemoryInfo() throws IOException {
|
||||||
|
File tempFile = File.createTempFile("memory", ".txt");
|
||||||
|
try (PrintWriter writer = new PrintWriter(tempFile)) {
|
||||||
|
MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();
|
||||||
|
|
||||||
|
writer.println("===== Heap Memory Usage =====");
|
||||||
|
MemoryUsage heapUsage = memoryMxBean.getHeapMemoryUsage();
|
||||||
|
writer.println("Init: " + formatMemory(heapUsage.getInit()));
|
||||||
|
writer.println("Used: " + formatMemory(heapUsage.getUsed()));
|
||||||
|
writer.println("Committed: " + formatMemory(heapUsage.getCommitted()));
|
||||||
|
writer.println("Max: " + formatMemory(heapUsage.getMax()));
|
||||||
|
|
||||||
|
writer.println("\n===== Non-Heap Memory Usage =====");
|
||||||
|
MemoryUsage nonHeapUsage = memoryMxBean.getNonHeapMemoryUsage();
|
||||||
|
writer.println("Init: " + formatMemory(nonHeapUsage.getInit()));
|
||||||
|
writer.println("Used: " + formatMemory(nonHeapUsage.getUsed()));
|
||||||
|
writer.println("Committed: " + formatMemory(nonHeapUsage.getCommitted()));
|
||||||
|
writer.println("Max: " + formatMemory(nonHeapUsage.getMax()));
|
||||||
|
|
||||||
|
writer.println("\n===== Memory Pool Details =====");
|
||||||
|
List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
|
||||||
|
for (MemoryPoolMXBean pool : pools) {
|
||||||
|
writer.println("\nPool: " + pool.getName());
|
||||||
|
writer.println("Type: " + pool.getType());
|
||||||
|
MemoryUsage usage = pool.getUsage();
|
||||||
|
writer.println("Usage: " + formatMemory(usage.getUsed()) + " / " + formatMemory(usage.getCommitted()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatMemory(long bytes) {
|
||||||
|
if (bytes < 1024) return bytes + " B";
|
||||||
|
int exp = (int) (Math.log(bytes) / Math.log(1024));
|
||||||
|
char unit = "KMGTPE".charAt(exp - 1);
|
||||||
|
return String.format("%.1f %sB", bytes / Math.pow(1024, exp), unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private File generateThreadDump() throws IOException {
|
||||||
|
File tempFile = File.createTempFile("thread_dump", ".txt");
|
||||||
|
try (PrintWriter writer = new PrintWriter(tempFile)) {
|
||||||
|
ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean();
|
||||||
|
ThreadInfo[] threadInfos = threadMxBean.dumpAllThreads(true, true);
|
||||||
|
|
||||||
|
writer.println("===== Thread Dump (" + threadInfos.length + " threads) =====");
|
||||||
|
for (ThreadInfo threadInfo : threadInfos) {
|
||||||
|
writer.println("\nThread #" + threadInfo.getThreadId() + ": " + threadInfo.getThreadName());
|
||||||
|
writer.println("State: " + threadInfo.getThreadState());
|
||||||
|
writer.println("Stack Trace:");
|
||||||
|
for (StackTraceElement stackTraceElement : threadInfo.getStackTrace()) {
|
||||||
|
writer.println(" " + stackTraceElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File generateGCInfo() throws IOException {
|
||||||
|
File tempFile = File.createTempFile("gc_", ".txt");
|
||||||
|
try (PrintWriter writer = new PrintWriter(tempFile)) {
|
||||||
|
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
|
||||||
|
|
||||||
|
writer.println("===== Garbage Collection =====");
|
||||||
|
for (GarbageCollectorMXBean gcBean : gcBeans) {
|
||||||
|
writer.println("\nCollector: " + gcBean.getName());
|
||||||
|
writer.println("Collections: " + gcBean.getCollectionCount());
|
||||||
|
writer.println("Time: " + gcBean.getCollectionTime() + " ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成系统属性
|
||||||
|
private File generateSystemProperties() throws IOException {
|
||||||
|
File tempFile = File.createTempFile("system_props", ".txt");
|
||||||
|
try (PrintWriter writer = new PrintWriter(tempFile)) {
|
||||||
|
Properties props = System.getProperties();
|
||||||
|
writer.println("===== System Properties =====");
|
||||||
|
for (String name : props.stringPropertyNames()) {
|
||||||
|
writer.println(name + " = " + props.getProperty(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成环境变量
|
||||||
|
private File generateEnvironmentVariables() throws IOException {
|
||||||
|
File tempFile = File.createTempFile("env_vars", ".txt");
|
||||||
|
try (PrintWriter writer = new PrintWriter(tempFile)) {
|
||||||
|
Map<String, String> env = System.getenv();
|
||||||
|
writer.println("===== Environment Variables =====");
|
||||||
|
for (Map.Entry<String, String> entry : env.entrySet()) {
|
||||||
|
writer.println(entry.getKey() + " = " + entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加目录到ZIP(递归)
|
||||||
|
private void addDirectoryToZip(ZipOutputStream zos, File dir, String basePath) throws IOException {
|
||||||
|
if (!dir.exists() || !dir.isDirectory()) return;
|
||||||
|
|
||||||
|
for (File file : dir.listFiles()) {
|
||||||
|
String entryPath = basePath + "/" + file.getName();
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
addDirectoryToZip(zos, file, entryPath);
|
||||||
|
} else {
|
||||||
|
addFileToZip(zos, file, entryPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addFileToZip(ZipOutputStream zos, File file, String entryPath) throws IOException {
|
||||||
|
if (!file.exists()) return;
|
||||||
|
|
||||||
|
String entryName = entryPath + file.getName();
|
||||||
|
ZipEntry zipEntry = new ZipEntry(entryName);
|
||||||
|
zos.putNextEntry(zipEntry);
|
||||||
|
|
||||||
|
try (FileInputStream fis = new FileInputStream(file)) {
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int length;
|
||||||
|
while ((length = fis.read(buffer)) > 0) {
|
||||||
|
zos.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zos.closeEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成系统信息文件的方法
|
||||||
|
private File generateSystemInfoFile() throws IOException {
|
||||||
|
File tempFile = File.createTempFile("system_info", ".txt");
|
||||||
|
try (PrintWriter writer = new PrintWriter(tempFile)) {
|
||||||
|
writer.println("===== System Information =====");
|
||||||
|
writer.println("OS: " + System.getProperty("os.name"));
|
||||||
|
writer.println("Version: " + System.getProperty("os.version"));
|
||||||
|
writer.println("Java Version: " + System.getProperty("java.version"));
|
||||||
|
writer.println("User: " + System.getProperty("user.name"));
|
||||||
|
writer.println("\n===== Runtime Information =====");
|
||||||
|
writer.println("Free Memory: " + Runtime.getRuntime().freeMemory() / (1024 * 1024) + " MB");
|
||||||
|
writer.println("Max Memory: " + Runtime.getRuntime().maxMemory() / (1024 * 1024) + " MB");
|
||||||
|
}
|
||||||
|
tempFile.deleteOnExit();
|
||||||
|
return tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setupModernButton(JButton button, Color bgColor, Color hoverColor) {
|
||||||
|
button.setFont(button.getFont().deriveFont(Font.BOLD));
|
||||||
|
button.setFocusPainted(false);
|
||||||
|
button.setBorder(BorderFactory.createEmptyBorder(8, 20, 8, 20));
|
||||||
|
button.setBackground(bgColor);
|
||||||
|
button.setForeground(Color.WHITE);
|
||||||
|
|
||||||
|
button.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(MouseEvent e) {
|
||||||
|
button.setBackground(hoverColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(MouseEvent e) {
|
||||||
|
button.setBackground(bgColor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
button.getModel().addChangeListener(e -> {
|
||||||
|
if (button.getModel().isPressed()) {
|
||||||
|
button.setBackground(hoverColor.darker());
|
||||||
|
} else {
|
||||||
|
button.setBackground(button.getModel().isRollover() ? hoverColor : bgColor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化Log4j2
|
* 初始化Log4j2
|
||||||
*/
|
*/
|
||||||
@@ -316,9 +768,6 @@ public class AxisInnovatorsBox {
|
|||||||
windowsJDialog.repaint();
|
windowsJDialog.repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
//public void execute(Runnable runnable){
|
|
||||||
// thread.
|
|
||||||
//}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新加载窗口
|
* 重新加载窗口
|
||||||
@@ -394,6 +843,7 @@ public class AxisInnovatorsBox {
|
|||||||
main.runWindow();
|
main.runWindow();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("There was a problem starting the main thread", e);
|
logger.error("There was a problem starting the main thread", e);
|
||||||
|
if (main.ex != null)
|
||||||
main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
main.organizingCrashReports(e);
|
main.organizingCrashReports(e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -407,8 +857,10 @@ public class AxisInnovatorsBox {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}, "TrayThread").start();
|
}, "TrayThread").start();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Failed to load plugins", e);
|
logger.error("Failed to load plugins", e);
|
||||||
|
if (main.ex != null)
|
||||||
main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
main.organizingCrashReports(e);
|
main.organizingCrashReports(e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -418,8 +870,10 @@ public class AxisInnovatorsBox {
|
|||||||
main.thread.start();
|
main.thread.start();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("In unexpected errors", e);
|
logger.error("In unexpected errors", e);
|
||||||
|
if (main.ex != null)
|
||||||
main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
main.ex.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
main.organizingCrashReports(e);
|
main.organizingCrashReports(e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ import com.axis.innovators.box.tools.FolderCreator;
|
|||||||
import com.axis.innovators.box.register.LanguageManager;
|
import com.axis.innovators.box.register.LanguageManager;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.FileLock;
|
||||||
|
import java.nio.channels.OverlappingFileLockException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -16,7 +22,23 @@ import java.util.Map;
|
|||||||
* @author tzdwindows 7
|
* @author tzdwindows 7
|
||||||
*/
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
// 单实例锁文件位置(系统临时目录)
|
||||||
|
private static final String LOCK_FILE = System.getProperty("java.io.tmpdir") + "/axis_innovators_box.lock";
|
||||||
|
private static FileLock lock = null;
|
||||||
|
private static RandomAccessFile lockFile = null;
|
||||||
|
private static FileChannel lockChannel = null;
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
if (!acquireLock()) {
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
null,
|
||||||
|
"程序已在运行中,无法启动多个实例",
|
||||||
|
"错误",
|
||||||
|
JOptionPane.ERROR_MESSAGE
|
||||||
|
);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10);
|
FolderCleaner.cleanFolder(FolderCreator.getLogsFolder(), 10);
|
||||||
|
|
||||||
LanguageManager.loadSavedLanguage();
|
LanguageManager.loadSavedLanguage();
|
||||||
@@ -38,15 +60,63 @@ public class Main {
|
|||||||
ModernJarViewer viewer = new ModernJarViewer(null, path);
|
ModernJarViewer viewer = new ModernJarViewer(null, path);
|
||||||
viewer.setVisible(true);
|
viewer.setVisible(true);
|
||||||
});
|
});
|
||||||
|
releaseLock(); // 释放锁(窗口模式)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (".html".equals(extension)) {
|
if (".html".equals(extension)) {
|
||||||
MainApplication.popupHTMLWindow(path);
|
MainApplication.popupHTMLWindow(path);
|
||||||
|
releaseLock(); // 释放锁(窗口模式)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AxisInnovatorsBox.run(args);
|
AxisInnovatorsBox.run(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试获取文件锁(单实例检查)
|
||||||
|
*/
|
||||||
|
private static boolean acquireLock() {
|
||||||
|
try {
|
||||||
|
lockFile = new RandomAccessFile(LOCK_FILE, "rw");
|
||||||
|
lockChannel = lockFile.getChannel();
|
||||||
|
|
||||||
|
lock = lockChannel.tryLock();
|
||||||
|
return lock != null;
|
||||||
|
} catch (OverlappingFileLockException e) {
|
||||||
|
return false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放文件锁
|
||||||
|
*/
|
||||||
|
private static void releaseLock() {
|
||||||
|
try {
|
||||||
|
if (lock != null && lock.isValid()) {
|
||||||
|
lock.release();
|
||||||
|
}
|
||||||
|
if (lockChannel != null) {
|
||||||
|
lockChannel.close();
|
||||||
|
}
|
||||||
|
if (lockFile != null) {
|
||||||
|
lockFile.close();
|
||||||
|
}
|
||||||
|
// 可选:删除锁文件
|
||||||
|
new File(LOCK_FILE).delete();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加JVM关闭钩子确保锁释放
|
||||||
|
static {
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||||
|
releaseLock();
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -36,8 +36,6 @@ public class MainWindow extends JFrame {
|
|||||||
private final Map<JComponent, Integer> cardElevations = new HashMap<>();
|
private final Map<JComponent, Integer> cardElevations = new HashMap<>();
|
||||||
private final Color CARD_COLOR = Color.WHITE;
|
private final Color CARD_COLOR = Color.WHITE;
|
||||||
private final List<ToolCategory> categories = new ArrayList<>();
|
private final List<ToolCategory> categories = new ArrayList<>();
|
||||||
private final boolean isBackground = true;
|
|
||||||
private final boolean isBlur = true;
|
|
||||||
private SystemTray systemTray;
|
private SystemTray systemTray;
|
||||||
//private TrayIcon trayIcon;
|
//private TrayIcon trayIcon;
|
||||||
|
|
||||||
@@ -62,7 +60,6 @@ public class MainWindow extends JFrame {
|
|||||||
categories.add(category);
|
categories.add(category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void initUI() {
|
public void initUI() {
|
||||||
setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title"));
|
setTitle(LanguageManager.getLoadedLanguages().getText("mainWindow.title"));
|
||||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||||
@@ -78,13 +75,14 @@ public class MainWindow extends JFrame {
|
|||||||
mainPanel.setOpaque(true);
|
mainPanel.setOpaque(true);
|
||||||
|
|
||||||
mainPanel.setLayout(new BorderLayout(20, 20));
|
mainPanel.setLayout(new BorderLayout(20, 20));
|
||||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(30, 30, 30, 30));
|
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 30, 30, 30));
|
||||||
|
|
||||||
mainPanel.add(createHeader(), BorderLayout.NORTH);
|
mainPanel.add(createHeader(), BorderLayout.NORTH);
|
||||||
mainPanel.add(createCategoryTabs(), BorderLayout.CENTER);
|
mainPanel.add(createCategoryTabs(), BorderLayout.CENTER);
|
||||||
|
//mainPanel.add(createFooter(), BorderLayout.SOUTH); 底部菜单
|
||||||
|
|
||||||
add(mainPanel);
|
add(mainPanel);
|
||||||
createSystemTray();
|
//createSystemTray();
|
||||||
addWindowListener(new WindowAdapter() {
|
addWindowListener(new WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosing(WindowEvent e) {
|
public void windowClosing(WindowEvent e) {
|
||||||
@@ -93,47 +91,25 @@ public class MainWindow extends JFrame {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createSystemTray() {
|
private JPanel createFooter() {
|
||||||
if (!SystemTray.isSupported()) {
|
JPanel footer = new JPanel();
|
||||||
logger.error("系统托盘不支持!");
|
footer.setLayout(new BoxLayout(footer, BoxLayout.X_AXIS));
|
||||||
return;
|
footer.setBorder(BorderFactory.createEmptyBorder(15, 30, 15, 30));
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化系统托盘
|
JLabel version = new JLabel("轴创工具箱 v1.0");
|
||||||
systemTray = SystemTray.getSystemTray();
|
version.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
||||||
|
version.setForeground(new Color(120, 120, 120));
|
||||||
|
|
||||||
// 1. 加载并处理圆角图标(修正图像类型)
|
footer.add(version);
|
||||||
ImageIcon rawIcon = LoadIcon.loadIcon("logo.png", 64);
|
footer.add(Box.createHorizontalGlue());
|
||||||
Image roundedImage = createRoundedIcon(rawIcon.getImage(), 16);
|
|
||||||
|
|
||||||
// 2. 创建支持中文的弹出菜单
|
JLabel status = new JLabel("已加载 " + categories.size() + " 个分类, " +
|
||||||
PopupMenu popup = new PopupMenu();
|
categories.stream().mapToInt(c -> c.getTools().size()).sum() + " 个工具");
|
||||||
|
status.setFont(new Font("微软雅黑", Font.PLAIN, 12));
|
||||||
|
status.setForeground(new Color(120, 120, 120));
|
||||||
|
footer.add(status);
|
||||||
|
|
||||||
// 3. 创建菜单项(使用标准AWT组件)
|
return footer;
|
||||||
MenuItem openItem = createBaseMenuItem("打开主界面", e -> setVisible(true));
|
|
||||||
MenuItem decompileItem = createBaseMenuItem("打开反编译工具", e ->
|
|
||||||
new ModernJarViewer(null).setVisible(true));
|
|
||||||
MenuItem exitItem = createBaseMenuItem("退出程序", e -> AxisInnovatorsBox.getMain().quit());
|
|
||||||
|
|
||||||
// 4. 构建菜单结构
|
|
||||||
popup.add(openItem);
|
|
||||||
popup.addSeparator();
|
|
||||||
popup.add(decompileItem);
|
|
||||||
popup.addSeparator();
|
|
||||||
popup.add(exitItem);
|
|
||||||
|
|
||||||
// 5. 创建托盘图标
|
|
||||||
//trayIcon = new TrayIcon(roundedImage, "轴创工具箱", popup);
|
|
||||||
//trayIcon.setImageAutoSize(true);
|
|
||||||
|
|
||||||
// 6. 添加事件监听
|
|
||||||
//addTrayEventListeners();
|
|
||||||
|
|
||||||
//try {
|
|
||||||
// systemTray.add(trayIcon);
|
|
||||||
//} catch (AWTException ex) {
|
|
||||||
// logger.error("添加系统托盘图标失败", ex);
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 基础菜单项创建方法(解决方法不存在问题)
|
// 基础菜单项创建方法(解决方法不存在问题)
|
||||||
@@ -347,6 +323,8 @@ public class MainWindow extends JFrame {
|
|||||||
title.setFont(new Font("微软雅黑", Font.BOLD, 28));
|
title.setFont(new Font("微软雅黑", Font.BOLD, 28));
|
||||||
title.setForeground(new Color(255, 255, 255));
|
title.setForeground(new Color(255, 255, 255));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
JButton settings = new JButton(LoadIcon.loadIcon("settings.png", 32));
|
JButton settings = new JButton(LoadIcon.loadIcon("settings.png", 32));
|
||||||
settings.putClientProperty(FlatClientProperties.BUTTON_TYPE, FlatClientProperties.BUTTON_TYPE_BORDERLESS);
|
settings.putClientProperty(FlatClientProperties.BUTTON_TYPE, FlatClientProperties.BUTTON_TYPE_BORDERLESS);
|
||||||
settings.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
settings.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||||
|
|||||||
Reference in New Issue
Block a user