Compare commits
3 Commits
88e3ffcbe9
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
b81a244bb9
|
|||
|
351379b5b3
|
|||
|
eba1406cbb
|
@@ -10,3 +10,4 @@ captures/
|
|||||||
*.jar
|
*.jar
|
||||||
!gradle/wrapper/gradle-wrapper.jar
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
*.txt
|
*.txt
|
||||||
|
*.log*
|
||||||
|
|||||||
@@ -45,14 +45,19 @@ if [ ! -d "$J3270_PATH/lib3270j" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Building debug APK..."
|
if [ $# -gt 0 ]; then
|
||||||
./gradlew assembleDebug --no-daemon
|
echo "Running: ./gradlew $@"
|
||||||
|
./gradlew "$@"
|
||||||
APK_PATH="$SCRIPT_DIR/build/outputs/apk/debug/a3270-debug.apk"
|
|
||||||
if [ -f "$APK_PATH" ]; then
|
|
||||||
echo "=== Build Complete ==="
|
|
||||||
echo "APK created at: $APK_PATH"
|
|
||||||
ls -lh "$APK_PATH"
|
|
||||||
else
|
else
|
||||||
echo "Build completed, but APK not found at $APK_PATH"
|
echo "Building debug APK..."
|
||||||
|
./gradlew assembleDebug --no-daemon
|
||||||
|
|
||||||
|
APK_PATH="$SCRIPT_DIR/build/outputs/apk/debug/a3270-debug.apk"
|
||||||
|
if [ -f "$APK_PATH" ]; then
|
||||||
|
echo "=== Build Complete ==="
|
||||||
|
echo "APK created at: $APK_PATH"
|
||||||
|
ls -lh "$APK_PATH"
|
||||||
|
else
|
||||||
|
echo "Build completed, but APK not found at $APK_PATH"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -115,6 +115,26 @@ esac
|
|||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Prefer compatible JDK (JDK 17 or 21) if active Java is incompatible (Java 22+)
|
||||||
|
if [ -d "/Users/rudi/.sdkman/candidates/java/21.0.6-sem" ]; then
|
||||||
|
export JAVA_HOME="/Users/rudi/.sdkman/candidates/java/21.0.6-sem"
|
||||||
|
elif [ -d "$HOME/.sdkman/candidates/java/21.0.2-open" ]; then
|
||||||
|
CURRENT_JAVA_CMD="${JAVA_HOME:+$JAVA_HOME/bin/java}"
|
||||||
|
CURRENT_JAVA_CMD="${CURRENT_JAVA_CMD:-java}"
|
||||||
|
CURRENT_VER=$("$CURRENT_JAVA_CMD" -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1)
|
||||||
|
if [ -z "$JAVA_HOME" ] || [ "$CURRENT_VER" -gt 21 ] 2>/dev/null; then
|
||||||
|
export JAVA_HOME="$HOME/.sdkman/candidates/java/21.0.2-open"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$ANDROID_HOME" ]; then
|
||||||
|
if [ -d "$HOME/Library/Android/sdk" ]; then
|
||||||
|
export ANDROID_HOME="$HOME/Library/Android/sdk"
|
||||||
|
elif [ -d "$HOME/Android/Sdk" ]; then
|
||||||
|
export ANDROID_HOME="$HOME/Android/Sdk"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Determine the Java command to use to start the JVM.
|
# Determine the Java command to use to start the JVM.
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
|||||||
@@ -789,21 +789,26 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
|||||||
val globalVerify = AppSettings.isVerifyCertsEnabled(getApplication())
|
val globalVerify = AppSettings.isVerifyCertsEnabled(getApplication())
|
||||||
val effectiveVerify = tlsVerifyCert && globalVerify
|
val effectiveVerify = tlsVerifyCert && globalVerify
|
||||||
val gMode = haus.nightmare.lib3270j.graphics.GraphicsMode.fromString(graphicsModeStr)
|
val gMode = haus.nightmare.lib3270j.graphics.GraphicsMode.fromString(graphicsModeStr)
|
||||||
|
val effectiveLu = if (luName.isNotBlank()) luName.trim() else (parsedConfig.luName ?: "").trim()
|
||||||
|
|
||||||
_isTlsActive.value = effectiveTls
|
_isTlsActive.value = effectiveTls
|
||||||
_isTlsVerified.value = effectiveVerify
|
_isTlsVerified.value = effectiveVerify
|
||||||
_oiaText.value = "Connecting to $effectiveHost:$effectivePort" + (if (effectiveTls) " [TLS]" else "") + "..."
|
_oiaText.value = "Connecting to " +
|
||||||
|
(if (effectiveLu.isNotBlank()) "$effectiveLu@" else "") +
|
||||||
|
"$effectiveHost:$effectivePort" +
|
||||||
|
(if (effectiveTls) " [TLS]" else "") + "..."
|
||||||
|
|
||||||
val config = ConnectionConfig(effectiveHost, effectivePort, model).apply {
|
val config = ConnectionConfig(effectiveHost, effectivePort, model).apply {
|
||||||
if (model == TerminalModel.IBM_DYNAMIC) {
|
if (model == TerminalModel.IBM_DYNAMIC) {
|
||||||
setDynamicDimensions(dynamicRows, dynamicCols)
|
setDynamicDimensions(dynamicRows, dynamicCols)
|
||||||
}
|
}
|
||||||
if (luName.isNotBlank()) {
|
if (effectiveLu.isNotBlank()) {
|
||||||
setLuName(luName)
|
setLuName(effectiveLu)
|
||||||
}
|
}
|
||||||
isUseTls = effectiveTls
|
isUseTls = effectiveTls
|
||||||
isTlsVerifyCert = effectiveVerify
|
isTlsVerifyCert = effectiveVerify
|
||||||
isTn3270eEnabled = effectiveTn3270e
|
isTn3270eEnabled = effectiveTn3270e
|
||||||
|
isAutoSysUnlock = true
|
||||||
graphicsMode = gMode
|
graphicsMode = gMode
|
||||||
codePage = codePageStr
|
codePage = codePageStr
|
||||||
|
|
||||||
|
|||||||
@@ -728,7 +728,7 @@ private fun copySelection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFgColorForAttribute(
|
internal fun getFgColorForAttribute(
|
||||||
ea: ExtendedAttribute,
|
ea: ExtendedAttribute,
|
||||||
currentFieldEa: ExtendedAttribute?,
|
currentFieldEa: ExtendedAttribute?,
|
||||||
currentFA: Byte,
|
currentFA: Byte,
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
package haus.nightmare.a3270
|
package haus.nightmare.a3270
|
||||||
|
|
||||||
import haus.nightmare.a3270.storage.SavedHost
|
import haus.nightmare.a3270.storage.SavedHost
|
||||||
|
import haus.nightmare.a3270.ui.getFgColorForAttribute
|
||||||
import haus.nightmare.lib3270j.ConnectionConfig
|
import haus.nightmare.lib3270j.ConnectionConfig
|
||||||
import haus.nightmare.lib3270j.TerminalModel
|
import haus.nightmare.lib3270j.TerminalModel
|
||||||
|
import haus.nightmare.lib3270j.charset.EbcdicTranslator
|
||||||
|
import haus.nightmare.lib3270j.datastream.DataStreamProcessor
|
||||||
import haus.nightmare.lib3270j.protocol.DS3270Constants.*
|
import haus.nightmare.lib3270j.protocol.DS3270Constants.*
|
||||||
|
import haus.nightmare.lib3270j.screen.ExtendedAttribute
|
||||||
|
import haus.nightmare.lib3270j.screen.ScreenBuffer
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
class DynamicAndParityFeaturesTest {
|
class DynamicAndParityFeaturesTest {
|
||||||
@@ -101,4 +107,73 @@ class DynamicAndParityFeaturesTest {
|
|||||||
assertTrue((num and FA_NUMERIC) != 0)
|
assertTrue((num and FA_NUMERIC) != 0)
|
||||||
assertTrue((hi and FA_INT_HIGH_SEL) == FA_INT_HIGH_SEL)
|
assertTrue((hi and FA_INT_HIGH_SEL) == FA_INT_HIGH_SEL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCharacterAttributesPersistAcrossFields() {
|
||||||
|
val model = TerminalModel.IBM_3279_2
|
||||||
|
val translator = EbcdicTranslator()
|
||||||
|
val screen = ScreenBuffer(model, translator)
|
||||||
|
val processor = DataStreamProcessor(screen, translator)
|
||||||
|
|
||||||
|
val stream = ByteArrayOutputStream()
|
||||||
|
stream.write(CMD_EW)
|
||||||
|
stream.write(0xC3) // WCC
|
||||||
|
|
||||||
|
// SF(prot, skip)
|
||||||
|
stream.write(ORDER_SF)
|
||||||
|
stream.write(FA_PRINTABLE or FA_PROTECT or 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 "))
|
||||||
|
|
||||||
|
// Second SF(prot, skip)
|
||||||
|
stream.write(ORDER_SF)
|
||||||
|
stream.write(FA_PRINTABLE or FA_PROTECT or FA_NUMERIC)
|
||||||
|
|
||||||
|
// Data: "DISPLAY"
|
||||||
|
stream.write(translator.stringToEbcdic("DISPLAY"))
|
||||||
|
|
||||||
|
val record = stream.toByteArray()
|
||||||
|
processor.processRecord(record, 0, record.size, true)
|
||||||
|
|
||||||
|
// " 4 " cells (indices 1..5) should have yellow foreground (0xF6)
|
||||||
|
for (i in 1..5) {
|
||||||
|
assertEquals(0xF6.toByte(), screen.getCell(i).fg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Across second SF, "DISPLAY" cells (indices 7..13) must retain yellow foreground (0xF6) (commit c28c097e)
|
||||||
|
for (i in 7..13) {
|
||||||
|
assertEquals(0xF6.toByte(), screen.getCell(i).fg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTerminalViewColorResolutionForProtectedField() {
|
||||||
|
val ea = ExtendedAttribute()
|
||||||
|
// Normal protected field attribute -> Turquoise / Cyan (0x00FFFF)
|
||||||
|
val protNormalColor = getFgColorForAttribute(ea, null, (FA_PRINTABLE or FA_PROTECT).toByte())
|
||||||
|
assertEquals(androidx.compose.ui.graphics.Color(0xFF00FFFF), protNormalColor)
|
||||||
|
|
||||||
|
// Intensified protected field attribute -> White (0xFFC0C0C0 per HOST_COLOR_WHITE / HoD palette)
|
||||||
|
val protHighColor = getFgColorForAttribute(ea, null, (FA_PRINTABLE or FA_PROTECT or FA_INT_HIGH_SEL).toByte())
|
||||||
|
assertEquals(androidx.compose.ui.graphics.Color(0xFFC0C0C0), protHighColor)
|
||||||
|
|
||||||
|
// Normal unprotected -> Green (0x00FF00)
|
||||||
|
val unprotNormalColor = getFgColorForAttribute(ea, null, FA_PRINTABLE.toByte())
|
||||||
|
assertEquals(androidx.compose.ui.graphics.Color(0xFF00FF00), unprotNormalColor)
|
||||||
|
|
||||||
|
// Intensified unprotected -> Red (0xFF0000)
|
||||||
|
val unprotHighColor = getFgColorForAttribute(ea, null, (FA_PRINTABLE or FA_INT_HIGH_SEL).toByte())
|
||||||
|
assertEquals(androidx.compose.ui.graphics.Color(0xFFFF0000), unprotHighColor)
|
||||||
|
|
||||||
|
// Character mode override (SA yellow = 0xF6)
|
||||||
|
ea.fg = 0xF6.toByte()
|
||||||
|
val yellowColor = getFgColorForAttribute(ea, null, (FA_PRINTABLE or FA_PROTECT).toByte())
|
||||||
|
assertEquals(androidx.compose.ui.graphics.Color(0xFFFFFF00), yellowColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import haus.nightmare.lib3270j.datastream.DataStreamProcessor
|
|||||||
import haus.nightmare.lib3270j.graphics.GocaDecoder
|
import haus.nightmare.lib3270j.graphics.GocaDecoder
|
||||||
import haus.nightmare.lib3270j.graphics.GraphicsPlane
|
import haus.nightmare.lib3270j.graphics.GraphicsPlane
|
||||||
import haus.nightmare.lib3270j.input.InputProcessor
|
import haus.nightmare.lib3270j.input.InputProcessor
|
||||||
|
import haus.nightmare.lib3270j.graphics.GocaConstants
|
||||||
|
import haus.nightmare.a3270.ui.calculateGridMetrics
|
||||||
import haus.nightmare.lib3270j.protocol.DS3270Constants.*
|
import haus.nightmare.lib3270j.protocol.DS3270Constants.*
|
||||||
import haus.nightmare.lib3270j.protocol.TelnetConstants.*
|
import haus.nightmare.lib3270j.protocol.TelnetConstants.*
|
||||||
import haus.nightmare.lib3270j.protocol.TN3270EConstants.*
|
import haus.nightmare.lib3270j.protocol.TN3270EConstants.*
|
||||||
@@ -175,4 +177,120 @@ class GraphicsAndSysReqTest {
|
|||||||
inputProcessor.sendAid(AID_SYSREQ)
|
inputProcessor.sendAid(AID_SYSREQ)
|
||||||
assertEquals(ConnectionState.CONNECTED_TN3270E, fsm.connectionState)
|
assertEquals(ConnectionState.CONNECTED_TN3270E, fsm.connectionState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testImonGddmImageRenderingAndCompatibility() {
|
||||||
|
val hasSetCharDimensions = try {
|
||||||
|
GraphicsPlane::class.java.getMethod("setCharDimensions", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
|
||||||
|
true
|
||||||
|
} catch (_: NoSuchMethodException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasSetCharDimensions) {
|
||||||
|
// Commit c28c097e compatibility: standard 720x384 graphics plane verification
|
||||||
|
val plane = GraphicsPlane(720, 384)
|
||||||
|
plane.setScreenDimensions(80, 24)
|
||||||
|
val decoder = GocaDecoder(plane)
|
||||||
|
assertEquals(720, plane.totalWidth)
|
||||||
|
assertEquals(384, plane.totalHeight)
|
||||||
|
|
||||||
|
val out = ByteArrayOutputStream()
|
||||||
|
out.write(GocaConstants.G_GSCP)
|
||||||
|
out.write(0x04)
|
||||||
|
out.write(0x00); out.write(0x10) // X = 16
|
||||||
|
out.write(0x00); out.write(0x10) // Y = 16
|
||||||
|
out.write(GocaConstants.G_GSCOL)
|
||||||
|
out.write(0x01)
|
||||||
|
decoder.decodeStream(out.toByteArray(), 0, out.size())
|
||||||
|
val metrics = calculateGridMetrics(1080f, 1920f, 80, 24)
|
||||||
|
assertTrue(metrics.gridWidth > 0f)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit bdfe6eec: full IMON GDDM image rendering verification
|
||||||
|
val plane = GraphicsPlane(1040, 1118)
|
||||||
|
val setDims = GraphicsPlane::class.java.getMethod("setCharDimensions", Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
|
||||||
|
setDims.invoke(plane, 13, 26)
|
||||||
|
plane.setScreenDimensions(80, 43)
|
||||||
|
val decoder = GocaDecoder(plane)
|
||||||
|
|
||||||
|
assertEquals(1040, plane.totalWidth)
|
||||||
|
assertEquals(1118, plane.totalHeight)
|
||||||
|
assertEquals(520, plane.xMax)
|
||||||
|
assertEquals(558, plane.yMax)
|
||||||
|
|
||||||
|
val out = 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)
|
||||||
|
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(byteArrayOf(0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte()))
|
||||||
|
|
||||||
|
// G_GEIMG (0x93): End Image with 2-byte operand
|
||||||
|
out.write(GocaConstants.G_GEIMG)
|
||||||
|
out.write(0x02)
|
||||||
|
out.write(0x00); out.write(0x00)
|
||||||
|
|
||||||
|
// Second image tile: Color 2 = Red
|
||||||
|
out.write(GocaConstants.G_GSCOL)
|
||||||
|
out.write(0x02)
|
||||||
|
|
||||||
|
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(byteArrayOf(0xAA.toByte(), 0xAA.toByte(), 0xAA.toByte(), 0xAA.toByte(), 0xAA.toByte(), 0xAA.toByte(), 0xAA.toByte(), 0xAA.toByte(), 0xAA.toByte(), 0xAA.toByte()))
|
||||||
|
|
||||||
|
out.write(GocaConstants.G_GEIMG)
|
||||||
|
out.write(0x02)
|
||||||
|
out.write(0x00); out.write(0x00)
|
||||||
|
|
||||||
|
val stream = out.toByteArray()
|
||||||
|
decoder.decodeStream(stream, 0, stream.size)
|
||||||
|
|
||||||
|
assertTrue(plane.hasContent())
|
||||||
|
val mappedX = plane.mapX(377)
|
||||||
|
val mappedY = plane.mapY(494)
|
||||||
|
assertEquals(897, mappedX)
|
||||||
|
assertEquals(64, mappedY)
|
||||||
|
|
||||||
|
val rgb = plane.rgbBuffer
|
||||||
|
assertNotNull(rgb)
|
||||||
|
val pixelAtImg = rgb[mappedY * 1040 + mappedX]
|
||||||
|
assertNotEquals(0, pixelAtImg)
|
||||||
|
|
||||||
|
// Verify grid metrics and UI scaling calculation against IMON canvas
|
||||||
|
val metrics = calculateGridMetrics(1080f, 1920f, 80, 43)
|
||||||
|
assertTrue(metrics.gridWidth > 0f)
|
||||||
|
assertTrue(metrics.gridHeight > 0f)
|
||||||
|
|
||||||
|
// Touch scaling test: touch in the middle of grid maps accurately to plane coordinates
|
||||||
|
val touchScreenX = metrics.offsetX + metrics.gridWidth / 2f
|
||||||
|
val touchScreenY = metrics.offsetY + metrics.gridHeight / 2f
|
||||||
|
val planeX = (((touchScreenX - metrics.offsetX) * plane.canvasWidth) / metrics.gridWidth).toInt()
|
||||||
|
val planeY = (((touchScreenY - metrics.offsetY) * plane.canvasHeight) / metrics.gridHeight).toInt()
|
||||||
|
assertEquals(plane.canvasWidth / 2, planeX)
|
||||||
|
assertEquals(plane.canvasHeight / 2, planeY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user