Skip to content

Commit

Permalink
feat(gui): add button to install desktop file directly from jadx-gui …
Browse files Browse the repository at this point in the history
…(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
  • Loading branch information
Hidoni authored Feb 1, 2025
1 parent b186040 commit afdd2d8
Show file tree
Hide file tree
Showing 16 changed files with 244 additions and 3 deletions.
3 changes: 2 additions & 1 deletion contrib/jadx-gui.desktop
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions jadx-core/src/main/java/jadx/core/utils/files/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,16 @@ public static Path createTempFileNoDelete(String suffix) {
}
}

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) {
Expand Down Expand Up @@ -266,6 +276,11 @@ public static void writeFile(Path file, String data) throws IOException {
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);
}
Expand Down
12 changes: 10 additions & 2 deletions jadx-gui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

Expand Down
15 changes: 15 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,13 @@
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;
Expand Down Expand Up @@ -1186,6 +1188,9 @@ private void initMenuAndToolbar() {
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
Expand Down Expand Up @@ -1724,6 +1729,16 @@ public void addToPluginsMenu(Action item) {
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;
}
Expand Down
1 change: 1 addition & 0 deletions jadx-gui/src/main/java/jadx/gui/ui/action/ActionModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
155 changes: 155 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/utils/DesktopEntryUtils.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
10 changes: 10 additions & 0 deletions jadx-gui/src/main/resources/files/jadx-gui.desktop.tmpl
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions jadx-gui/src/main/resources/i18n/Messages_en_US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions jadx-gui/src/main/resources/i18n/Messages_es_ES.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions jadx-gui/src/main/resources/i18n/Messages_id_ID.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
Loading

0 comments on commit afdd2d8

Please sign in to comment.