diff --git a/artwork/j3270_icon.svg b/artwork/j3270_icon.svg
new file mode 100644
index 0000000..a332e3a
--- /dev/null
+++ b/artwork/j3270_icon.svg
@@ -0,0 +1,146 @@
+
+
diff --git a/artwork/lib3270j.svg b/artwork/lib3270j.svg
new file mode 100644
index 0000000..6aa51a3
--- /dev/null
+++ b/artwork/lib3270j.svg
@@ -0,0 +1,146 @@
+
+
diff --git a/j3270/src/main/java/haus/nightmare/j3270/J3270App.java b/j3270/src/main/java/haus/nightmare/j3270/J3270App.java
index 16ef191..9f474c7 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/J3270App.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/J3270App.java
@@ -60,6 +60,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
super("j3270 — Java TN3270 Terminal Emulator");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(Color.BLACK);
+ AppIcon.applyTo(this);
buildUI();
buildMenuBar();
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ft/FileTransferDialog.java b/j3270/src/main/java/haus/nightmare/j3270/ft/FileTransferDialog.java
index 529889d..8416355 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/ft/FileTransferDialog.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/ft/FileTransferDialog.java
@@ -1,5 +1,6 @@
package haus.nightmare.j3270.ft;
+import haus.nightmare.j3270.ui.AppIcon;
import haus.nightmare.j3270.ui.HostDirectoryDialog;
import haus.nightmare.j3270.ui.ThemeManager;
import haus.nightmare.lib3270j.ft.FTConfig;
@@ -92,6 +93,7 @@ public class FileTransferDialog extends JDialog {
super(owner, "File Transfer (IND$FILE)", false);
this.owner = owner;
this.coordinator = coordinator;
+ AppIcon.applyTo(this);
buildUI();
pack();
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/AppIcon.java b/j3270/src/main/java/haus/nightmare/j3270/ui/AppIcon.java
new file mode 100644
index 0000000..29495a0
--- /dev/null
+++ b/j3270/src/main/java/haus/nightmare/j3270/ui/AppIcon.java
@@ -0,0 +1,138 @@
+package haus.nightmare.j3270.ui;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Utility for loading and applying application window and taskbar icons.
+ * Loads multi-resolution lib3270j icons from resources.
+ */
+public final class AppIcon {
+
+ private static final Logger log = Logger.getLogger(AppIcon.class.getName());
+
+ private static final int[] ICON_SIZES = {16, 24, 32, 48, 64, 128, 256, 512};
+ private static List cachedIcons = null;
+ private static Image defaultIcon = null;
+
+ private AppIcon() {}
+
+ /**
+ * Retrieves the list of available application icon images in various resolutions.
+ *
+ * @return an unmodifiable list of Image objects, or empty list if none could be loaded.
+ */
+ public static synchronized List getAppIcons() {
+ if (cachedIcons == null) {
+ loadIcons();
+ }
+ return cachedIcons;
+ }
+
+ /**
+ * Retrieves the default application icon image (256x256 or best available).
+ *
+ * @return the default Image, or null if no icon is available.
+ */
+ public static synchronized Image getDefaultIcon() {
+ if (cachedIcons == null) {
+ loadIcons();
+ }
+ return defaultIcon;
+ }
+
+ /**
+ * Applies the application icons to a target Window (JFrame, JDialog, etc.)
+ * and updates the desktop Taskbar icon if supported.
+ *
+ * @param window the Window to set icon images for.
+ */
+ public static void applyTo(Window window) {
+ if (GraphicsEnvironment.isHeadless()) {
+ return;
+ }
+
+ List icons = getAppIcons();
+ if (icons.isEmpty()) {
+ return;
+ }
+
+ if (window != null) {
+ window.setIconImages(icons);
+ }
+
+ // Apply to OS Taskbar / Dock if supported (Java 9+)
+ try {
+ if (Taskbar.isTaskbarSupported()) {
+ Taskbar taskbar = Taskbar.getTaskbar();
+ if (taskbar.isSupported(Taskbar.Feature.ICON_IMAGE)) {
+ Image bestIcon = defaultIcon != null ? defaultIcon : icons.get(icons.size() - 1);
+ taskbar.setIconImage(bestIcon);
+ }
+ }
+ } catch (Throwable t) {
+ log.log(Level.FINE, "Taskbar icon could not be set: {0}", t.getMessage());
+ }
+ }
+
+ private static void loadIcons() {
+ List icons = new ArrayList<>();
+
+ for (int size : ICON_SIZES) {
+ String path = "/icons/lib3270j_" + size + ".png";
+ Image img = loadImage(path);
+ if (img != null) {
+ icons.add(img);
+ }
+ }
+
+ if (icons.isEmpty()) {
+ Image img = loadImage("/icons/lib3270j.png");
+ if (img != null) {
+ icons.add(img);
+ }
+ }
+
+ if (!icons.isEmpty()) {
+ for (Image img : icons) {
+ if (img.getWidth(null) == 256) {
+ defaultIcon = img;
+ break;
+ }
+ }
+ if (defaultIcon == null) {
+ defaultIcon = icons.get(icons.size() - 1);
+ }
+ }
+
+ cachedIcons = Collections.unmodifiableList(icons);
+ }
+
+ private static Image loadImage(String resourcePath) {
+ try {
+ URL url = AppIcon.class.getResource(resourcePath);
+ if (url != null) {
+ BufferedImage img = ImageIO.read(url);
+ if (img != null) {
+ return img;
+ }
+ }
+ try (InputStream in = AppIcon.class.getResourceAsStream(resourcePath)) {
+ if (in != null) {
+ return ImageIO.read(in);
+ }
+ }
+ } catch (Exception e) {
+ log.log(Level.FINE, "Failed to load icon from " + resourcePath, e);
+ }
+ return null;
+ }
+}
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/ConnectDialog.java b/j3270/src/main/java/haus/nightmare/j3270/ui/ConnectDialog.java
index e3a385c..8e687db 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/ui/ConnectDialog.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/ui/ConnectDialog.java
@@ -31,6 +31,7 @@ public class ConnectDialog extends JDialog {
public ConnectDialog(Frame parent) {
super(parent, "Connect to Host", true);
+ AppIcon.applyTo(this);
buildUI();
pack();
setLocationRelativeTo(parent);
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/FieldInspectorDialog.java b/j3270/src/main/java/haus/nightmare/j3270/ui/FieldInspectorDialog.java
index 4de1862..0c6c795 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/ui/FieldInspectorDialog.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/ui/FieldInspectorDialog.java
@@ -32,6 +32,7 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
super(parent, "3270 Presentation Space Field Inspector", false);
this.client = client;
this.terminalPanel = terminalPanel;
+ AppIcon.applyTo(this);
buildUI();
setSize(850, 420);
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/FindDialog.java b/j3270/src/main/java/haus/nightmare/j3270/ui/FindDialog.java
index 0668f33..7c5f572 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/ui/FindDialog.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/ui/FindDialog.java
@@ -32,6 +32,7 @@ public class FindDialog extends JDialog {
super(parent, "Find on Screen", false);
this.client = client;
this.terminalPanel = terminalPanel;
+ AppIcon.applyTo(this);
buildUI();
pack();
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/HostDirectoryDialog.java b/j3270/src/main/java/haus/nightmare/j3270/ui/HostDirectoryDialog.java
index 2f462d0..73b0e67 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/ui/HostDirectoryDialog.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/ui/HostDirectoryDialog.java
@@ -40,6 +40,7 @@ public class HostDirectoryDialog extends JDialog {
super(parent, "Host Catalog & Directory Browser", true);
this.client = client;
this.initialHostType = hostType;
+ AppIcon.applyTo(this);
buildUI(initialQuery);
setSize(700, 420);
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/PrinterSessionDialog.java b/j3270/src/main/java/haus/nightmare/j3270/ui/PrinterSessionDialog.java
index 8180ed5..f026540 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/ui/PrinterSessionDialog.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/ui/PrinterSessionDialog.java
@@ -48,6 +48,7 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
public PrinterSessionDialog(Frame parent, String defaultHost, int defaultPort, boolean defaultTls) {
super(parent, "IBM 3287 Printer Session Manager", false);
this.parent = parent;
+ AppIcon.applyTo(this);
config = new PrinterConfig(defaultHost, defaultPort);
config.setUseTls(defaultTls);
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/ScriptDialog.java b/j3270/src/main/java/haus/nightmare/j3270/ui/ScriptDialog.java
index ac95f63..a23edab 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/ui/ScriptDialog.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/ui/ScriptDialog.java
@@ -25,6 +25,7 @@ public class ScriptDialog extends JDialog {
super(parent, "Run Keystrokes / ECL Script", false);
this.client = client;
this.terminalPanel = terminalPanel;
+ AppIcon.applyTo(this);
buildUI();
setSize(600, 480);
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/SettingsDialog.java b/j3270/src/main/java/haus/nightmare/j3270/ui/SettingsDialog.java
index 852acfd..0cefcb3 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/ui/SettingsDialog.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/ui/SettingsDialog.java
@@ -62,6 +62,7 @@ public class SettingsDialog extends JDialog {
public SettingsDialog(J3270App parent) {
super(parent, "Settings", true);
this.parentApp = parent;
+ AppIcon.applyTo(this);
initComponents();
setSize(600, 520);
diff --git a/j3270/src/main/java/haus/nightmare/j3270/ui/UntrustedCertificateDialog.java b/j3270/src/main/java/haus/nightmare/j3270/ui/UntrustedCertificateDialog.java
index 5f909ca..15a17b8 100644
--- a/j3270/src/main/java/haus/nightmare/j3270/ui/UntrustedCertificateDialog.java
+++ b/j3270/src/main/java/haus/nightmare/j3270/ui/UntrustedCertificateDialog.java
@@ -17,6 +17,7 @@ public class UntrustedCertificateDialog extends JDialog {
public UntrustedCertificateDialog(Frame parent, String host, int port, X509Certificate[] chain, CertificateException exception) {
super(parent, "Untrusted SSL/TLS Certificate", true);
+ AppIcon.applyTo(this);
buildUI(host, port, chain, exception);
pack();
setLocationRelativeTo(parent);
diff --git a/j3270/src/main/resources/icons/j3270_icon.svg b/j3270/src/main/resources/icons/j3270_icon.svg
new file mode 100644
index 0000000..a332e3a
--- /dev/null
+++ b/j3270/src/main/resources/icons/j3270_icon.svg
@@ -0,0 +1,146 @@
+
+
diff --git a/j3270/src/main/resources/icons/lib3270j.png b/j3270/src/main/resources/icons/lib3270j.png
new file mode 100644
index 0000000..c0cd2c5
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j.png differ
diff --git a/j3270/src/main/resources/icons/lib3270j.svg b/j3270/src/main/resources/icons/lib3270j.svg
new file mode 100644
index 0000000..6aa51a3
--- /dev/null
+++ b/j3270/src/main/resources/icons/lib3270j.svg
@@ -0,0 +1,146 @@
+
+
diff --git a/j3270/src/main/resources/icons/lib3270j_128.png b/j3270/src/main/resources/icons/lib3270j_128.png
new file mode 100644
index 0000000..8696639
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j_128.png differ
diff --git a/j3270/src/main/resources/icons/lib3270j_16.png b/j3270/src/main/resources/icons/lib3270j_16.png
new file mode 100644
index 0000000..49b1f9b
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j_16.png differ
diff --git a/j3270/src/main/resources/icons/lib3270j_24.png b/j3270/src/main/resources/icons/lib3270j_24.png
new file mode 100644
index 0000000..3234f7d
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j_24.png differ
diff --git a/j3270/src/main/resources/icons/lib3270j_256.png b/j3270/src/main/resources/icons/lib3270j_256.png
new file mode 100644
index 0000000..c0cd2c5
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j_256.png differ
diff --git a/j3270/src/main/resources/icons/lib3270j_32.png b/j3270/src/main/resources/icons/lib3270j_32.png
new file mode 100644
index 0000000..45c6133
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j_32.png differ
diff --git a/j3270/src/main/resources/icons/lib3270j_48.png b/j3270/src/main/resources/icons/lib3270j_48.png
new file mode 100644
index 0000000..87e9795
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j_48.png differ
diff --git a/j3270/src/main/resources/icons/lib3270j_512.png b/j3270/src/main/resources/icons/lib3270j_512.png
new file mode 100644
index 0000000..88c0456
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j_512.png differ
diff --git a/j3270/src/main/resources/icons/lib3270j_64.png b/j3270/src/main/resources/icons/lib3270j_64.png
new file mode 100644
index 0000000..3567d8e
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j_64.png differ
diff --git a/j3270/src/main/resources/icons/lib3270j_wide.png b/j3270/src/main/resources/icons/lib3270j_wide.png
new file mode 100644
index 0000000..63618db
Binary files /dev/null and b/j3270/src/main/resources/icons/lib3270j_wide.png differ
diff --git a/j3270/src/test/java/haus/nightmare/j3270/AppIconTest.java b/j3270/src/test/java/haus/nightmare/j3270/AppIconTest.java
new file mode 100644
index 0000000..0c9d730
--- /dev/null
+++ b/j3270/src/test/java/haus/nightmare/j3270/AppIconTest.java
@@ -0,0 +1,57 @@
+package haus.nightmare.j3270;
+
+import haus.nightmare.j3270.ui.AppIcon;
+import org.junit.jupiter.api.Test;
+
+import javax.swing.*;
+import java.awt.*;
+import java.net.URL;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class AppIconTest {
+
+ @Test
+ public void testIconsAreLoaded() {
+ List icons = AppIcon.getAppIcons();
+ assertNotNull(icons, "Icons list must not be null");
+ assertFalse(icons.isEmpty(), "At least one icon image must be loaded");
+
+ // Verify standard sizes are present
+ int[] expectedSizes = {16, 24, 32, 48, 64, 128, 256, 512};
+ for (int expectedSize : expectedSizes) {
+ boolean found = icons.stream().anyMatch(img -> img.getWidth(null) == expectedSize && img.getHeight(null) == expectedSize);
+ assertTrue(found, "Expected icon of size " + expectedSize + "x" + expectedSize + " was not found");
+ }
+ }
+
+ @Test
+ public void testDefaultIcon() {
+ Image defaultIcon = AppIcon.getDefaultIcon();
+ assertNotNull(defaultIcon, "Default icon must not be null");
+ assertEquals(256, defaultIcon.getWidth(null), "Default icon width should be 256");
+ assertEquals(256, defaultIcon.getHeight(null), "Default icon height should be 256");
+ }
+
+ @Test
+ public void testApplyToInHeadless() {
+ // Must execute without exception even in headless environment
+ assertDoesNotThrow(() -> AppIcon.applyTo(null));
+
+ if (!GraphicsEnvironment.isHeadless()) {
+ JFrame frame = new JFrame();
+ assertDoesNotThrow(() -> AppIcon.applyTo(frame));
+ frame.dispose();
+ }
+ }
+
+ @Test
+ public void testSvgResourcesExist() {
+ URL svgUrl = getClass().getResource("/icons/lib3270j.svg");
+ assertNotNull(svgUrl, "lib3270j.svg must exist in /icons/");
+
+ URL iconSvgUrl = getClass().getResource("/icons/j3270_icon.svg");
+ assertNotNull(iconSvgUrl, "j3270_icon.svg must exist in /icons/");
+ }
+}