feat(gui): add button to install desktop file directly from jadx-gui (PR #2404)
* fix: missing/incorrect .desktop file values - correct comment - Add StartupWMClass * feat: add button to install desktop file from jadx-gui (#1392) * fix: use POSIX compliant realpath instead of readlink * fix: get XDG executable from PATH
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Name=JADX GUI
|
||||
Comment=Dex to Java compiler
|
||||
Comment=Dex to Java decompiler
|
||||
Icon=jadx
|
||||
Exec=jadx-gui %f
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Development;Java;
|
||||
Keywords=Java;Decompiler;
|
||||
StartupWMClass=jadx-gui-JadxGUI
|
||||
|
||||
@@ -231,6 +231,16 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Path createTempFileNonPrefixed(String fileName) {
|
||||
try {
|
||||
Path path = Files.createFile(tempRootDir.resolve(fileName));
|
||||
path.toFile().deleteOnExit();
|
||||
return path;
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Failed to create non-prefixed temp file: " + fileName, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyStream(InputStream input, OutputStream output) throws IOException {
|
||||
byte[] buffer = new byte[READ_BUFFER_SIZE];
|
||||
while (true) {
|
||||
@@ -266,6 +276,11 @@ public class FileUtils {
|
||||
StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
}
|
||||
|
||||
public static void writeFile(Path file, InputStream is) throws IOException {
|
||||
FileUtils.makeDirsForFile(file);
|
||||
Files.copy(is, file, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
public static String readFile(Path textFile) throws IOException {
|
||||
return Files.readString(textFile);
|
||||
}
|
||||
|
||||
@@ -110,11 +110,19 @@ project.components.withType(AdhocComponentWithVariants::class.java).forEach { c
|
||||
|
||||
tasks.startShadowScripts {
|
||||
doLast {
|
||||
val newContent =
|
||||
val newWindowsScriptContent =
|
||||
windowsScript.readText()
|
||||
.replace("java.exe", "javaw.exe")
|
||||
.replace("\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS%", "start \"jadx-gui\" /B \"%JAVA_EXE%\" %DEFAULT_JVM_OPTS%")
|
||||
windowsScript.writeText(newContent)
|
||||
// Add launch script path as a property
|
||||
val newUnixScriptContent =
|
||||
unixScript.readText()
|
||||
.replace(
|
||||
Regex("DEFAULT_JVM_OPTS=.+", RegexOption.MULTILINE),
|
||||
{ result -> result.value + "\" \\\"-Djadx.launchScript.path=\$(realpath $0)\\\"\"" },
|
||||
)
|
||||
windowsScript.writeText(newWindowsScriptContent)
|
||||
unixScript.writeText(newUnixScriptContent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,11 +153,13 @@ import jadx.gui.ui.treenodes.StartPageNode;
|
||||
import jadx.gui.ui.treenodes.SummaryNode;
|
||||
import jadx.gui.update.JadxUpdate;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.DesktopEntryUtils;
|
||||
import jadx.gui.utils.FontUtils;
|
||||
import jadx.gui.utils.ILoadListener;
|
||||
import jadx.gui.utils.LafManager;
|
||||
import jadx.gui.utils.Link;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.SystemInfo;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.dbg.UIWatchDog;
|
||||
import jadx.gui.utils.fileswatcher.LiveReloadWorker;
|
||||
@@ -1186,6 +1188,9 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
|
||||
JMenu help = new JadxMenu(NLS.str("menu.help"), shortcutsController);
|
||||
help.setMnemonic(KeyEvent.VK_H);
|
||||
help.add(showLogAction);
|
||||
if (SystemInfo.IS_UNIX && !SystemInfo.IS_MAC) {
|
||||
help.add(new JadxGuiAction(ActionModel.CREATE_DESKTOP_ENTRY, this::createDesktopEntry));
|
||||
}
|
||||
if (Jadx.isDevVersion()) {
|
||||
help.add(new AbstractAction("Show sample error report") {
|
||||
@Override
|
||||
@@ -1724,6 +1729,16 @@ public class MainWindow extends JFrame implements ExportProjectDialog.ExportProj
|
||||
pluginsMenu.add(item);
|
||||
}
|
||||
|
||||
private void createDesktopEntry() {
|
||||
if (DesktopEntryUtils.createDesktopEntry()) {
|
||||
JOptionPane.showMessageDialog(this, NLS.str("message.desktop_entry_creation_success"),
|
||||
NLS.str("message.success_title"), JOptionPane.INFORMATION_MESSAGE);
|
||||
} else {
|
||||
JOptionPane.showMessageDialog(this, NLS.str("message.desktop_entry_creation_error"),
|
||||
NLS.str("message.errorTitle"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
public RenameMappingsGui getRenameMappings() {
|
||||
return renameMappings;
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ public enum ActionModel {
|
||||
Shortcut.keyboard(KeyEvent.VK_D, UiUtils.ctrlButton() | KeyEvent.ALT_DOWN_MASK)),
|
||||
SHOW_LOG(MENU_TOOLBAR, "menu.log", "menu.log", "ui/logVerbose",
|
||||
Shortcut.keyboard(KeyEvent.VK_L, UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK)),
|
||||
CREATE_DESKTOP_ENTRY(MENU_TOOLBAR, "menu.create_desktop_entry", "menu.create_desktop_entry", null, Shortcut.none()),
|
||||
BACK(MENU_TOOLBAR, "nav.back", "nav.back", "ui/left",
|
||||
Shortcut.keyboard(KeyEvent.VK_ESCAPE)),
|
||||
BACK_V(MENU_TOOLBAR, "nav.back", "nav.back", "ui/left",
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.export.TemplateFile;
|
||||
import jadx.core.utils.files.FileUtils;
|
||||
|
||||
public class DesktopEntryUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DesktopEntryUtils.class);
|
||||
private static final Map<Integer, String> SIZE_TO_LOGO_MAP = Map.of(
|
||||
16, "jadx-logo-16px.png",
|
||||
32, "jadx-logo-32px.png",
|
||||
48, "jadx-logo-48px.png",
|
||||
252, "jadx-logo.png",
|
||||
256, "jadx-logo.png");
|
||||
private static final Path XDG_DESKTOP_MENU_COMMAND_PATH = findExecutablePath("xdg-desktop-menu");
|
||||
private static final Path XDG_ICON_RESOURCE_COMMAND_PATH = findExecutablePath("xdg-icon-resource");
|
||||
|
||||
public static boolean createDesktopEntry() {
|
||||
if (XDG_DESKTOP_MENU_COMMAND_PATH == null) {
|
||||
LOG.error("xdg-desktop-menu was not found in $PATH");
|
||||
return false;
|
||||
}
|
||||
if (XDG_ICON_RESOURCE_COMMAND_PATH == null) {
|
||||
LOG.error("xdg-icon-resource was not found in $PATH");
|
||||
return false;
|
||||
}
|
||||
Path desktopTempFile = FileUtils.createTempFileNonPrefixed("jadx-gui.desktop");
|
||||
Path iconTempFolder = FileUtils.createTempDir("logos");
|
||||
LOG.debug("Creating desktop with temp files: {}, {}", desktopTempFile, iconTempFolder);
|
||||
try {
|
||||
return createDesktopEntry(desktopTempFile, iconTempFolder);
|
||||
} finally {
|
||||
try {
|
||||
FileUtils.deleteFileIfExists(desktopTempFile);
|
||||
FileUtils.deleteDirIfExists(iconTempFolder);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to clean up temp files", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean createDesktopEntry(Path desktopTempFile, Path iconTempFolder) {
|
||||
String launchScriptPath = getLaunchScriptPath();
|
||||
if (launchScriptPath == null) {
|
||||
return false;
|
||||
}
|
||||
for (Map.Entry<Integer, String> entry : SIZE_TO_LOGO_MAP.entrySet()) {
|
||||
Path path = iconTempFolder.resolve(entry.getKey() + ".png");
|
||||
if (!writeLogoFile(entry.getValue(), path)) {
|
||||
return false;
|
||||
}
|
||||
if (!installIcon(entry.getKey(), path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!writeDesktopFile(launchScriptPath, desktopTempFile)) {
|
||||
return false;
|
||||
}
|
||||
return installDesktopEntry(desktopTempFile);
|
||||
}
|
||||
|
||||
private static boolean installDesktopEntry(Path desktopTempFile) {
|
||||
try {
|
||||
ProcessBuilder desktopFileInstallCommand = new ProcessBuilder(Objects.requireNonNull(XDG_DESKTOP_MENU_COMMAND_PATH).toString(),
|
||||
"install", desktopTempFile.toString());
|
||||
Process process = desktopFileInstallCommand.start();
|
||||
int statusCode = process.waitFor();
|
||||
if (statusCode != 0) {
|
||||
LOG.error("Got error code {} while installing desktop file", statusCode);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to install desktop file", e);
|
||||
return false;
|
||||
}
|
||||
LOG.info("Successfully installed desktop file");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean installIcon(int size, Path iconPath) {
|
||||
try {
|
||||
ProcessBuilder iconInstallCommand =
|
||||
new ProcessBuilder(Objects.requireNonNull(XDG_ICON_RESOURCE_COMMAND_PATH).toString(), "install", "--novendor", "--size",
|
||||
String.valueOf(size), iconPath.toString(),
|
||||
"jadx");
|
||||
Process process = iconInstallCommand.start();
|
||||
int statusCode = process.waitFor();
|
||||
if (statusCode != 0) {
|
||||
LOG.error("Got error code {} while installing icon of size {}", statusCode, size);
|
||||
return false;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to install icon of size {}", size, e);
|
||||
return false;
|
||||
}
|
||||
LOG.info("Successfully installed icon of size {}", size);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Path findExecutablePath(String executableName) {
|
||||
for (String pathDirectory : System.getenv("PATH").split(File.pathSeparator)) {
|
||||
Path path = Paths.get(pathDirectory, executableName);
|
||||
if (path.toFile().isFile() && path.toFile().canExecute()) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean writeDesktopFile(String launchScriptPath, Path desktopFilePath) {
|
||||
try {
|
||||
TemplateFile tmpl = TemplateFile.fromResources("/files/jadx-gui.desktop.tmpl");
|
||||
tmpl.add("launchScriptPath", launchScriptPath);
|
||||
FileUtils.writeFile(desktopFilePath, tmpl.build());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to save .desktop file at: {}", desktopFilePath, e);
|
||||
return false;
|
||||
}
|
||||
LOG.debug("Wrote .desktop file to {}", desktopFilePath);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean writeLogoFile(String logoFile, Path logoPath) {
|
||||
try (InputStream is = DesktopEntryUtils.class.getResourceAsStream("/logos/" + logoFile)) {
|
||||
FileUtils.writeFile(logoPath, is);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to write logo file at: {}", logoPath, e);
|
||||
return false;
|
||||
}
|
||||
LOG.debug("Wrote logo file to: {}", logoPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static @Nullable String getLaunchScriptPath() {
|
||||
String launchScriptPath = System.getProperty("jadx.launchScript.path");
|
||||
if (launchScriptPath.isEmpty()) {
|
||||
LOG.error(
|
||||
"The jadx.launchScript.path property is not set. Please launch JADX with the bundled launch script or set it to the appropriate value yourself.");
|
||||
return null;
|
||||
}
|
||||
LOG.debug("JADX launch script path: {}", launchScriptPath);
|
||||
return launchScriptPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Name=JADX GUI
|
||||
Comment=Dex to Java decompiler
|
||||
Icon=jadx
|
||||
Exec={{launchScriptPath}} %f
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Categories=Development;Java;
|
||||
Keywords=Java;Decompiler;
|
||||
StartupWMClass=jadx-gui-JadxGUI
|
||||
@@ -24,6 +24,7 @@ menu.tools=Tools
|
||||
#menu.reset_cache=Reset code cache
|
||||
menu.deobfuscation=Deobfuskierung
|
||||
menu.log=Log-Anzeige
|
||||
#menu.create_desktop_entry=Create Desktop Entry
|
||||
menu.help=Hilfe
|
||||
menu.about=Über
|
||||
#menu.quark=Quark Engine
|
||||
@@ -113,6 +114,9 @@ message.indexingClassesSkipped=<html>Jadx hat nur noch wenig Speicherplatz. Dahe
|
||||
#message.enter_new_name=Enter new name
|
||||
#message.could_not_rename=Can't rename the file
|
||||
#message.confirm_remove_script=Do you really want to remove script?
|
||||
#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details).
|
||||
#message.desktop_entry_creation_success=Desktop entry created successfully!
|
||||
#message.success_title=Success
|
||||
|
||||
heapUsage.text=JADX-Speicherauslastung: %.2f GB von %.2f GB
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ menu.decompile_all=Decompile all classes
|
||||
menu.reset_cache=Reset code cache
|
||||
menu.deobfuscation=Deobfuscation
|
||||
menu.log=Log Viewer
|
||||
menu.create_desktop_entry=Create Desktop Entry
|
||||
menu.help=Help
|
||||
menu.about=About
|
||||
menu.quark=Quark Engine
|
||||
@@ -113,6 +114,9 @@ message.indexingClassesSkipped=<html>Jadx is running low on memory. Therefore %d
|
||||
message.enter_new_name=Enter new name
|
||||
message.could_not_rename=Can't rename the file
|
||||
message.confirm_remove_script=Do you really want to remove script?
|
||||
message.desktop_entry_creation_error=Failed to create desktop entry (check log for details).
|
||||
message.desktop_entry_creation_success=Desktop entry created successfully!
|
||||
message.success_title=Success
|
||||
|
||||
heapUsage.text=JADX memory usage: %.2f GB of %.2f GB
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ menu.tools=Herramientas
|
||||
#menu.reset_cache=Reset code cache
|
||||
menu.deobfuscation=Desofuscación
|
||||
menu.log=Visor log
|
||||
#menu.create_desktop_entry=Create Desktop Entry
|
||||
menu.help=Ayuda
|
||||
menu.about=Acerca de...
|
||||
#menu.quark=Quark Engine
|
||||
@@ -113,6 +114,9 @@ nav.forward=Adelante
|
||||
#message.enter_new_name=Enter new name
|
||||
#message.could_not_rename=Can't rename the file
|
||||
#message.confirm_remove_script=Do you really want to remove script?
|
||||
#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details).
|
||||
#message.desktop_entry_creation_success=Desktop entry created successfully!
|
||||
#message.success_title=Success
|
||||
|
||||
#heapUsage.text=JADX memory usage: %.2f GB of %.2f GB
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ menu.decompile_all=Deskompilasi semua kelas
|
||||
menu.reset_cache=Reset cache kode
|
||||
menu.deobfuscation=Deobfikasi
|
||||
menu.log=Pemantau Log
|
||||
#menu.create_desktop_entry=Create Desktop Entry
|
||||
menu.help=Bantuan
|
||||
menu.about=Tentang
|
||||
menu.quark=Mesin Quark
|
||||
@@ -113,6 +114,9 @@ message.indexingClassesSkipped=<html>JADX kekurangan memori. Oleh karena itu %d
|
||||
#message.enter_new_name=Enter new name
|
||||
#message.could_not_rename=Can't rename the file
|
||||
#message.confirm_remove_script=Do you really want to remove script?
|
||||
#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details).
|
||||
#message.desktop_entry_creation_success=Desktop entry created successfully!
|
||||
#message.success_title=Success
|
||||
|
||||
heapUsage.text=Penggunaan memori JADX: %.2f GB dari %.2f GB
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ menu.tools=도구
|
||||
#menu.reset_cache=Reset code cache
|
||||
menu.deobfuscation=난독화 해제
|
||||
menu.log=로그 뷰어
|
||||
#menu.create_desktop_entry=Create Desktop Entry
|
||||
menu.help=도움말
|
||||
menu.about=정보
|
||||
#menu.quark=Quark Engine
|
||||
@@ -113,6 +114,9 @@ message.indexingClassesSkipped=<html>Jadx의 메모리가 부족합니다. 따
|
||||
#message.enter_new_name=Enter new name
|
||||
#message.could_not_rename=Can't rename the file
|
||||
#message.confirm_remove_script=Do you really want to remove script?
|
||||
#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details).
|
||||
#message.desktop_entry_creation_success=Desktop entry created successfully!
|
||||
#message.success_title=Success
|
||||
|
||||
heapUsage.text=JADX 메모리 사용량 : %.2f GB / %.2f GB
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ menu.tools=Ferramentas
|
||||
#menu.reset_cache=Reset code cache
|
||||
menu.deobfuscation=Desofuscar
|
||||
menu.log=Visualizador de log
|
||||
#menu.create_desktop_entry=Create Desktop Entry
|
||||
menu.help=Ajuda
|
||||
menu.about=Sobre
|
||||
#menu.quark=Quark Engine
|
||||
@@ -113,6 +114,9 @@ message.indexingClassesSkipped=<html>Jadx está rodando com pouca memória. Por
|
||||
#message.enter_new_name=Enter new name
|
||||
#message.could_not_rename=Can't rename the file
|
||||
#message.confirm_remove_script=Do you really want to remove script?
|
||||
#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details).
|
||||
#message.desktop_entry_creation_success=Desktop entry created successfully!
|
||||
#message.success_title=Success
|
||||
|
||||
heapUsage.text=Uso de memória do JADX: %.2f GB of %.2f GB
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ menu.decompile_all=Декомпилировать все
|
||||
menu.reset_cache=Сбросить кэш
|
||||
menu.deobfuscation=Деобфускация
|
||||
menu.log=Просмотр логов
|
||||
#menu.create_desktop_entry=Create Desktop Entry
|
||||
menu.help=Помощь
|
||||
menu.about=О программе
|
||||
#menu.quark=Quark Engine
|
||||
@@ -113,6 +114,9 @@ message.indexingClassesSkipped=<html>JaDX запущен с малым коли
|
||||
#message.enter_new_name=Enter new name
|
||||
#message.could_not_rename=Can't rename the file
|
||||
#message.confirm_remove_script=Do you really want to remove script?
|
||||
#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details).
|
||||
#message.desktop_entry_creation_success=Desktop entry created successfully!
|
||||
#message.success_title=Success
|
||||
|
||||
heapUsage.text=JADX использует: %.2f ГБ из %.2f ГБ
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ menu.decompile_all=反编译所有类
|
||||
menu.reset_cache=重置代码缓存
|
||||
menu.deobfuscation=反混淆
|
||||
menu.log=日志查看器
|
||||
#menu.create_desktop_entry=Create Desktop Entry
|
||||
menu.help=帮助
|
||||
menu.about=关于
|
||||
menu.quark=Quark 引擎
|
||||
@@ -113,6 +114,9 @@ message.indexingClassesSkipped=<html>Jadx 的内存不足。因此,%d 个类
|
||||
message.enter_new_name=输入新名称
|
||||
message.could_not_rename=无法重命名文件
|
||||
message.confirm_remove_script=您真的要删除脚本吗?
|
||||
#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details).
|
||||
#message.desktop_entry_creation_success=Desktop entry created successfully!
|
||||
#message.success_title=Success
|
||||
|
||||
heapUsage.text=JADX 内存使用率:%.2f GB / %.2f GB
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ menu.decompile_all=反編譯所有類別
|
||||
menu.reset_cache=重設程式碼快取
|
||||
menu.deobfuscation=去模糊化
|
||||
menu.log=記錄檔檢視器
|
||||
#menu.create_desktop_entry=Create Desktop Entry
|
||||
menu.help=幫助
|
||||
menu.about=關於
|
||||
menu.quark= Quark 引擎
|
||||
@@ -113,6 +114,9 @@ message.indexingClassesSkipped=<html>Jadx 的記憶體不足。故 %d 個類別
|
||||
#message.enter_new_name=Enter new name
|
||||
#message.could_not_rename=Can't rename the file
|
||||
#message.confirm_remove_script=Do you really want to remove script?
|
||||
#message.desktop_entry_creation_error=Failed to create desktop entry (check log for details).
|
||||
#message.desktop_entry_creation_success=Desktop entry created successfully!
|
||||
#message.success_title=Success
|
||||
|
||||
heapUsage.text=JADX 記憶體使用率:%.2f GB / %.2f GB
|
||||
|
||||
|
||||
Reference in New Issue
Block a user