4 Commits

Author SHA1 Message Date
rudi 7892229a2c Add icon
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 32s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 1m9s
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 1m12s
2026-09-14 13:01:41 +00:00
rudi 14bf7ba4b1 Add bugfixes from testing
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 37s
Release j3270 / Build & Publish Release (push) Successful in 43s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 1m14s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 1m12s
2026-09-12 17:21:21 -04:00
rudi bdfe6eec2a IMON GRAPHIC!
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 31s
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 47s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 1m9s
Release j3270 / Build & Publish Release (push) Successful in 42s
2026-09-12 15:33:12 +00:00
rudi c28c097e25 Fix bugs reported in Discord
Build and Test j3270 / Build JAR & Run Tests (Java 17) (push) Successful in 33s
Build and Test j3270 / Build JAR & Run Tests (Java 11) (push) Successful in 42s
Build and Test j3270 / Build JAR & Run Tests (Java 21) (push) Successful in 1m17s
2026-09-12 14:58:48 +00:00
36 changed files with 1369 additions and 116 deletions
+146
View File
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="124 738 320 320"
width="320"
height="320"
xml:space="preserve"
id="svg_j3270_icon"
version="1.1">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<!-- Pure black background (#000000) for classic 3270 CRT terminal look -->
<rect x="124" y="738" width="320" height="320" fill="#000000" />
<g transform="matrix(1.3333333,0,0,-1.3333333,0,1122.6667)" id="g10">
<g transform="scale(0.1)" id="g12">
<g id="g14">
<!-- Green Terminal Screen Frame -->
<path
id="path28"
style="fill:none;stroke:#00ff00;stroke-width:3.75079012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="M 1177.27,2220.71 V 1142.45 H 3084.31 V 2220.71 H 1177.27" />
<!-- Blue Wave / Exponential Curve -->
<path
id="path30"
style="fill:none;stroke:#0000ff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1172.41,1137.58 79.1,-1.22 -0.21,-0.38 c 1.08,0 2.18,0 3.27,0 762.95,0 1464.86,417.88 1829.53,1089.21" />
<!-- Red Circular Arc -->
<path
id="path32"
style="fill:none;stroke:#ff0000;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2593.86,1620.73 -1.22,-28 -0.08,-0.08 c 0.43,-250.15 202.81,-452.72 452.3,-452.72 0.14,0 0.28,0 0.42,0 l 43.9,2.52" />
<!-- Yellow Smiley Head -->
<path
id="path34"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2976,1363.94 v 0.08 c 44.73,34.29 71.21,87.25 71.8,143.61 l 0.01,-0.15 c -0.06,97.48 -79.11,176.47 -176.59,176.47 -0.36,0 -0.73,0 -1.09,0 l -0.07,0.03 c -0.25,0 -0.51,0 -0.75,0 -37.38,0 -73.79,-11.94 -103.92,-34.08 l 0.02,-0.03 c -44.38,-34.04 -70.82,-86.47 -71.8,-142.39 l 0.06,0.04 c 0.54,-97.43 79.04,-176.47 176.46,-177.69 l 0.08,0.02 c 0.64,-0.01 1.29,-0.01 1.92,-0.01 37.4,0 73.82,11.94 103.96,34.08" />
<!-- 'l' -->
<path
id="path36"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
<path
id="path38"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
<!-- 'i' -->
<path
id="path40"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
<path
id="path42"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
<!-- 'b' -->
<path
id="path44"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
<path
id="path46"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
<!-- '3' -->
<path
id="path48"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
<path
id="path50"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
<!-- '2' -->
<path
id="path52"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
<path
id="path54"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
<!-- '7' -->
<path
id="path56"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
<path
id="path58"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
<!-- '0' -->
<path
id="path60"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
<path
id="path62"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
<!-- 'j' -->
<path
id="path64"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
<path
id="path66"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
<!-- Smile -->
<path
id="path68"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2778.85,1478.34 0.04,-0.02 c 21.03,-31.91 56.69,-51.12 94.91,-51.12 0.41,0 0.83,0 1.24,0.01 l -0.02,0.07 c 43.88,0.19 84.11,24.51 104.66,63.28" />
<!-- Left Eye (cyan) -->
<path
id="path70"
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2820.22,1595.17 0.01,0.01 c -14.62,-1.91 -25.56,-14.37 -25.56,-29.11 0,-0.03 0,-0.07 0,-0.1 l -0.02,-0.01 c 0,-0.03 0,-0.07 0,-0.1 0,-16.09 13.04,-29.14 29.13,-29.14 0.43,0 0.87,0.01 1.29,0.03 h 3.67 0.01 c 14.61,1.91 25.55,14.37 25.55,29.11 0,0.03 0,0.07 0,0.1 l 0.02,0.01 c 0,0.04 0,0.08 0,0.12 0,16.07 -13.03,29.09 -29.09,29.09 -0.03,0 -0.08,0 -0.12,0 l -4.89,-0.01" />
<!-- Right Eye (cyan) -->
<path
id="path72"
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2921.23,1569.61 v -3.65 l -0.01,-0.01 c 0,-0.27 -0.01,-0.54 -0.01,-0.81 0,-18.22 14.66,-33.05 32.87,-33.26 l 0.03,0.01 c 0.28,0 0.57,-0.01 0.84,-0.01 17.29,0 31.72,13.22 33.24,30.44 l -0.02,3.64 0.02,0.02 c 0,0.27 0.01,0.54 0.01,0.81 0,18.37 -14.9,33.28 -33.28,33.28 -0.26,0 -0.54,-0.01 -0.81,-0.02 l -0.02,-0.03 c -17.14,-0.18 -31.36,-13.34 -32.86,-30.42" />
<!-- Nose -->
<path
id="path74"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2892.03,1528.23 -29.21,-20.68" />
<path
id="path76"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2862.82,1507.55 h 32.86" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

+146
View File
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="144 814 280 169"
width="280"
height="169"
xml:space="preserve"
id="svg_lib3270j_cropped"
version="1.1">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<!-- Pure black background (#000000) for classic 3270 CRT terminal look -->
<rect x="144" y="814" width="280" height="169" fill="#000000" />
<g transform="matrix(1.3333333,0,0,-1.3333333,0,1122.6667)" id="g10">
<g transform="scale(0.1)" id="g12">
<g id="g14">
<!-- Green Terminal Screen Frame -->
<path
id="path28"
style="fill:none;stroke:#00ff00;stroke-width:3.75079012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="M 1177.27,2220.71 V 1142.45 H 3084.31 V 2220.71 H 1177.27" />
<!-- Blue Wave / Exponential Curve -->
<path
id="path30"
style="fill:none;stroke:#0000ff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1172.41,1137.58 79.1,-1.22 -0.21,-0.38 c 1.08,0 2.18,0 3.27,0 762.95,0 1464.86,417.88 1829.53,1089.21" />
<!-- Red Circular Arc -->
<path
id="path32"
style="fill:none;stroke:#ff0000;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2593.86,1620.73 -1.22,-28 -0.08,-0.08 c 0.43,-250.15 202.81,-452.72 452.3,-452.72 0.14,0 0.28,0 0.42,0 l 43.9,2.52" />
<!-- Yellow Smiley Head -->
<path
id="path34"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2976,1363.94 v 0.08 c 44.73,34.29 71.21,87.25 71.8,143.61 l 0.01,-0.15 c -0.06,97.48 -79.11,176.47 -176.59,176.47 -0.36,0 -0.73,0 -1.09,0 l -0.07,0.03 c -0.25,0 -0.51,0 -0.75,0 -37.38,0 -73.79,-11.94 -103.92,-34.08 l 0.02,-0.03 c -44.38,-34.04 -70.82,-86.47 -71.8,-142.39 l 0.06,0.04 c 0.54,-97.43 79.04,-176.47 176.46,-177.69 l 0.08,0.02 c 0.64,-0.01 1.29,-0.01 1.92,-0.01 37.4,0 73.82,11.94 103.96,34.08" />
<!-- 'l' -->
<path
id="path36"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
<path
id="path38"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
<!-- 'i' -->
<path
id="path40"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
<path
id="path42"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
<!-- 'b' -->
<path
id="path44"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
<path
id="path46"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
<!-- '3' -->
<path
id="path48"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
<path
id="path50"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
<!-- '2' -->
<path
id="path52"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
<path
id="path54"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
<!-- '7' -->
<path
id="path56"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
<path
id="path58"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
<!-- '0' -->
<path
id="path60"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
<path
id="path62"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
<!-- 'j' -->
<path
id="path64"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
<path
id="path66"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
<!-- Smile -->
<path
id="path68"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2778.85,1478.34 0.04,-0.02 c 21.03,-31.91 56.69,-51.12 94.91,-51.12 0.41,0 0.83,0 1.24,0.01 l -0.02,0.07 c 43.88,0.19 84.11,24.51 104.66,63.28" />
<!-- Left Eye (cyan) -->
<path
id="path70"
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2820.22,1595.17 0.01,0.01 c -14.62,-1.91 -25.56,-14.37 -25.56,-29.11 0,-0.03 0,-0.07 0,-0.1 l -0.02,-0.01 c 0,-0.03 0,-0.07 0,-0.1 0,-16.09 13.04,-29.14 29.13,-29.14 0.43,0 0.87,0.01 1.29,0.03 h 3.67 0.01 c 14.61,1.91 25.55,14.37 25.55,29.11 0,0.03 0,0.07 0,0.1 l 0.02,0.01 c 0,0.04 0,0.08 0,0.12 0,16.07 -13.03,29.09 -29.09,29.09 -0.03,0 -0.08,0 -0.12,0 l -4.89,-0.01" />
<!-- Right Eye (cyan) -->
<path
id="path72"
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2921.23,1569.61 v -3.65 l -0.01,-0.01 c 0,-0.27 -0.01,-0.54 -0.01,-0.81 0,-18.22 14.66,-33.05 32.87,-33.26 l 0.03,0.01 c 0.28,0 0.57,-0.01 0.84,-0.01 17.29,0 31.72,13.22 33.24,30.44 l -0.02,3.64 0.02,0.02 c 0,0.27 0.01,0.54 0.01,0.81 0,18.37 -14.9,33.28 -33.28,33.28 -0.26,0 -0.54,-0.01 -0.81,-0.02 l -0.02,-0.03 c -17.14,-0.18 -31.36,-13.34 -32.86,-30.42" />
<!-- Nose -->
<path
id="path74"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2892.03,1528.23 -29.21,-20.68" />
<path
id="path76"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2862.82,1507.55 h 32.86" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

@@ -60,6 +60,7 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
super("j3270 — Java TN3270 Terminal Emulator"); super("j3270 — Java TN3270 Terminal Emulator");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBackground(Color.BLACK); setBackground(Color.BLACK);
AppIcon.applyTo(this);
buildUI(); buildUI();
buildMenuBar(); buildMenuBar();
@@ -1002,8 +1003,72 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
"About j3270", JOptionPane.INFORMATION_MESSAGE); "About j3270", JOptionPane.INFORMATION_MESSAGE);
} }
/**
* Configure application logging. When debug is false, disables all logging
* to console and file, preventing creation of j3270.log.
*/
public static void configureLogging(boolean debug) {
Logger globalRoot = Logger.getLogger("");
for (java.util.logging.Handler h : globalRoot.getHandlers()) {
globalRoot.removeHandler(h);
try {
h.close();
} catch (Exception ignored) {
}
}
if (debug) {
Level logLevel = Level.ALL;
globalRoot.setLevel(Level.ALL);
java.util.logging.Filter appFilter = record -> record.getLoggerName() != null &&
(record.getLoggerName().startsWith("haus.nightmare") || record.getLoggerName().startsWith("org.pubvm"));
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
consoleHandler.setFormatter(new SimpleFormatter());
consoleHandler.setFilter(appFilter);
globalRoot.addHandler(consoleHandler);
Logger.getLogger("haus.nightmare").setLevel(Level.ALL);
Logger.getLogger("haus.nightmare.j3270").setLevel(Level.ALL);
Logger.getLogger("haus.nightmare.lib3270j").setLevel(Level.ALL);
try {
java.util.logging.FileHandler fileHandler = new java.util.logging.FileHandler("j3270.log", 10 * 1024 * 1024, 1, false) {
@Override
public synchronized void publish(java.util.logging.LogRecord record) {
super.publish(record);
flush();
}
};
fileHandler.setLevel(Level.ALL);
fileHandler.setFormatter(new SimpleFormatter());
fileHandler.setFilter(appFilter);
globalRoot.addHandler(fileHandler);
log.info("Logging protocol trace to j3270.log (debug=" + debug + ", level=" + logLevel + ")");
} catch (Exception e) {
System.err.println("Could not create j3270.log: " + e.getMessage());
}
} else {
globalRoot.setLevel(Level.OFF);
for (String pkg : new String[]{"haus.nightmare", "haus.nightmare.j3270", "haus.nightmare.lib3270j", "org.pubvm"}) {
Logger l = Logger.getLogger(pkg);
l.setLevel(Level.OFF);
for (java.util.logging.Handler h : l.getHandlers()) {
l.removeHandler(h);
try {
h.close();
} catch (Exception ignored) {
}
}
}
}
}
// ========== Main ========== // ========== Main ==========
public static void main(String[] args) { public static void main(String[] args) {
boolean debug = false; boolean debug = false;
boolean cliTls = false; boolean cliTls = false;
@@ -1045,42 +1110,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
} }
} }
Level logLevel = debug ? Level.FINE : Level.INFO; configureLogging(debug);
Logger globalRoot = Logger.getLogger("");
for (java.util.logging.Handler h : globalRoot.getHandlers()) {
globalRoot.removeHandler(h);
}
java.util.logging.Filter appFilter = record -> record.getLoggerName() != null &&
(record.getLoggerName().startsWith("haus.nightmare") || record.getLoggerName().startsWith("org.pubvm"));
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setLevel(Level.ALL);
consoleHandler.setFormatter(new SimpleFormatter());
consoleHandler.setFilter(appFilter);
globalRoot.addHandler(consoleHandler);
Logger.getLogger("haus.nightmare").setLevel(Level.ALL);
Logger.getLogger("haus.nightmare.j3270").setLevel(Level.ALL);
Logger.getLogger("haus.nightmare.lib3270j").setLevel(Level.ALL);
try {
java.util.logging.FileHandler fileHandler = new java.util.logging.FileHandler("j3270.log", 10 * 1024 * 1024, 1, false) {
@Override
public synchronized void publish(java.util.logging.LogRecord record) {
super.publish(record);
flush();
}
};
fileHandler.setLevel(Level.ALL);
fileHandler.setFormatter(new SimpleFormatter());
fileHandler.setFilter(appFilter);
globalRoot.addHandler(fileHandler);
log.info("Logging protocol trace to j3270.log (debug=" + debug + ", level=" + logLevel + ")");
} catch (Exception e) {
System.err.println("Could not create j3270.log: " + e.getMessage());
}
if (configFile != null) { if (configFile != null) {
try { try {
@@ -1,5 +1,6 @@
package haus.nightmare.j3270.ft; package haus.nightmare.j3270.ft;
import haus.nightmare.j3270.ui.AppIcon;
import haus.nightmare.j3270.ui.HostDirectoryDialog; import haus.nightmare.j3270.ui.HostDirectoryDialog;
import haus.nightmare.j3270.ui.ThemeManager; import haus.nightmare.j3270.ui.ThemeManager;
import haus.nightmare.lib3270j.ft.FTConfig; import haus.nightmare.lib3270j.ft.FTConfig;
@@ -92,6 +93,7 @@ public class FileTransferDialog extends JDialog {
super(owner, "File Transfer (IND$FILE)", false); super(owner, "File Transfer (IND$FILE)", false);
this.owner = owner; this.owner = owner;
this.coordinator = coordinator; this.coordinator = coordinator;
AppIcon.applyTo(this);
buildUI(); buildUI();
pack(); pack();
@@ -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<Image> 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<Image> 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<Image> 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<Image> 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;
}
}
@@ -31,6 +31,7 @@ public class ConnectDialog extends JDialog {
public ConnectDialog(Frame parent) { public ConnectDialog(Frame parent) {
super(parent, "Connect to Host", true); super(parent, "Connect to Host", true);
AppIcon.applyTo(this);
buildUI(); buildUI();
pack(); pack();
setLocationRelativeTo(parent); setLocationRelativeTo(parent);
@@ -32,6 +32,7 @@ public class FieldInspectorDialog extends JDialog implements ScreenUpdateListene
super(parent, "3270 Presentation Space Field Inspector", false); super(parent, "3270 Presentation Space Field Inspector", false);
this.client = client; this.client = client;
this.terminalPanel = terminalPanel; this.terminalPanel = terminalPanel;
AppIcon.applyTo(this);
buildUI(); buildUI();
setSize(850, 420); setSize(850, 420);
@@ -32,6 +32,7 @@ public class FindDialog extends JDialog {
super(parent, "Find on Screen", false); super(parent, "Find on Screen", false);
this.client = client; this.client = client;
this.terminalPanel = terminalPanel; this.terminalPanel = terminalPanel;
AppIcon.applyTo(this);
buildUI(); buildUI();
pack(); pack();
@@ -40,6 +40,7 @@ public class HostDirectoryDialog extends JDialog {
super(parent, "Host Catalog & Directory Browser", true); super(parent, "Host Catalog & Directory Browser", true);
this.client = client; this.client = client;
this.initialHostType = hostType; this.initialHostType = hostType;
AppIcon.applyTo(this);
buildUI(initialQuery); buildUI(initialQuery);
setSize(700, 420); setSize(700, 420);
@@ -48,6 +48,7 @@ public class PrinterSessionDialog extends JDialog implements PrintSessionListene
public PrinterSessionDialog(Frame parent, String defaultHost, int defaultPort, boolean defaultTls) { public PrinterSessionDialog(Frame parent, String defaultHost, int defaultPort, boolean defaultTls) {
super(parent, "IBM 3287 Printer Session Manager", false); super(parent, "IBM 3287 Printer Session Manager", false);
this.parent = parent; this.parent = parent;
AppIcon.applyTo(this);
config = new PrinterConfig(defaultHost, defaultPort); config = new PrinterConfig(defaultHost, defaultPort);
config.setUseTls(defaultTls); config.setUseTls(defaultTls);
@@ -25,6 +25,7 @@ public class ScriptDialog extends JDialog {
super(parent, "Run Keystrokes / ECL Script", false); super(parent, "Run Keystrokes / ECL Script", false);
this.client = client; this.client = client;
this.terminalPanel = terminalPanel; this.terminalPanel = terminalPanel;
AppIcon.applyTo(this);
buildUI(); buildUI();
setSize(600, 480); setSize(600, 480);
@@ -62,6 +62,7 @@ public class SettingsDialog extends JDialog {
public SettingsDialog(J3270App parent) { public SettingsDialog(J3270App parent) {
super(parent, "Settings", true); super(parent, "Settings", true);
this.parentApp = parent; this.parentApp = parent;
AppIcon.applyTo(this);
initComponents(); initComponents();
setSize(600, 520); setSize(600, 520);
@@ -17,6 +17,7 @@ public class UntrustedCertificateDialog extends JDialog {
public UntrustedCertificateDialog(Frame parent, String host, int port, X509Certificate[] chain, CertificateException exception) { public UntrustedCertificateDialog(Frame parent, String host, int port, X509Certificate[] chain, CertificateException exception) {
super(parent, "Untrusted SSL/TLS Certificate", true); super(parent, "Untrusted SSL/TLS Certificate", true);
AppIcon.applyTo(this);
buildUI(host, port, chain, exception); buildUI(host, port, chain, exception);
pack(); pack();
setLocationRelativeTo(parent); setLocationRelativeTo(parent);
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="124 738 320 320"
width="320"
height="320"
xml:space="preserve"
id="svg_j3270_icon"
version="1.1">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<!-- Pure black background (#000000) for classic 3270 CRT terminal look -->
<rect x="124" y="738" width="320" height="320" fill="#000000" />
<g transform="matrix(1.3333333,0,0,-1.3333333,0,1122.6667)" id="g10">
<g transform="scale(0.1)" id="g12">
<g id="g14">
<!-- Green Terminal Screen Frame -->
<path
id="path28"
style="fill:none;stroke:#00ff00;stroke-width:3.75079012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="M 1177.27,2220.71 V 1142.45 H 3084.31 V 2220.71 H 1177.27" />
<!-- Blue Wave / Exponential Curve -->
<path
id="path30"
style="fill:none;stroke:#0000ff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1172.41,1137.58 79.1,-1.22 -0.21,-0.38 c 1.08,0 2.18,0 3.27,0 762.95,0 1464.86,417.88 1829.53,1089.21" />
<!-- Red Circular Arc -->
<path
id="path32"
style="fill:none;stroke:#ff0000;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2593.86,1620.73 -1.22,-28 -0.08,-0.08 c 0.43,-250.15 202.81,-452.72 452.3,-452.72 0.14,0 0.28,0 0.42,0 l 43.9,2.52" />
<!-- Yellow Smiley Head -->
<path
id="path34"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2976,1363.94 v 0.08 c 44.73,34.29 71.21,87.25 71.8,143.61 l 0.01,-0.15 c -0.06,97.48 -79.11,176.47 -176.59,176.47 -0.36,0 -0.73,0 -1.09,0 l -0.07,0.03 c -0.25,0 -0.51,0 -0.75,0 -37.38,0 -73.79,-11.94 -103.92,-34.08 l 0.02,-0.03 c -44.38,-34.04 -70.82,-86.47 -71.8,-142.39 l 0.06,0.04 c 0.54,-97.43 79.04,-176.47 176.46,-177.69 l 0.08,0.02 c 0.64,-0.01 1.29,-0.01 1.92,-0.01 37.4,0 73.82,11.94 103.96,34.08" />
<!-- 'l' -->
<path
id="path36"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
<path
id="path38"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
<!-- 'i' -->
<path
id="path40"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
<path
id="path42"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
<!-- 'b' -->
<path
id="path44"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
<path
id="path46"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
<!-- '3' -->
<path
id="path48"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
<path
id="path50"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
<!-- '2' -->
<path
id="path52"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
<path
id="path54"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
<!-- '7' -->
<path
id="path56"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
<path
id="path58"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
<!-- '0' -->
<path
id="path60"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
<path
id="path62"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
<!-- 'j' -->
<path
id="path64"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
<path
id="path66"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
<!-- Smile -->
<path
id="path68"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2778.85,1478.34 0.04,-0.02 c 21.03,-31.91 56.69,-51.12 94.91,-51.12 0.41,0 0.83,0 1.24,0.01 l -0.02,0.07 c 43.88,0.19 84.11,24.51 104.66,63.28" />
<!-- Left Eye (cyan) -->
<path
id="path70"
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2820.22,1595.17 0.01,0.01 c -14.62,-1.91 -25.56,-14.37 -25.56,-29.11 0,-0.03 0,-0.07 0,-0.1 l -0.02,-0.01 c 0,-0.03 0,-0.07 0,-0.1 0,-16.09 13.04,-29.14 29.13,-29.14 0.43,0 0.87,0.01 1.29,0.03 h 3.67 0.01 c 14.61,1.91 25.55,14.37 25.55,29.11 0,0.03 0,0.07 0,0.1 l 0.02,0.01 c 0,0.04 0,0.08 0,0.12 0,16.07 -13.03,29.09 -29.09,29.09 -0.03,0 -0.08,0 -0.12,0 l -4.89,-0.01" />
<!-- Right Eye (cyan) -->
<path
id="path72"
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2921.23,1569.61 v -3.65 l -0.01,-0.01 c 0,-0.27 -0.01,-0.54 -0.01,-0.81 0,-18.22 14.66,-33.05 32.87,-33.26 l 0.03,0.01 c 0.28,0 0.57,-0.01 0.84,-0.01 17.29,0 31.72,13.22 33.24,30.44 l -0.02,3.64 0.02,0.02 c 0,0.27 0.01,0.54 0.01,0.81 0,18.37 -14.9,33.28 -33.28,33.28 -0.26,0 -0.54,-0.01 -0.81,-0.02 l -0.02,-0.03 c -17.14,-0.18 -31.36,-13.34 -32.86,-30.42" />
<!-- Nose -->
<path
id="path74"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2892.03,1528.23 -29.21,-20.68" />
<path
id="path76"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2862.82,1507.55 h 32.86" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

+146
View File
@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="144 814 280 169"
width="280"
height="169"
xml:space="preserve"
id="svg_lib3270j_cropped"
version="1.1">
<metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<!-- Pure black background (#000000) for classic 3270 CRT terminal look -->
<rect x="144" y="814" width="280" height="169" fill="#000000" />
<g transform="matrix(1.3333333,0,0,-1.3333333,0,1122.6667)" id="g10">
<g transform="scale(0.1)" id="g12">
<g id="g14">
<!-- Green Terminal Screen Frame -->
<path
id="path28"
style="fill:none;stroke:#00ff00;stroke-width:3.75079012;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="M 1177.27,2220.71 V 1142.45 H 3084.31 V 2220.71 H 1177.27" />
<!-- Blue Wave / Exponential Curve -->
<path
id="path30"
style="fill:none;stroke:#0000ff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1172.41,1137.58 79.1,-1.22 -0.21,-0.38 c 1.08,0 2.18,0 3.27,0 762.95,0 1464.86,417.88 1829.53,1089.21" />
<!-- Red Circular Arc -->
<path
id="path32"
style="fill:none;stroke:#ff0000;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2593.86,1620.73 -1.22,-28 -0.08,-0.08 c 0.43,-250.15 202.81,-452.72 452.3,-452.72 0.14,0 0.28,0 0.42,0 l 43.9,2.52" />
<!-- Yellow Smiley Head -->
<path
id="path34"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2976,1363.94 v 0.08 c 44.73,34.29 71.21,87.25 71.8,143.61 l 0.01,-0.15 c -0.06,97.48 -79.11,176.47 -176.59,176.47 -0.36,0 -0.73,0 -1.09,0 l -0.07,0.03 c -0.25,0 -0.51,0 -0.75,0 -37.38,0 -73.79,-11.94 -103.92,-34.08 l 0.02,-0.03 c -44.38,-34.04 -70.82,-86.47 -71.8,-142.39 l 0.06,0.04 c 0.54,-97.43 79.04,-176.47 176.46,-177.69 l 0.08,0.02 c 0.64,-0.01 1.29,-0.01 1.92,-0.01 37.4,0 73.82,11.94 103.96,34.08" />
<!-- 'l' -->
<path
id="path36"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
<path
id="path38"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1502.21,1785.02 h 23.13 v 32.86 h -23.13 l -4.87,8.52 v 154.56 h -18.25 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52" />
<!-- 'i' -->
<path
id="path40"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
<path
id="path42"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1566.71,1785.02 h 13.39 v 32.86 h -13.39 l -4.86,8.52 v 105.88 h -18.26 V 1826.4 l 4.87,-25.56 4.87,-7.3 13.38,-8.52 m -4.86,163.08 v 32.86 h -18.26 v -32.86 h 18.26" />
<!-- 'b' -->
<path
id="path44"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
<path
id="path46"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1598.36,1785.02 v 195.94 h 18.25 v -48.68 h 41.38 l 13.39,-8.52 4.87,-8.52 4.86,-24.34 v -64.5 l -4.86,-25.56 -4.87,-7.3 -13.39,-8.52 h -59.63 m 18.25,32.86 h 41.38 l 4.87,8.52 v 64.5 l -4.87,8.52 h -41.38 v -81.54" />
<!-- '3' -->
<path
id="path48"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
<path
id="path50"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1722.49,1817.88 h -23.12 v -32.86 h 69.36 l 13.4,8.52 4.86,7.3 3.65,25.56 v 40.16 l -8.51,15.82 8.51,17.04 v 40.16 l -3.65,24.34 -4.86,8.52 -13.4,8.52 h -69.36 v -32.86 h 69.36 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24 v -32.86 h 46.24 l 3.66,-8.52 v -31.64 l -3.66,-8.52 h -46.24" />
<!-- '2' -->
<path
id="path52"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
<path
id="path54"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1808.9,1785.02 h 92.49 v 32.86 h -73.02 v 32.86 l 3.65,7.3 h 46.25 l 13.38,8.52 4.87,8.52 4.87,24.34 v 40.16 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -46.25 l -13.39,-8.52 -4.86,-8.52 -4.87,-24.34 v -24.34 h 19.47 v 24.34 l 3.65,8.52 h 46.25 l 4.86,-8.52 v -40.16 l -4.86,-8.52 h -46.25 l -13.39,-8.52 -4.86,-7.3 -4.87,-24.34 v -65.72" />
<!-- '7' -->
<path
id="path56"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
<path
id="path58"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 1919.64,1980.96 h 91.28 v -65.72 l -49.9,-130.22 h -23.12 l 54.76,138.74 v 24.34 h -73.02 v 32.86" />
<!-- '0' -->
<path
id="path60"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
<path
id="path62"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2047.43,1972.44 -4.87,-8.52 -3.65,-24.34 V 1826.4 l 3.65,-25.56 4.87,-7.3 14.61,-8.52 h 31.64 l 13.38,8.52 4.87,7.3 4.87,25.56 v 113.18 l -4.87,24.34 -4.87,8.52 -13.38,8.52 h -31.64 l -14.61,-8.52 m 14.61,-24.34 h 31.64 l 4.86,-8.52 V 1826.4 l -4.86,-8.52 h -31.64 l -4.88,8.52 v 113.18 l 4.88,8.52" />
<!-- 'j' -->
<path
id="path64"
style="fill:#ff00ff;fill-opacity:1;fill-rule:evenodd;stroke:none"
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
<path
id="path66"
style="fill:none;stroke:#ff00ff;stroke-width:1.21700001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2176.43,1948.1 v 32.86 h -18.25 v -32.86 h 18.25 m 0,-15.82 V 1769.2 l -4.87,-25.56 -4.86,-7.3 -13.39,-8.52 h -18.26 v 32.86 h 18.26 l 4.87,8.52 v 163.08 h 18.25" />
<!-- Smile -->
<path
id="path68"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2778.85,1478.34 0.04,-0.02 c 21.03,-31.91 56.69,-51.12 94.91,-51.12 0.41,0 0.83,0 1.24,0.01 l -0.02,0.07 c 43.88,0.19 84.11,24.51 104.66,63.28" />
<!-- Left Eye (cyan) -->
<path
id="path70"
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2820.22,1595.17 0.01,0.01 c -14.62,-1.91 -25.56,-14.37 -25.56,-29.11 0,-0.03 0,-0.07 0,-0.1 l -0.02,-0.01 c 0,-0.03 0,-0.07 0,-0.1 0,-16.09 13.04,-29.14 29.13,-29.14 0.43,0 0.87,0.01 1.29,0.03 h 3.67 0.01 c 14.61,1.91 25.55,14.37 25.55,29.11 0,0.03 0,0.07 0,0.1 l 0.02,0.01 c 0,0.04 0,0.08 0,0.12 0,16.07 -13.03,29.09 -29.09,29.09 -0.03,0 -0.08,0 -0.12,0 l -4.89,-0.01" />
<!-- Right Eye (cyan) -->
<path
id="path72"
style="fill:none;stroke:#00ffff;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2921.23,1569.61 v -3.65 l -0.01,-0.01 c 0,-0.27 -0.01,-0.54 -0.01,-0.81 0,-18.22 14.66,-33.05 32.87,-33.26 l 0.03,0.01 c 0.28,0 0.57,-0.01 0.84,-0.01 17.29,0 31.72,13.22 33.24,30.44 l -0.02,3.64 0.02,0.02 c 0,0.27 0.01,0.54 0.01,0.81 0,18.37 -14.9,33.28 -33.28,33.28 -0.26,0 -0.54,-0.01 -0.81,-0.02 l -0.02,-0.03 c -17.14,-0.18 -31.36,-13.34 -32.86,-30.42" />
<!-- Nose -->
<path
id="path74"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2892.03,1528.23 -29.21,-20.68" />
<path
id="path76"
style="fill:none;stroke:#ffff00;stroke-width:7.50158978;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 2862.82,1507.55 h 32.86" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -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<Image> 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/");
}
}
@@ -0,0 +1,93 @@
package haus.nightmare.j3270;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.*;
public class LoggingConfigurationTest {
private PrintStream originalOut;
private PrintStream originalErr;
@BeforeEach
public void setUp() {
originalOut = System.out;
originalErr = System.err;
}
@AfterEach
public void tearDown() {
System.setOut(originalOut);
System.setErr(originalErr);
// Ensure all handlers are closed and logging reset
J3270App.configureLogging(false);
}
@Test
public void testLoggingDisabledWhenDebugFalse() {
File logFile = new File("j3270.log");
if (logFile.exists()) {
logFile.delete();
}
ByteArrayOutputStream outContent = new ByteArrayOutputStream();
ByteArrayOutputStream errContent = new ByteArrayOutputStream();
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));
J3270App.configureLogging(false);
// Root logger should have no handlers attached
Logger rootLogger = Logger.getLogger("");
assertEquals(0, rootLogger.getHandlers().length, "Root logger should have no handlers when debug is disabled");
assertEquals(Level.OFF, rootLogger.getLevel(), "Root logger level should be OFF when debug is disabled");
// Application loggers should be OFF
assertEquals(Level.OFF, Logger.getLogger("haus.nightmare").getLevel());
assertEquals(Level.OFF, Logger.getLogger("haus.nightmare.j3270").getLevel());
assertEquals(Level.OFF, Logger.getLogger("haus.nightmare.lib3270j").getLevel());
// Emit log records at all levels
Logger appLogger = Logger.getLogger("haus.nightmare.j3270.J3270App");
appLogger.severe("Test SEVERE message");
appLogger.warning("Test WARNING message");
appLogger.info("Test INFO message");
appLogger.fine("Test FINE message");
// Verify nothing was written to stdout or stderr
assertEquals(0, outContent.size(), "Standard output should be empty when debug is disabled");
assertEquals(0, errContent.size(), "Standard error should be empty when debug is disabled");
// Verify j3270.log was NOT created
assertFalse(logFile.exists(), "j3270.log should NOT be created when debug is disabled");
}
@Test
public void testLoggingEnabledWhenDebugTrue() {
J3270App.configureLogging(true);
Logger rootLogger = Logger.getLogger("");
Handler[] handlers = rootLogger.getHandlers();
assertTrue(handlers.length >= 2, "Root logger should have at least ConsoleHandler and FileHandler when debug is enabled");
assertEquals(Level.ALL, rootLogger.getLevel());
assertEquals(Level.ALL, Logger.getLogger("haus.nightmare").getLevel());
assertEquals(Level.ALL, Logger.getLogger("haus.nightmare.j3270").getLevel());
assertEquals(Level.ALL, Logger.getLogger("haus.nightmare.lib3270j").getLevel());
File logFile = new File("j3270.log");
assertTrue(logFile.exists(), "j3270.log should be created when debug is enabled");
// Clean up
J3270App.configureLogging(false);
}
}
@@ -435,6 +435,14 @@ public class ConnectionConfig {
} }
} }
// Parse [lu@]host[:port] (e.g. "00C2@mvs.host.com:1023")
String parsedLu = null;
int atIdx = s.indexOf('@');
if (atIdx > 0 && atIdx < s.length() - 1) {
parsedLu = s.substring(0, atIdx).trim();
s = s.substring(atIdx + 1).trim();
}
String host = s; String host = s;
int port = (defaultPort > 0) ? defaultPort : (tls ? 992 : 23); int port = (defaultPort > 0) ? defaultPort : (tls ? 992 : 23);
@@ -458,6 +466,9 @@ public class ConnectionConfig {
} }
ConnectionConfig config = new ConnectionConfig(host, port, defaultModel != null ? defaultModel : TerminalModel.IBM_3279_4); ConnectionConfig config = new ConnectionConfig(host, port, defaultModel != null ? defaultModel : TerminalModel.IBM_3279_4);
if (parsedLu != null && !parsedLu.isEmpty()) {
config.setLuName(parsedLu);
}
config.setUseTls(tls); config.setUseTls(tls);
config.setTn3270eEnabled(tn3270e); config.setTn3270eEnabled(tn3270e);
config.setKeepAliveEnabled(keepAlive); config.setKeepAliveEnabled(keepAlive);
@@ -54,6 +54,12 @@ public class DataStreamProcessor {
private boolean unlockSysPending = false; private boolean unlockSysPending = false;
private boolean rcvdRead = false; private boolean rcvdRead = false;
// Modal SA (set attribute) character attributes
private byte currentFg = 0;
private byte currentBg = 0;
private byte currentGr = 0;
private byte currentCs = 0;
/** Functional interface for sending output back through the telnet stack. */ /** Functional interface for sending output back through the telnet stack. */
@FunctionalInterface @FunctionalInterface
public interface OutputSender { public interface OutputSender {
@@ -240,6 +246,10 @@ public class DataStreamProcessor {
programSymbolManager.commitStagedSymbols(); programSymbolManager.commitStagedSymbols();
log.info(">>> EAU: erasing all unprotected fields"); log.info(">>> EAU: erasing all unprotected fields");
screen.eraseAllUnprotected(); screen.eraseAllUnprotected();
currentFg = 0;
currentBg = 0;
currentGr = 0;
currentCs = 0;
break; break;
case CMD_WSF: case CMD_WSF:
case SNA_CMD_WSF: case SNA_CMD_WSF:
@@ -378,11 +388,19 @@ public class DataStreamProcessor {
if (wccReset(wcc)) { if (wccReset(wcc)) {
// Reset all character attributes to defaults // Reset all character attributes to defaults
log.fine("WCC reset: clearing default attributes"); log.fine("WCC reset: clearing default attributes");
currentFg = 0;
currentBg = 0;
currentGr = 0;
currentCs = 0;
} }
if (eraseFirst) { if (eraseFirst) {
screen.clear(); screen.clear();
log.fine("Cleared screen for Erase/Write"); log.fine("Cleared screen for Erase/Write");
currentFg = 0;
currentBg = 0;
currentGr = 0;
currentCs = 0;
} }
// Process orders and data starting at byte 2 // Process orders and data starting at byte 2
@@ -390,9 +408,6 @@ public class DataStreamProcessor {
int end = offset + length; int end = offset + length;
int baddr = screen.getBufferAddress(); int baddr = screen.getBufferAddress();
int size = screen.getRows() * screen.getCols(); int size = screen.getRows() * screen.getCols();
// Current SA (set attribute) values for character-mode
byte currentFg = 0, currentBg = 0, currentGr = 0, currentCs = 0;
boolean lastWasOrder = false; boolean lastWasOrder = false;
while (pos < end) { while (pos < end) {
@@ -427,10 +442,6 @@ public class DataStreamProcessor {
// FA position is a display position that shows as blank // FA position is a display position that shows as blank
ea.ec = 0; ea.ec = 0;
ea.ucs4 = ' '; ea.ucs4 = ' ';
currentFg = 0;
currentBg = 0;
currentGr = 0;
currentCs = 0;
screen.setFormatted(true); screen.setFormatted(true);
baddr = (baddr + 1) % size; baddr = (baddr + 1) % size;
screen.setBufferAddress(baddr); screen.setBufferAddress(baddr);
@@ -455,10 +466,6 @@ public class DataStreamProcessor {
ea.clear(); ea.clear();
ea.ec = 0; ea.ec = 0;
ea.ucs4 = ' '; ea.ucs4 = ' ';
currentFg = 0;
currentBg = 0;
currentGr = 0;
currentCs = 0;
for (int i = 0; i < nPairs; i++) { for (int i = 0; i < nPairs; i++) {
int attrType = data[pos + 2 + i * 2] & 0xFF; int attrType = data[pos + 2 + i * 2] & 0xFF;
@@ -485,6 +492,12 @@ public class DataStreamProcessor {
int attrType = data[pos + 1] & 0xFF; int attrType = data[pos + 1] & 0xFF;
int attrValue = data[pos + 2] & 0xFF; int attrValue = data[pos + 2] & 0xFF;
switch (attrType) { switch (attrType) {
case XA_ALL:
currentFg = 0;
currentBg = 0;
currentGr = 0;
currentCs = 0;
break;
case XA_FOREGROUND: case XA_FOREGROUND:
currentFg = (byte) attrValue; currentFg = (byte) attrValue;
break; break;
@@ -984,7 +997,13 @@ public class DataStreamProcessor {
break; break;
case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA: // 0x85: Graphics Object Data / GOCA case haus.nightmare.lib3270j.graphics.GocaConstants.SF_OBJDATA: // 0x85: Graphics Object Data / GOCA
if (fieldLen > 3) { if (fieldLen > 3) {
graphicsPlane.setCharDimensions(qrBuilder.getCharWidth(), qrBuilder.getCharHeight());
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows()); graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
int targetW = screen.getCols() * qrBuilder.getCharWidth();
int targetH = screen.getRows() * qrBuilder.getCharHeight();
if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) {
graphicsPlane.resize(targetW, targetH);
}
gocaDecoder.decodeStream(data, pos + 3, fieldLen - 3); gocaDecoder.decodeStream(data, pos + 3, fieldLen - 3);
notifyScreenUpdated(); notifyScreenUpdated();
} }
@@ -1298,9 +1317,10 @@ public class DataStreamProcessor {
int orderOffset = (fieldLen >= 7) ? (offset + 7) : (offset + 4); int orderOffset = (fieldLen >= 7) ? (offset + 7) : (offset + 4);
int orderLen = Math.max(0, fieldLen - (orderOffset - offset)); int orderLen = Math.max(0, fieldLen - (orderOffset - offset));
graphicsPlane.setCharDimensions(qrBuilder.getCharWidth(), qrBuilder.getCharHeight());
graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows()); graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows());
int targetW = screen.getCols() * 9; int targetW = screen.getCols() * qrBuilder.getCharWidth();
int targetH = screen.getRows() * 16; int targetH = screen.getRows() * qrBuilder.getCharHeight();
if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) { if (graphicsPlane.getCanvasWidth() != targetW || graphicsPlane.getCanvasHeight() != targetH) {
graphicsPlane.resize(targetW, targetH); graphicsPlane.resize(targetW, targetH);
} }
@@ -424,8 +424,8 @@ public class QueryReplyBuilder {
out.write((Yr_HOD >> 16) & 0xFF); out.write((Yr_HOD >> 16) & 0xFF);
out.write((Yr_HOD >> 8) & 0xFF); out.write((Yr_HOD >> 8) & 0xFF);
out.write(Yr_HOD & 0xFF); out.write(Yr_HOD & 0xFF);
int charW = getCharWidth(); int charW = getCharWidth(maxRows);
int charH = getCharHeight(); int charH = getCharHeight(maxRows);
out.write(charW); // AW out.write(charW); // AW
out.write(charH); // AH out.write(charH); // AH
int buf = maxCols * maxRows; int buf = maxCols * maxRows;
@@ -435,6 +435,11 @@ public class QueryReplyBuilder {
} }
public int getCharWidth() { public int getCharWidth() {
int rows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
return getCharWidth(rows);
}
public int getCharWidth(int rows) {
if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) { if (screen != null && screen.getTranslator() != null && screen.getTranslator().isDBCS()) {
return 12; return 12;
} }
@@ -442,18 +447,11 @@ public class QueryReplyBuilder {
} }
public int getCharHeight() { public int getCharHeight() {
// ARCHITECTURAL NOTE ON 3179G GOCA VERTICAL ALIGNMENT & QUERY REPLIES: int rows = (screen != null) ? screen.getMaxRows() : MODEL_2_ROWS;
// Why hardcoding SH = 12 (0x0C) in Character Sets & Usable Area failed in past iterations: return getCharHeight(rows);
// When SDH/AH is declared as 12 (0x0C) in Query Reply, the mainframe host GDDM engine computes }
// total presentation space as rows * 12 (e.g. 43 * 12 = 516 units, yMax = 257).
// GDDM then places the top menu bar at Row 1 (gy = 187..200). public int getCharHeight(int rows) {
// Meanwhile, the client emulator rendered into a 16-pitch grid (43 * 16 = 688 units, yMax = 343).
// On a 688-unit canvas, gy = 200 mapped to Row 9.2 (middle of the screen), leaving a massive void above.
// When the user clicked on the visual menu drawn at Row 9, the client emitted gy = 189 with cursor at Row 9,
// which GDDM rejected as outside its menu hit box (causing terminal alarm beeps).
//
// Solution: Declare SDH = 16 (0x10) when Vector Graphics is enabled (3179G standard), ensuring host GDDM
// and client GraphicsPlane share the exact same 16-pitch presentation space (720x688, yMax = 343).
return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2; return graphicsMode.isVectorGraphicsEnabled() ? 0x10 : SH_3279_2;
} }
@@ -395,13 +395,16 @@ public class GocaDecoder {
if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00 || order == GocaConstants.G_COMT) { if (order == GocaConstants.G_NOP1 || order == 0xFF || order == 0x00 || order == GocaConstants.G_COMT) {
return 1; return 1;
} }
// Orders with optional 0x00 trailing length/qualifier byte (e.g. 3E 00, 71 00, 60 00, 7E 00, 3F 00, 93 00, 91 00) // Orders with optional 0x00 trailing length/qualifier byte (e.g. 3E 00, 71 00, 60 00, 7E 00, 3F 00)
if (order == GocaConstants.G_ENDPROLOGUE || order == GocaConstants.G_ENDSEGM || if (order == GocaConstants.G_ENDPROLOGUE || order == GocaConstants.G_ENDSEGM ||
order == GocaConstants.G_GEAR || order == GocaConstants.G_GERASE || order == GocaConstants.G_GEAR || order == GocaConstants.G_GERASE ||
order == GocaConstants.G_GPOP || order == GocaConstants.G_GEIMG || order == GocaConstants.G_GPOP) {
(inImage && order == GocaConstants.G_GEIMG_ALT)) {
return (idx + 1 < end && data[idx + 1] == 0x00) ? 2 : 1; return (idx + 1 < end && data[idx + 1] == 0x00) ? 2 : 1;
} }
// G_GEIMG (0x93 End Image): self-defining draw order (e.g. 93 02 00 00 or 93 00 or 93)
if (order == GocaConstants.G_GEIMG) {
return (idx + 1 < end) ? ((data[idx + 1] & 0xFF) + 2) : 1;
}
if (idx + 1 >= end) { if (idx + 1 >= end) {
return -1; return -1;
} }
@@ -552,31 +555,45 @@ public class GocaDecoder {
idx += orderLen; idx += orderLen;
break; break;
} }
case GocaConstants.G_GEIMG: // 0x93 case GocaConstants.G_GEIMG: { // 0x93 End Image
case GocaConstants.G_GEIMG_ALT: { // 0x91 endImage();
if (inImage || order == GocaConstants.G_GEIMG) { idx += orderLen;
break;
}
case GocaConstants.G_GBIMGC: { // 0x91: Begin Image at Current Position (G_GBIMGC)
if (inImage) {
endImage(); endImage();
idx += orderLen;
} else {
// 0x91: Begin Image at Current Position (G_GBIMGC)
if (payloadLen >= 4 && idx + 2 + payloadLen <= end) {
int w = readCoord(inputData, idx + 2);
int h = readCoord(inputData, idx + 4);
int bitDepth = GocaConstants.BPP_1;
int compression = GocaConstants.IMG_UNCOMPRESSED;
if (payloadLen >= 5) {
int fmt = inputData[idx + 6] & 0xFF;
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
else if (fmt == 4) bitDepth = GocaConstants.BPP_4;
else if (fmt == 8) bitDepth = GocaConstants.BPP_8;
}
if (payloadLen >= 6) {
compression = inputData[idx + 7] & 0xFF;
}
beginImage(curX, curY, w, h, bitDepth, compression);
}
idx += orderLen;
} }
int w = 0, h = 0;
int bitDepth = GocaConstants.BPP_1;
int compression = GocaConstants.IMG_UNCOMPRESSED;
if (payloadLen >= 6 && idx + 2 + payloadLen <= end) {
w = readCoord(inputData, idx + 4);
h = readCoord(inputData, idx + 6);
if (payloadLen >= 7) {
int fmt = inputData[idx + 8] & 0xFF;
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
else if (fmt == 4) bitDepth = GocaConstants.BPP_4;
else if (fmt == 8) bitDepth = GocaConstants.BPP_8;
}
if (payloadLen >= 8) {
compression = inputData[idx + 9] & 0xFF;
}
} else if (payloadLen >= 4 && idx + 2 + payloadLen <= end) {
w = readCoord(inputData, idx + 2);
h = readCoord(inputData, idx + 4);
if (payloadLen >= 5) {
int fmt = inputData[idx + 6] & 0xFF;
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
else if (fmt == 4) bitDepth = GocaConstants.BPP_4;
else if (fmt == 8) bitDepth = GocaConstants.BPP_8;
}
if (payloadLen >= 6) {
compression = inputData[idx + 7] & 0xFF;
}
}
beginImage(curX, curY, w, h, bitDepth, compression);
idx += orderLen;
break; break;
} }
case GocaConstants.G_BEGSEGM: { // Begin Segment (0x70) case GocaConstants.G_BEGSEGM: { // Begin Segment (0x70)
@@ -986,21 +1003,39 @@ public class GocaDecoder {
break; break;
} }
case GocaConstants.G_GBIMG: { // Begin Image (0xD1) case GocaConstants.G_GBIMG: { // Begin Image (0xD1)
if (inImage) {
endImage();
}
if (payloadLen >= 8 && idx + 2 + payloadLen <= end) { if (payloadLen >= 8 && idx + 2 + payloadLen <= end) {
int x = readCoord(inputData, idx + 2); int x = readCoord(inputData, idx + 2);
int y = readCoord(inputData, idx + 4); int y = readCoord(inputData, idx + 4);
int w = readCoord(inputData, idx + 6); int w, h;
int h = readCoord(inputData, idx + 8);
int bitDepth = GocaConstants.BPP_1; int bitDepth = GocaConstants.BPP_1;
int compression = GocaConstants.IMG_UNCOMPRESSED; int compression = GocaConstants.IMG_UNCOMPRESSED;
if (payloadLen >= 9) {
int fmt = inputData[idx + 10] & 0xFF;
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
else if (fmt == 4) bitDepth = GocaConstants.BPP_4;
else if (fmt == 8) bitDepth = GocaConstants.BPP_8;
}
if (payloadLen >= 10) { if (payloadLen >= 10) {
compression = inputData[idx + 11] & 0xFF; w = readCoord(inputData, idx + 8);
h = readCoord(inputData, idx + 10);
if (payloadLen >= 11) {
int fmt = inputData[idx + 12] & 0xFF;
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
else if (fmt == 4) bitDepth = GocaConstants.BPP_4;
else if (fmt == 8) bitDepth = GocaConstants.BPP_8;
}
if (payloadLen >= 12) {
compression = inputData[idx + 13] & 0xFF;
}
} else {
w = readCoord(inputData, idx + 6);
h = readCoord(inputData, idx + 8);
if (payloadLen >= 9) {
int fmt = inputData[idx + 10] & 0xFF;
if (fmt == 2) bitDepth = GocaConstants.BPP_2;
else if (fmt == 4) bitDepth = GocaConstants.BPP_4;
else if (fmt == 8) bitDepth = GocaConstants.BPP_8;
}
if (payloadLen >= 10) {
compression = inputData[idx + 11] & 0xFF;
}
} }
beginImage(x, y, w, h, bitDepth, compression); beginImage(x, y, w, h, bitDepth, compression);
} }
@@ -418,9 +418,27 @@ public class GraphicsPlane implements PixelBuffer {
return transform; return transform;
} }
// Character cell dimensions matching Query Reply presentation space
private int charWidth = 9;
private int charHeight = 16;
public synchronized void setCharDimensions(int width, int height) {
if (width > 0) this.charWidth = width;
if (height > 0) this.charHeight = height;
this.transform.setDefaultCharMetrics(this.charWidth, this.charHeight);
}
public int getCharWidth() {
return charWidth;
}
public int getCharHeight() {
return charHeight;
}
public int getTotalWidth() { public int getTotalWidth() {
int cols = screenCols > 0 ? screenCols : 80; int cols = screenCols > 0 ? screenCols : 80;
return cols * 9; return cols * charWidth;
} }
/** /**
@@ -457,7 +475,7 @@ public class GraphicsPlane implements PixelBuffer {
*/ */
public int getTotalHeight() { public int getTotalHeight() {
int rows = screenRows > 0 ? screenRows : 24; int rows = screenRows > 0 ? screenRows : 24;
return rows * 16; return rows * charHeight;
} }
public int getXMax() { public int getXMax() {
@@ -70,8 +70,22 @@ public class TelnetFSM {
private List<String> getCandidateTerminalTypes() { private List<String> getCandidateTerminalTypes() {
List<String> list = new ArrayList<>(); List<String> list = new ArrayList<>();
String currentLu = null;
List<String> lus = config.getLuNames();
if (lus != null && !lus.isEmpty() && luIndex < lus.size()) {
currentLu = lus.get(luIndex);
} else if (config.getLuName() != null && !config.getLuName().trim().isEmpty()) {
currentLu = config.getLuName().trim();
}
if (config.getTerminalName() != null && !config.getTerminalName().trim().isEmpty()) { if (config.getTerminalName() != null && !config.getTerminalName().trim().isEmpty()) {
list.add(config.getTerminalName().trim()); String tName = config.getTerminalName().trim();
if (currentLu != null && !currentLu.isEmpty() && !tName.contains("@")) {
list.add(tName + "@" + currentLu);
list.add(tName);
} else {
list.add(tName);
}
return list; return list;
} }
if (config.isDynamicModel()) { if (config.isDynamicModel()) {
@@ -82,24 +96,41 @@ public class TelnetFSM {
list.add("IBM-3279-2"); list.add("IBM-3279-2");
list.add("IBM-3278-2"); list.add("IBM-3278-2");
list.add("UNKNOWN"); list.add("UNKNOWN");
return list; } else {
TerminalModel model = config.getModel();
list.add(model.getTerminalType());
list.add(model.getBaseTerminalType());
if (model.isColor()) {
try {
TerminalModel mono = TerminalModel.forModel(model.getModelNumber(), false);
list.add(mono.getTerminalType());
list.add(mono.getBaseTerminalType());
} catch (Exception ignored) {}
}
if (model.getModelNumber() != 2) {
list.add("IBM-3279-2-E");
list.add("IBM-3279-2");
list.add("IBM-3278-2");
}
list.add("UNKNOWN");
} }
TerminalModel model = config.getModel();
list.add(model.getTerminalType()); if (currentLu != null && !currentLu.isEmpty()) {
list.add(model.getBaseTerminalType()); List<String> luCandidates = new ArrayList<>();
if (model.isColor()) { for (String item : list) {
try { if (!"UNKNOWN".equalsIgnoreCase(item) && !item.contains("@")) {
TerminalModel mono = TerminalModel.forModel(model.getModelNumber(), false); luCandidates.add(item + "@" + currentLu);
list.add(mono.getTerminalType()); }
list.add(mono.getBaseTerminalType()); }
} catch (Exception ignored) {} for (String item : list) {
if (!"UNKNOWN".equalsIgnoreCase(item)) {
luCandidates.add(item);
}
}
luCandidates.add("UNKNOWN");
return luCandidates;
} }
if (model.getModelNumber() != 2) {
list.add("IBM-3279-2-E");
list.add("IBM-3279-2");
list.add("IBM-3278-2");
}
list.add("UNKNOWN");
return list; return list;
} }
@@ -589,6 +620,9 @@ public class TelnetFSM {
out.write(IAC); out.write(IAC);
out.write(SE); out.write(SE);
sendBytes(out.toByteArray()); sendBytes(out.toByteArray());
if (termType.contains("@")) {
connectedLu = termType.substring(termType.indexOf('@') + 1).trim();
}
log.warning(">>> SENT SB TTYPE IS " + termType + " SE"); log.warning(">>> SENT SB TTYPE IS " + termType + " SE");
} }
} }
@@ -82,4 +82,120 @@ public class DataStreamProcessorTest {
assertNotNull(sentData.get()); assertNotNull(sentData.get());
assertEquals((byte) AID_ENTER, sentData.get()[0], "ReadBuffer must transmit operator's stored AID"); assertEquals((byte) AID_ENTER, sentData.get()[0], "ReadBuffer must transmit operator's stored AID");
} }
@Test
public void testCharacterAttributesPersistAcrossStartField() throws java.io.IOException {
// User's exact ISPF Option 0 sequence:
// SBA(7, 1) -> SF(prot,skip) -> SA(yellow) -> ' 4 ' -> SBA(7, 7) -> SF(prot,skip) -> 'DISPLAY'
java.io.ByteArrayOutputStream stream = new java.io.ByteArrayOutputStream();
stream.write(CMD_EW);
stream.write(0xC3); // WCC
// SBA(7, 1) -> address 7*80 + 1 = 561
byte[] addr1 = encodeAddress(561, 24, 80);
stream.write(ORDER_SBA);
stream.write(addr1);
// SF(prot, skip)
stream.write(ORDER_SF);
stream.write(FA_PRINTABLE | FA_PROTECT | FA_NUMERIC);
// SA(yellow) -> XA_FOREGROUND, COLOR_YELLOW (0xF6)
stream.write(ORDER_SA);
stream.write(XA_FOREGROUND);
stream.write(0xF6);
// Data: ' 4 '
stream.write(translator.stringToEbcdic(" 4 "));
// SBA(7, 7) -> address 7*80 + 7 = 567
byte[] addr2 = encodeAddress(567, 24, 80);
stream.write(ORDER_SBA);
stream.write(addr2);
// SF(prot, skip)
stream.write(ORDER_SF);
stream.write(FA_PRINTABLE | FA_PROTECT | FA_NUMERIC);
// Data: 'DISPLAY'
stream.write(translator.stringToEbcdic("DISPLAY"));
byte[] record = stream.toByteArray();
processor.processRecord(record, 0, record.length, true);
// Positions 562..566 (' 4 ') must be yellow (0xF6)
for (int i = 562; i <= 566; i++) {
assertEquals((byte) 0xF6, screen.getCell(i).fg, "Cell at " + i + " should have yellow foreground (0xF6)");
}
// Positions 568..574 ('DISPLAY') across the second SF must also retain yellow (0xF6)
for (int i = 568; i <= 574; i++) {
assertEquals((byte) 0xF6, screen.getCell(i).fg, "Cell at " + i + " ('DISPLAY') should retain yellow foreground (0xF6)");
}
// Now test that SA with XA_ALL resets character attributes to default (0)
java.io.ByteArrayOutputStream resetStream = new java.io.ByteArrayOutputStream();
resetStream.write(CMD_W);
resetStream.write(0xC3);
resetStream.write(ORDER_SA);
resetStream.write(XA_ALL);
resetStream.write(0x00);
resetStream.write(translator.stringToEbcdic("TEST"));
byte[] resetRecord = resetStream.toByteArray();
processor.processRecord(resetRecord, 0, resetRecord.length, true);
for (int i = 575; i < 579; i++) {
assertEquals((byte) 0, screen.getCell(i).fg, "Cell at " + i + " should have default foreground (0) after SA(XA_ALL)");
}
}
@Test
public void testCharacterAttributesPersistAcrossStartFieldExtended() throws java.io.IOException {
java.io.ByteArrayOutputStream stream = new java.io.ByteArrayOutputStream();
stream.write(CMD_EW);
stream.write(0xC3);
// SA(yellow)
stream.write(ORDER_SA);
stream.write(XA_FOREGROUND);
stream.write(0xF6);
// SFE with 1 pair (3270 FA)
stream.write(ORDER_SFE);
stream.write(0x01); // 1 pair
stream.write(XA_3270);
stream.write(FA_PRINTABLE);
// Data 'HELLO'
stream.write(translator.stringToEbcdic("HELLO"));
byte[] record = stream.toByteArray();
processor.processRecord(record, 0, record.length, true);
// Check cells of HELLO (positions 1..5) have fg == 0xF6
for (int i = 1; i <= 5; i++) {
assertEquals((byte) 0xF6, screen.getCell(i).fg, "Cell at " + i + " should retain yellow foreground across SFE");
}
}
@Test
public void testCharacterAttributesResetOnErase() throws java.io.IOException {
java.io.ByteArrayOutputStream stream = new java.io.ByteArrayOutputStream();
stream.write(CMD_EW);
stream.write(0xC3);
stream.write(ORDER_SA);
stream.write(XA_FOREGROUND);
stream.write(0xF6);
stream.write(translator.stringToEbcdic("A"));
processor.processRecord(stream.toByteArray(), 0, stream.size(), true);
assertEquals((byte) 0xF6, screen.getCell(0).fg);
// New EW command resets attributes
java.io.ByteArrayOutputStream ewStream = new java.io.ByteArrayOutputStream();
ewStream.write(CMD_EW);
ewStream.write(0xC3);
ewStream.write(translator.stringToEbcdic("B"));
processor.processRecord(ewStream.toByteArray(), 0, ewStream.size(), true);
assertEquals((byte) 0, screen.getCell(0).fg);
}
} }
@@ -285,6 +285,79 @@ public class GocaDecoderTest {
assertTrue(plane.hasContent(), "Expected plane to have content after image decoding"); assertTrue(plane.hasContent(), "Expected plane to have content after image decoding");
} }
@Test
public void testImonGddmImageRendering() throws Exception {
GraphicsPlane plane = new GraphicsPlane(1040, 1118);
plane.setCharDimensions(13, 26);
plane.setScreenDimensions(80, 43);
GocaDecoder decoder = new GocaDecoder(plane);
assertEquals(1040, plane.getTotalWidth());
assertEquals(1118, plane.getTotalHeight());
assertEquals(520, plane.getXMax());
assertEquals(558, plane.getYMax());
ByteArrayOutputStream out = new ByteArrayOutputStream();
// GSCP: Set Current Position (377, 494)
out.write(GocaConstants.G_GSCP);
out.write(0x04);
out.write(0x01); out.write(0x79); // X = 377
out.write(0x01); out.write(0xEE); // Y = 494
// GSCOL: Color 1 = Blue
out.write(GocaConstants.G_GSCOL);
out.write(0x01);
// G_GBIMGC (0x91): Begin Image Current Position: len=6, flags=0, w=80, h=80
out.write(GocaConstants.G_GBIMGC);
out.write(0x06);
out.write(0x00); out.write(0x00); // flags/format
out.write(0x00); out.write(0x50); // w = 80
out.write(0x00); out.write(0x50); // h = 80
// G_GIMD (0x92): 10 bytes scanline
out.write(GocaConstants.G_GIMD);
out.write(0x0A);
out.write(new byte[] { (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF });
// G_GEIMG (0x93): End Image with 2-byte operand (02 00 00 as sent by IMON)
out.write(GocaConstants.G_GEIMG);
out.write(0x02);
out.write(0x00); out.write(0x00);
// Subsequent order to verify stream synchronization: GSCOL Color 2 = Red
out.write(GocaConstants.G_GSCOL);
out.write(0x02);
// G_GBIMGC (0x91): Second image tile
out.write(GocaConstants.G_GBIMGC);
out.write(0x06);
out.write(0x00); out.write(0x00);
out.write(0x00); out.write(0x50);
out.write(0x00); out.write(0x50);
out.write(GocaConstants.G_GIMD);
out.write(0x0A);
out.write(new byte[] { (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA, (byte)0xAA });
out.write(GocaConstants.G_GEIMG);
out.write(0x02);
out.write(0x00); out.write(0x00);
byte[] stream = out.toByteArray();
decoder.decodeStream(stream, 0, stream.length);
assertTrue(plane.hasContent(), "Expected plane to have content after IMON GOCA decoding");
int mappedX = plane.mapX(377);
int mappedY = plane.mapY(494);
assertEquals(897, mappedX);
assertEquals(64, mappedY);
int[] rgb = plane.getRgbBuffer();
int pixelAtImg = rgb[mappedY * 1040 + mappedX];
assertNotEquals(0, pixelAtImg, "Pixel at image location should be rendered");
}
@Test @Test
public void testDirectObjectControlSf24() { public void testDirectObjectControlSf24() {
haus.nightmare.lib3270j.screen.ScreenBuffer sb = new haus.nightmare.lib3270j.screen.ScreenBuffer( haus.nightmare.lib3270j.screen.ScreenBuffer sb = new haus.nightmare.lib3270j.screen.ScreenBuffer(
@@ -84,6 +84,41 @@ public class TelnetFSMTest {
ConnectionConfig c4 = ConnectionConfig.parseHostString("non-e:vm.ibm.com", 23, TerminalModel.IBM_3279_4); ConnectionConfig c4 = ConnectionConfig.parseHostString("non-e:vm.ibm.com", 23, TerminalModel.IBM_3279_4);
assertFalse(c4.isTn3270eEnabled()); assertFalse(c4.isTn3270eEnabled());
assertEquals("vm.ibm.com", c4.getHost()); assertEquals("vm.ibm.com", c4.getHost());
ConnectionConfig c5 = ConnectionConfig.parseHostString("00C2@mvs.hugfreevikings.wtf:1023", 23, TerminalModel.IBM_3279_4);
assertEquals("mvs.hugfreevikings.wtf", c5.getHost());
assertEquals(1023, c5.getPort());
assertEquals("00C2", c5.getLuName());
ConnectionConfig c6 = ConnectionConfig.parseHostString("L:TSO01@secure.mvs.com:992", 23, TerminalModel.IBM_3279_4);
assertTrue(c6.isUseTls());
assertEquals("secure.mvs.com", c6.getHost());
assertEquals(992, c6.getPort());
assertEquals("TSO01", c6.getLuName());
}
@Test
public void testPlainTn3270TTypeWithLuName() {
config.setTn3270eEnabled(false);
config.setLuName("00C2");
fsm.onConnected();
feedBytes(TelnetConstants.IAC, TelnetConstants.DO, TelnetConstants.TELOPT_TTYPE);
// Host sends SB TTYPE SEND
feedBytes(TelnetConstants.IAC, TelnetConstants.SB, TelnetConstants.TELOPT_TTYPE,
TelnetConstants.TELQUAL_SEND,
TelnetConstants.IAC, TelnetConstants.SE);
boolean foundTtypeLu = false;
for (byte[] pkt : connection.sentData) {
String s = new String(pkt, java.nio.charset.StandardCharsets.ISO_8859_1);
if (s.contains("IBM-3279-4-E@00C2")) {
foundTtypeLu = true;
break;
}
}
assertTrue(foundTtypeLu, "Plain TN3270 TTYPE IS must append @<luName> when configured");
assertEquals("00C2", fsm.getConnectedLu());
} }
@Test @Test