diff --git a/jdm-core/src/main/java/jdiskmark/App.java b/jdm-core/src/main/java/jdiskmark/App.java index be290d5..440c8ee 100644 --- a/jdm-core/src/main/java/jdiskmark/App.java +++ b/jdm-core/src/main/java/jdiskmark/App.java @@ -1,9 +1,5 @@ package jdiskmark; -import static jdiskmark.Benchmark.BenchmarkType; -import static jdiskmark.Benchmark.BlockSequence; -import static jdiskmark.Benchmark.IOMode.READ; -import static jdiskmark.Benchmark.IOMode.WRITE; import static jdiskmark.DriveAccessChecker.validateTargetDirectory; import picocli.CommandLine; @@ -109,6 +105,50 @@ public String toString() { } } + /** + * Branding icon variants for the application window, taskbar, and installer. + * Change {@link #activeIcon} to switch the icon across all display contexts. + */ + public enum AppIcon { + BETA("/icons/icon-jdm-beta.png"), + /** Custom JDiskMark turtle logo — the default project brand. */ + TURTLE("/icons/icon-jdm-turtle.png"), + /** Duke, the BSD-licensed Java mascot from the OpenJDK project. */ + DUKE("/icons/icon-duke.png"); + + public final String resourcePath; + + AppIcon(String resourcePath) { + this.resourcePath = resourcePath; + } + + /** + * Load this icon as an ImageIcon, or {@code null} if the resource + * is not found on the classpath. + */ + public javax.swing.ImageIcon load() { + java.io.InputStream is = App.class.getResourceAsStream(resourcePath); + if (is == null) { + java.util.logging.Logger.getLogger(App.class.getName()).warning( + "Icon resource not found: " + resourcePath); + return null; + } + try (is) { + return new javax.swing.ImageIcon(is.readAllBytes()); + } catch (java.io.IOException e) { + java.util.logging.Logger.getLogger(App.class.getName()).log( + java.util.logging.Level.WARNING, "Could not load icon: " + resourcePath, e); + return null; + } + } + } + + /** + * Active branding icon — change this single line to switch the icon + * used for the window title bar, taskbar, and About dialog. + */ + public static AppIcon activeIcon = AppIcon.TURTLE; + // application mode public static Mode mode = Mode.CLI; // elevated priviledges diff --git a/jdm-core/src/main/java/jdiskmark/Gui.java b/jdm-core/src/main/java/jdiskmark/Gui.java index 1646a0b..e647f37 100644 --- a/jdm-core/src/main/java/jdiskmark/Gui.java +++ b/jdm-core/src/main/java/jdiskmark/Gui.java @@ -109,6 +109,9 @@ public static void configureDarkLaf() { } else if (App.os.contains("Linux")) { UIManager.setLookAndFeel(new FlatDarkLaf()); } + // Enable FlatLaf custom window decorations (unified title bar + menu bar) + javax.swing.JFrame.setDefaultLookAndFeelDecorated(true); + javax.swing.JDialog.setDefaultLookAndFeelDecorated(true); } catch (UnsupportedLookAndFeelException e) { // /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. @@ -126,6 +129,8 @@ public static void configureDarkLaf() { public static void configureDarculaLaf() { try { UIManager.setLookAndFeel(new FlatDarculaLaf()); + javax.swing.JFrame.setDefaultLookAndFeelDecorated(true); + javax.swing.JDialog.setDefaultLookAndFeelDecorated(true); } catch (UnsupportedLookAndFeelException e) { // /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. @@ -149,6 +154,8 @@ public static void configureLightLaf() { } else if (App.os.contains("Linux")) { UIManager.setLookAndFeel(new FlatLightLaf()); } + javax.swing.JFrame.setDefaultLookAndFeelDecorated(true); + javax.swing.JDialog.setDefaultLookAndFeelDecorated(true); } catch (UnsupportedLookAndFeelException e) { // /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. @@ -228,6 +235,17 @@ public static void init() { } mainFrame = new MainFrame(); + + // Embed the menu bar into the FlatLaf custom title bar (VS Code style) + mainFrame.getRootPane().putClientProperty( + com.formdev.flatlaf.FlatClientProperties.MENU_BAR_EMBEDDED, true); + + // Apply branding icon to the window title bar and taskbar + javax.swing.ImageIcon icon = App.activeIcon.load(); + if (icon != null) { + mainFrame.setIconImage(icon.getImage()); + } + if (runPanel != null) { runPanel.hideFirstColumn(); } diff --git a/jdm-core/src/main/java/jdiskmark/MainFrame.java b/jdm-core/src/main/java/jdiskmark/MainFrame.java index e0dcfef..08e30b6 100644 --- a/jdm-core/src/main/java/jdiskmark/MainFrame.java +++ b/jdm-core/src/main/java/jdiskmark/MainFrame.java @@ -839,8 +839,17 @@ private void exitMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN- }//GEN-LAST:event_exitMenuItemActionPerformed private void aboutMenuItemActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_aboutMenuItemActionPerformed - JOptionPane.showMessageDialog(Gui.mainFrame, - "JDiskMark " + App.VERSION, "About...", JOptionPane.PLAIN_MESSAGE); + javax.swing.ImageIcon rawIcon = App.activeIcon.load(); + javax.swing.ImageIcon icon = rawIcon; + if (rawIcon != null) { + int w = rawIcon.getIconWidth() / 2; + int h = rawIcon.getIconHeight() / 2; + java.awt.Image scaled = rawIcon.getImage() + .getScaledInstance(w, h, java.awt.Image.SCALE_SMOOTH); + icon = new javax.swing.ImageIcon(scaled); + } + JOptionPane.showMessageDialog(Gui.mainFrame, + "JDiskMark " + App.VERSION, "About...", JOptionPane.PLAIN_MESSAGE, icon); }//GEN-LAST:event_aboutMenuItemActionPerformed private void openLocButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openLocButtonActionPerformed diff --git a/jdm-core/src/main/resources/icons/icon-duke.png b/jdm-core/src/main/resources/icons/icon-duke.png new file mode 100644 index 0000000..62b6b5c Binary files /dev/null and b/jdm-core/src/main/resources/icons/icon-duke.png differ diff --git a/jdm-core/src/main/resources/icons/icon-jdm-beta.png b/jdm-core/src/main/resources/icons/icon-jdm-beta.png new file mode 100644 index 0000000..4417127 Binary files /dev/null and b/jdm-core/src/main/resources/icons/icon-jdm-beta.png differ diff --git a/jdm-core/src/main/resources/icons/icon-jdm-turtle-bw.png b/jdm-core/src/main/resources/icons/icon-jdm-turtle-bw.png new file mode 100644 index 0000000..edf9bdb Binary files /dev/null and b/jdm-core/src/main/resources/icons/icon-jdm-turtle-bw.png differ diff --git a/jdm-core/src/main/resources/icons/icon-jdm-turtle.png b/jdm-core/src/main/resources/icons/icon-jdm-turtle.png new file mode 100644 index 0000000..05a7453 Binary files /dev/null and b/jdm-core/src/main/resources/icons/icon-jdm-turtle.png differ diff --git a/jdm-core/src/test/java/jdiskmark/AppTest.java b/jdm-core/src/test/java/jdiskmark/AppTest.java index 734bcc5..7472799 100644 --- a/jdm-core/src/test/java/jdiskmark/AppTest.java +++ b/jdm-core/src/test/java/jdiskmark/AppTest.java @@ -1,6 +1,8 @@ package jdiskmark; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import static org.junit.jupiter.api.Assertions.*; /** @@ -23,4 +25,16 @@ void sharePortal_onStartup_isFalse() { assertFalse(App.sharePortal, "sharePortal must default to false — network activity requires explicit user opt-in each session"); } + + /** + * Every AppIcon enum entry must resolve to an actual classpath resource. + * This guards against icon renames or path typos that would cause the + * About dialog (and taskbar/title-bar) to silently display no icon. + */ + @ParameterizedTest + @EnumSource(App.AppIcon.class) + void appIcon_load_isNonNull(App.AppIcon icon) { + assertNotNull(icon.load(), + "Icon resource not found on classpath: " + icon.resourcePath); + } } diff --git a/jdm-dist/jdm-deb-slim/pom.xml b/jdm-dist/jdm-deb-slim/pom.xml index bc2ceeb..e1dbed9 100644 --- a/jdm-dist/jdm-deb-slim/pom.xml +++ b/jdm-dist/jdm-deb-slim/pom.xml @@ -101,6 +101,30 @@ root + + + ${project.basedir}/../../jdm-core/src/main/resources/icons/icon-jdm-turtle.png + file + /usr/share/pixmaps/jdiskmark.png + + perm + 644 + root + root + + + + + ${project.basedir}/src/deb/share/jdiskmark.desktop + file + + perm + /usr/share/applications + 644 + root + root + + diff --git a/jdm-dist/jdm-deb-slim/src/deb/share/jdiskmark.desktop b/jdm-dist/jdm-deb-slim/src/deb/share/jdiskmark.desktop new file mode 100644 index 0000000..ba5508c --- /dev/null +++ b/jdm-dist/jdm-deb-slim/src/deb/share/jdiskmark.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Type=Application +Version=1.0 +Name=JDiskMark +GenericName=Disk Benchmark +Comment=Java disk benchmark utility for read/write performance testing +Exec=/usr/bin/jdiskmark +Icon=jdiskmark +Terminal=false +Categories=Utility;System; +Keywords=disk;benchmark;performance;storage;speed; +StartupWMClass=jdiskmark-App diff --git a/jdm-dist/jdm-deb/pom.xml b/jdm-dist/jdm-deb/pom.xml index 30baf26..e0bc247 100644 --- a/jdm-dist/jdm-deb/pom.xml +++ b/jdm-dist/jdm-deb/pom.xml @@ -50,9 +50,55 @@ --java-options${jvm.run.options} --linux-shortcut --linux-menu-groupUtility + --icon${project.basedir}/../../jdm-core/src/main/resources/icons/icon-jdm-turtle.png + --resource-dir${project.basedir}/src/main/jpackage-resources + + + + patch-deb-desktop + verify + + exec + + + bash + + -c + &2 + printf ' %s\n' "${DEBS[@]}" >&2 + exit 1 +fi +DEB="${DEBS[0]}" +WORK="${project.build.directory}/deb-repack" +echo "[patch] Patching desktop entry in $DEB" +rm -rf "$WORK" && mkdir -p "$WORK/pkg" +dpkg-deb -R "$DEB" "$WORK/pkg" +DESKTOP=$(find "$WORK/pkg/opt" -name "*.desktop" | grep -v runtime | head -1) +if [ -n "$DESKTOP" ]; then + sed -i 's|^Name=jdiskmark$|Name=JDiskMark|' "$DESKTOP" + sed -i 's|^Categories=\(.*\)|GenericName=Disk Benchmark\nCategories=\1\nKeywords=disk;benchmark;performance;storage;speed;\nStartupWMClass=jdiskmark-App|' "$DESKTOP" + echo "[patch] Result ($(basename $DESKTOP)):" + cat "$DESKTOP" +fi +dpkg-deb -b "$WORK/pkg" "$DEB" +echo "[patch] Done." + ]]> + + + + diff --git a/jdm-dist/jdm-deb/src/main/jpackage-resources/template.desktop b/jdm-dist/jdm-deb/src/main/jpackage-resources/template.desktop new file mode 100644 index 0000000..d37986a --- /dev/null +++ b/jdm-dist/jdm-deb/src/main/jpackage-resources/template.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Name=APPLICATION_NAME +GenericName=Disk Benchmark +Comment=APPLICATION_DESCRIPTION +Exec=APPLICATION_LAUNCHER +Icon=APPLICATION_ICON +Terminal=false +Type=Application +Categories=DEPLOY_BUNDLE_CATEGORY +Keywords=disk;benchmark;performance;storage;speed; +StartupWMClass=jdiskmark-App +DESKTOP_MIMES diff --git a/jdm-dist/jdm-deb/src/main/jpackage-resources/template.postinst b/jdm-dist/jdm-deb/src/main/jpackage-resources/template.postinst new file mode 100644 index 0000000..c5e2e40 --- /dev/null +++ b/jdm-dist/jdm-deb/src/main/jpackage-resources/template.postinst @@ -0,0 +1,38 @@ +#!/bin/sh +# postinst script for APPLICATION_PACKAGE +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-configure' `in-favour' +# `removing' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +package_type=deb +LAUNCHER_AS_SERVICE_SCRIPTS + +case "$1" in + configure) +DESKTOP_COMMANDS_INSTALL +LAUNCHER_AS_SERVICE_COMMANDS_INSTALL + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +exit 0