Debugging v0.2 features from j3270
This commit is contained in:
@@ -36,6 +36,35 @@ import org.pubvm.a3270.ui.UntrustedCertificateDialog
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
companion object {
|
||||
init {
|
||||
val rootLogger = java.util.logging.Logger.getLogger("")
|
||||
rootLogger.level = java.util.logging.Level.ALL
|
||||
for (h in rootLogger.handlers) {
|
||||
rootLogger.removeHandler(h)
|
||||
}
|
||||
rootLogger.addHandler(object : java.util.logging.Handler() {
|
||||
override fun publish(record: java.util.logging.LogRecord?) {
|
||||
if (record == null) return
|
||||
val msg = record.message ?: ""
|
||||
val tag = "a3270-" + (record.loggerName?.substringAfterLast('.') ?: "lib3270j")
|
||||
when {
|
||||
record.level.intValue() >= java.util.logging.Level.SEVERE.intValue() ->
|
||||
android.util.Log.e(tag, msg, record.thrown)
|
||||
record.level.intValue() >= java.util.logging.Level.WARNING.intValue() ->
|
||||
android.util.Log.w(tag, msg, record.thrown)
|
||||
record.level.intValue() >= java.util.logging.Level.INFO.intValue() ->
|
||||
android.util.Log.i(tag, msg, record.thrown)
|
||||
else ->
|
||||
android.util.Log.d(tag, msg, record.thrown)
|
||||
}
|
||||
}
|
||||
override fun flush() {}
|
||||
override fun close() {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private val viewModel: TerminalViewModel by viewModels()
|
||||
private val isShiftPressedState = mutableStateOf(false)
|
||||
|
||||
@@ -221,6 +250,7 @@ fun MainScreen(
|
||||
cursorAddr = cursorAddr,
|
||||
screenVersion = screenVersion,
|
||||
programSymbolManager = viewModel.getClient()?.programSymbolManager,
|
||||
graphicsPlane = viewModel.getClient()?.graphicsPlane,
|
||||
maskHiddenFields = maskHiddenInput,
|
||||
blinkCursor = cursorBlink,
|
||||
onTapAddress = { addr ->
|
||||
|
||||
@@ -221,10 +221,14 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
||||
}
|
||||
|
||||
fun resolveUntrustedCert(accept: Boolean) {
|
||||
val prompt = _untrustedCertPrompt.value
|
||||
if (prompt != null) {
|
||||
prompt.deferred.complete(accept)
|
||||
_untrustedCertPrompt.value = null
|
||||
try {
|
||||
val prompt = _untrustedCertPrompt.value
|
||||
if (prompt != null) {
|
||||
prompt.deferred.complete(accept)
|
||||
_untrustedCertPrompt.value = null
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
log.warning("Error resolving untrusted cert: ${t.message}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +255,6 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
||||
tlsVerifyCert: Boolean = true,
|
||||
graphicsModeStr: String = "BOTH"
|
||||
) {
|
||||
if (_connectionState.value.isConnected()) return
|
||||
currentHost = host
|
||||
activeHostType = hostType
|
||||
lastScreenContentHash = 0
|
||||
@@ -261,6 +264,15 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
// Always disconnect any existing client before establishing a new connection
|
||||
if (client != null || _connectionState.value.isConnected()) {
|
||||
try {
|
||||
client?.disconnect()
|
||||
} catch (_: Exception) {}
|
||||
client = null
|
||||
_connectionState.value = ConnectionState.NOT_CONNECTED
|
||||
}
|
||||
|
||||
val model = when (modelNum) {
|
||||
3 -> TerminalModel.IBM_3279_3
|
||||
4 -> TerminalModel.IBM_3279_4
|
||||
@@ -303,8 +315,14 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
||||
exception = exception,
|
||||
deferred = deferred
|
||||
)
|
||||
val accepted = kotlinx.coroutines.runBlocking { deferred.await() }
|
||||
_untrustedCertPrompt.value = null
|
||||
val accepted = try {
|
||||
kotlinx.coroutines.runBlocking { deferred.await() }
|
||||
} catch (t: Throwable) {
|
||||
log.warning("Certificate verifier prompt interrupted or failed: ${t.message}")
|
||||
false
|
||||
} finally {
|
||||
_untrustedCertPrompt.value = null
|
||||
}
|
||||
accepted
|
||||
}
|
||||
|
||||
@@ -379,10 +397,10 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
||||
_screenBuffer.value = newClient.screenBuffer
|
||||
|
||||
newClient.connect()
|
||||
} catch (e: Exception) {
|
||||
} catch (e: Throwable) {
|
||||
log.severe("Failed to connect: ${e.message}")
|
||||
_connectionState.value = ConnectionState.NOT_CONNECTED
|
||||
_oiaText.value = "Failed: ${e.localizedMessage ?: e.message}"
|
||||
_oiaText.value = "Failed: ${e.localizedMessage ?: e.message ?: "Connection error"}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,13 @@ object HostStorage {
|
||||
val jsonArray = JSONArray(jsonStr)
|
||||
for (i in 0 until jsonArray.length()) {
|
||||
val obj = jsonArray.getJSONObject(i)
|
||||
list.add(SavedHost.fromJson(obj))
|
||||
val saved = SavedHost.fromJson(obj)
|
||||
val effective = if (saved.graphicsMode == "PROGRAMMED_SYMBOLS") {
|
||||
saved.copy(graphicsMode = "BOTH")
|
||||
} else {
|
||||
saved
|
||||
}
|
||||
list.add(effective)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
||||
@@ -298,9 +298,9 @@ fun ConnectDialog(
|
||||
) {
|
||||
Text("Graphics:", fontSize = 11.sp, color = Color.Gray)
|
||||
listOf(
|
||||
"BOTH" to "Both",
|
||||
"VECTOR_GRAPHICS" to "Vector",
|
||||
"PROGRAMMED_SYMBOLS" to "Symbols",
|
||||
"VECTOR_GRAPHICS" to "Vector",
|
||||
"BOTH" to "Both",
|
||||
"NONE" to "None"
|
||||
).forEach { (modeKey, modeLabel) ->
|
||||
FilterChip(
|
||||
|
||||
@@ -59,6 +59,7 @@ fun TerminalView(
|
||||
cursorAddr: Int,
|
||||
screenVersion: Long,
|
||||
programSymbolManager: org.lib3270j.graphics.ProgramSymbolManager? = null,
|
||||
graphicsPlane: org.lib3270j.graphics.GraphicsPlane? = null,
|
||||
maskHiddenFields: Boolean = true,
|
||||
blinkCursor: Boolean = true,
|
||||
onTapAddress: (Int) -> Unit,
|
||||
@@ -304,28 +305,8 @@ fun TerminalView(
|
||||
if (slot != null) {
|
||||
val symWidth = slot.width
|
||||
val symHeight = slot.height
|
||||
val pixelData = slot.pixelData
|
||||
if (pixelData != null && symWidth > 0 && symHeight > 0) {
|
||||
val fgArgb = fgColor.toArgb()
|
||||
val bgArgb = bgColor.toArgb()
|
||||
val isTriple = slot.isTriplePlane
|
||||
val rgbArray = IntArray(symWidth * symHeight)
|
||||
for (py in 0 until symHeight) {
|
||||
for (px in 0 until symWidth) {
|
||||
val pIdx = py * symWidth + px
|
||||
val v = if (pIdx < pixelData.size) (pixelData[pIdx].toInt() and 0xFF) else 0
|
||||
if (v == 0) {
|
||||
rgbArray[pIdx] = bgArgb
|
||||
} else if (!isTriple) {
|
||||
rgbArray[pIdx] = fgArgb
|
||||
} else {
|
||||
val redVal = if ((v and 0x01) != 0) 255 else 0
|
||||
val greenVal = if ((v and 0x02) != 0) 255 else 0
|
||||
val blueVal = if ((v and 0x04) != 0) 255 else 0
|
||||
rgbArray[pIdx] = (0xFF shl 24) or (redVal shl 16) or (greenVal shl 8) or blueVal
|
||||
}
|
||||
}
|
||||
}
|
||||
val rgbArray = slot.getRgbPixels(fgColor.toArgb(), bgColor.toArgb())
|
||||
if (rgbArray != null && symWidth > 0 && symHeight > 0) {
|
||||
val bmp = android.graphics.Bitmap.createBitmap(rgbArray, symWidth, symHeight, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
drawContext.canvas.nativeCanvas.drawBitmap(
|
||||
bmp,
|
||||
@@ -362,6 +343,25 @@ fun TerminalView(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw Vector Graphics Plane overlay if present
|
||||
if (graphicsPlane != null && graphicsPlane.hasContent()) {
|
||||
val gridW = (cols * cellWidth).toInt()
|
||||
val gridH = (rows * cellHeight).toInt()
|
||||
if (gridW > 0 && gridH > 0) {
|
||||
graphicsPlane.resize(gridW, gridH)
|
||||
val rgb = graphicsPlane.rgbBuffer
|
||||
if (rgb != null) {
|
||||
val bmp = android.graphics.Bitmap.createBitmap(rgb, gridW, gridH, android.graphics.Bitmap.Config.ARGB_8888)
|
||||
drawContext.canvas.nativeCanvas.drawBitmap(
|
||||
bmp,
|
||||
null,
|
||||
android.graphics.RectF(0f, 0f, gridW.toFloat(), gridH.toFloat()),
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Context Menu Popup
|
||||
|
||||
@@ -42,30 +42,54 @@ fun UntrustedCertificateDialog(
|
||||
append("\n\n")
|
||||
}
|
||||
if (cert != null) {
|
||||
append("Subject:\n ")
|
||||
append(cert.subjectX500Principal.name)
|
||||
append("\n\n")
|
||||
try {
|
||||
append("Subject:\n ")
|
||||
append(cert.subjectX500Principal?.name ?: cert.subjectDN?.name ?: "Unknown")
|
||||
append("\n\n")
|
||||
} catch (t: Throwable) {
|
||||
append("Subject: ${t.message ?: "Unavailable"}\n\n")
|
||||
}
|
||||
|
||||
append("Issuer:\n ")
|
||||
append(cert.issuerX500Principal.name)
|
||||
append("\n\n")
|
||||
try {
|
||||
append("Issuer:\n ")
|
||||
append(cert.issuerX500Principal?.name ?: cert.issuerDN?.name ?: "Unknown")
|
||||
append("\n\n")
|
||||
} catch (t: Throwable) {
|
||||
append("Issuer: ${t.message ?: "Unavailable"}\n\n")
|
||||
}
|
||||
|
||||
append("Validity:\n From: ")
|
||||
append(sdf.format(cert.notBefore))
|
||||
append("\n To: ")
|
||||
append(sdf.format(cert.notAfter))
|
||||
append("\n\n")
|
||||
try {
|
||||
append("Validity:\n From: ")
|
||||
append(cert.notBefore?.let { sdf.format(it) } ?: "Unknown")
|
||||
append("\n To: ")
|
||||
append(cert.notAfter?.let { sdf.format(it) } ?: "Unknown")
|
||||
append("\n\n")
|
||||
} catch (t: Throwable) {
|
||||
append("Validity: ${t.message ?: "Unavailable"}\n\n")
|
||||
}
|
||||
|
||||
append("Serial Number:\n ")
|
||||
append(cert.serialNumber.toString(16).uppercase(Locale.US))
|
||||
append("\n\n")
|
||||
try {
|
||||
append("Serial Number:\n ")
|
||||
append(cert.serialNumber?.toString(16)?.uppercase(Locale.US) ?: "Unknown")
|
||||
append("\n\n")
|
||||
} catch (t: Throwable) {
|
||||
append("Serial Number: ${t.message ?: "Unavailable"}\n\n")
|
||||
}
|
||||
|
||||
append("SHA-256 Fingerprint:\n ")
|
||||
append(computeFingerprint(cert, "SHA-256"))
|
||||
append("\n\n")
|
||||
try {
|
||||
append("SHA-256 Fingerprint:\n ")
|
||||
append(computeFingerprint(cert, "SHA-256"))
|
||||
append("\n\n")
|
||||
} catch (t: Throwable) {
|
||||
append("SHA-256 Fingerprint: ${t.message ?: "Unavailable"}\n\n")
|
||||
}
|
||||
|
||||
append("SHA-1 Fingerprint:\n ")
|
||||
append(computeFingerprint(cert, "SHA-1"))
|
||||
try {
|
||||
append("SHA-1 Fingerprint:\n ")
|
||||
append(computeFingerprint(cert, "SHA-1"))
|
||||
} catch (t: Throwable) {
|
||||
append("SHA-1 Fingerprint: ${t.message ?: "Unavailable"}")
|
||||
}
|
||||
} else {
|
||||
append("No peer certificate information available.")
|
||||
}
|
||||
@@ -161,8 +185,8 @@ private fun computeFingerprint(cert: X509Certificate, algorithm: String): String
|
||||
return try {
|
||||
val md = MessageDigest.getInstance(algorithm)
|
||||
val digest = md.digest(cert.encoded)
|
||||
digest.joinToString(":") { "%02X".format(it) }
|
||||
} catch (e: Exception) {
|
||||
digest.joinToString(":") { "%02X".format(it.toInt() and 0xFF) }
|
||||
} catch (e: Throwable) {
|
||||
"Unable to compute fingerprint: ${e.message}"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user