updtaes from j3270
This commit is contained in:
@@ -45,6 +45,10 @@ if [ ! -d "$J3270_PATH/lib3270j" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
echo "Running: ./gradlew $@"
|
||||||
|
./gradlew "$@"
|
||||||
|
else
|
||||||
echo "Building debug APK..."
|
echo "Building debug APK..."
|
||||||
./gradlew assembleDebug
|
./gradlew assembleDebug
|
||||||
|
|
||||||
@@ -56,3 +60,4 @@ if [ -f "$APK_PATH" ]; then
|
|||||||
else
|
else
|
||||||
echo "Build completed, but APK not found at $APK_PATH"
|
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
|
||||||
|
|||||||
@@ -257,10 +257,11 @@ fun TerminalView(
|
|||||||
val gridW = metrics.gridWidth.toInt()
|
val gridW = metrics.gridWidth.toInt()
|
||||||
val gridH = metrics.gridHeight.toInt()
|
val gridH = metrics.gridHeight.toInt()
|
||||||
if (gridW > 0 && gridH > 0) {
|
if (gridW > 0 && gridH > 0) {
|
||||||
graphicsPlane.resize(gridW, gridH)
|
val gWidth = graphicsPlane.canvasWidth
|
||||||
|
val gHeight = graphicsPlane.canvasHeight
|
||||||
val rgb = graphicsPlane.rgbBuffer
|
val rgb = graphicsPlane.rgbBuffer
|
||||||
if (rgb != null) {
|
if (rgb != null && gWidth > 0 && gHeight > 0) {
|
||||||
val bmp = android.graphics.Bitmap.createBitmap(rgb, gridW, gridH, android.graphics.Bitmap.Config.ARGB_8888)
|
val bmp = android.graphics.Bitmap.createBitmap(rgb, gWidth, gHeight, android.graphics.Bitmap.Config.ARGB_8888)
|
||||||
drawContext.canvas.nativeCanvas.drawBitmap(
|
drawContext.canvas.nativeCanvas.drawBitmap(
|
||||||
bmp,
|
bmp,
|
||||||
null,
|
null,
|
||||||
@@ -728,7 +729,7 @@ private fun copySelection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFgColorForAttribute(ea: ExtendedAttribute, currentFieldEa: ExtendedAttribute?, currentFA: Byte): Color {
|
internal fun getFgColorForAttribute(ea: ExtendedAttribute, currentFieldEa: ExtendedAttribute?, currentFA: Byte): Color {
|
||||||
val fg = if (ea.fg != 0.toByte()) (ea.fg.toInt() and 0xFF)
|
val fg = if (ea.fg != 0.toByte()) (ea.fg.toInt() and 0xFF)
|
||||||
else if (currentFieldEa != null && currentFieldEa.fg != 0.toByte()) (currentFieldEa.fg.toInt() and 0xFF)
|
else if (currentFieldEa != null && currentFieldEa.fg != 0.toByte()) (currentFieldEa.fg.toInt() and 0xFF)
|
||||||
else 0
|
else 0
|
||||||
|
|||||||
@@ -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