This commit is contained in:
+2
-2
@@ -11,8 +11,8 @@ android {
|
|||||||
applicationId "haus.nightmare.a3270"
|
applicationId "haus.nightmare.a3270"
|
||||||
minSdk 24
|
minSdk 24
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 5
|
versionCode 6
|
||||||
versionName "1.0.0"
|
versionName "1.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -460,7 +460,12 @@ fun MainScreen(
|
|||||||
autoHost.tlsVerifyCert,
|
autoHost.tlsVerifyCert,
|
||||||
autoHost.tn3270e,
|
autoHost.tn3270e,
|
||||||
autoHost.graphicsMode,
|
autoHost.graphicsMode,
|
||||||
autoHost.codePage
|
autoHost.codePage,
|
||||||
|
autoHost.proxyType,
|
||||||
|
autoHost.proxyHost,
|
||||||
|
autoHost.proxyPort,
|
||||||
|
autoHost.proxyUsername,
|
||||||
|
autoHost.proxyPassword
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -638,9 +643,9 @@ fun MainScreen(
|
|||||||
if (showConnectDialog) {
|
if (showConnectDialog) {
|
||||||
ConnectDialog(
|
ConnectDialog(
|
||||||
onDismiss = { showConnectDialog = false },
|
onDismiss = { showConnectDialog = false },
|
||||||
onConnect = { host, port, model, luName, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, cp ->
|
onConnect = { host, port, model, luName, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, cp, pType, pHost, pPort, pUser, pPass ->
|
||||||
showConnectDialog = false
|
showConnectDialog = false
|
||||||
viewModel.connect(host, port, model, luName, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, cp)
|
viewModel.connect(host, port, model, luName, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, cp, pType, pHost, pPort, pUser, pPass)
|
||||||
terminalInputViewRef?.showSoftKeyboard()
|
terminalInputViewRef?.showSoftKeyboard()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -396,7 +396,12 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
|||||||
tlsVerifyCert: Boolean = true,
|
tlsVerifyCert: Boolean = true,
|
||||||
tn3270e: Boolean = true,
|
tn3270e: Boolean = true,
|
||||||
graphicsModeStr: String = "BOTH",
|
graphicsModeStr: String = "BOTH",
|
||||||
codePageStr: String = "037"
|
codePageStr: String = "037",
|
||||||
|
proxyTypeStr: String = "NONE",
|
||||||
|
proxyHost: String = "",
|
||||||
|
proxyPort: Int = 0,
|
||||||
|
proxyUser: String = "",
|
||||||
|
proxyPass: String = ""
|
||||||
) {
|
) {
|
||||||
currentHost = host
|
currentHost = host
|
||||||
activeHostType = hostType
|
activeHostType = hostType
|
||||||
@@ -447,6 +452,16 @@ class TerminalViewModel(application: Application) : AndroidViewModel(application
|
|||||||
isTn3270eEnabled = effectiveTn3270e
|
isTn3270eEnabled = effectiveTn3270e
|
||||||
graphicsMode = gMode
|
graphicsMode = gMode
|
||||||
codePage = codePageStr
|
codePage = codePageStr
|
||||||
|
|
||||||
|
val pType = try {
|
||||||
|
ConnectionConfig.ProxyType.valueOf(proxyTypeStr.uppercase())
|
||||||
|
} catch (_: Exception) {
|
||||||
|
ConnectionConfig.ProxyType.NONE
|
||||||
|
}
|
||||||
|
if (pType != ConnectionConfig.ProxyType.NONE && proxyHost.isNotBlank()) {
|
||||||
|
val defaultPort = if (pType == ConnectionConfig.ProxyType.HTTP) 8080 else 1080
|
||||||
|
setProxy(pType, proxyHost.trim(), if (proxyPort > 0) proxyPort else defaultPort, proxyUser.ifBlank { null }, proxyPass.ifBlank { null })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config.certificateVerifier = haus.nightmare.lib3270j.tls.TlsCertificateVerifier { chain, _, exception ->
|
config.certificateVerifier = haus.nightmare.lib3270j.tls.TlsCertificateVerifier { chain, _, exception ->
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ data class SavedHost(
|
|||||||
val tlsVerifyCert: Boolean = true,
|
val tlsVerifyCert: Boolean = true,
|
||||||
val tn3270e: Boolean = true,
|
val tn3270e: Boolean = true,
|
||||||
val graphicsMode: String = "BOTH",
|
val graphicsMode: String = "BOTH",
|
||||||
val codePage: String = "037"
|
val codePage: String = "037",
|
||||||
|
val proxyType: String = "NONE",
|
||||||
|
val proxyHost: String = "",
|
||||||
|
val proxyPort: Int = 0,
|
||||||
|
val proxyUsername: String = "",
|
||||||
|
val proxyPassword: String = ""
|
||||||
) {
|
) {
|
||||||
fun toJson(): JSONObject {
|
fun toJson(): JSONObject {
|
||||||
return JSONObject().apply {
|
return JSONObject().apply {
|
||||||
@@ -36,6 +41,11 @@ data class SavedHost(
|
|||||||
put("tn3270e", tn3270e)
|
put("tn3270e", tn3270e)
|
||||||
put("graphicsMode", graphicsMode)
|
put("graphicsMode", graphicsMode)
|
||||||
put("codePage", codePage)
|
put("codePage", codePage)
|
||||||
|
put("proxyType", proxyType)
|
||||||
|
put("proxyHost", proxyHost)
|
||||||
|
put("proxyPort", proxyPort)
|
||||||
|
put("proxyUsername", proxyUsername)
|
||||||
|
put("proxyPassword", proxyPassword)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +64,12 @@ data class SavedHost(
|
|||||||
tlsVerifyCert = json.optBoolean("tlsVerifyCert", true),
|
tlsVerifyCert = json.optBoolean("tlsVerifyCert", true),
|
||||||
tn3270e = json.optBoolean("tn3270e", true),
|
tn3270e = json.optBoolean("tn3270e", true),
|
||||||
graphicsMode = json.optString("graphicsMode", "BOTH"),
|
graphicsMode = json.optString("graphicsMode", "BOTH"),
|
||||||
codePage = json.optString("codePage", "037")
|
codePage = json.optString("codePage", "037"),
|
||||||
|
proxyType = json.optString("proxyType", "NONE"),
|
||||||
|
proxyHost = json.optString("proxyHost", ""),
|
||||||
|
proxyPort = json.optInt("proxyPort", 0),
|
||||||
|
proxyUsername = json.optString("proxyUsername", ""),
|
||||||
|
proxyPassword = json.optString("proxyPassword", "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,12 @@ fun ConnectDialog(
|
|||||||
tlsVerifyCert: Boolean,
|
tlsVerifyCert: Boolean,
|
||||||
tn3270e: Boolean,
|
tn3270e: Boolean,
|
||||||
graphicsMode: String,
|
graphicsMode: String,
|
||||||
codePage: String
|
codePage: String,
|
||||||
|
proxyType: String,
|
||||||
|
proxyHost: String,
|
||||||
|
proxyPort: Int,
|
||||||
|
proxyUser: String,
|
||||||
|
proxyPass: String
|
||||||
) -> Unit
|
) -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -67,6 +72,12 @@ fun ConnectDialog(
|
|||||||
var tn3270e by remember { mutableStateOf(true) }
|
var tn3270e by remember { mutableStateOf(true) }
|
||||||
var graphicsMode by remember { mutableStateOf("BOTH") }
|
var graphicsMode by remember { mutableStateOf("BOTH") }
|
||||||
var codePage by remember { mutableStateOf(haus.nightmare.a3270.storage.AppSettings.getDefaultCodePage(context)) }
|
var codePage by remember { mutableStateOf(haus.nightmare.a3270.storage.AppSettings.getDefaultCodePage(context)) }
|
||||||
|
var proxyType by remember { mutableStateOf("NONE") }
|
||||||
|
var proxyHost by remember { mutableStateOf("") }
|
||||||
|
var proxyPortStr by remember { mutableStateOf("") }
|
||||||
|
var proxyUsername by remember { mutableStateOf("") }
|
||||||
|
var proxyPassword by remember { mutableStateOf("") }
|
||||||
|
var showProxySettings by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
fun loadProfile(saved: SavedHost) {
|
fun loadProfile(saved: SavedHost) {
|
||||||
selectedHostId = saved.id
|
selectedHostId = saved.id
|
||||||
@@ -82,6 +93,12 @@ fun ConnectDialog(
|
|||||||
tn3270e = saved.tn3270e
|
tn3270e = saved.tn3270e
|
||||||
graphicsMode = saved.graphicsMode
|
graphicsMode = saved.graphicsMode
|
||||||
codePage = saved.codePage
|
codePage = saved.codePage
|
||||||
|
proxyType = saved.proxyType
|
||||||
|
proxyHost = saved.proxyHost
|
||||||
|
proxyPortStr = if (saved.proxyPort > 0) saved.proxyPort.toString() else ""
|
||||||
|
proxyUsername = saved.proxyUsername
|
||||||
|
proxyPassword = saved.proxyPassword
|
||||||
|
showProxySettings = saved.proxyType != "NONE"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearFields() {
|
fun clearFields() {
|
||||||
@@ -98,6 +115,12 @@ fun ConnectDialog(
|
|||||||
tn3270e = true
|
tn3270e = true
|
||||||
graphicsMode = "BOTH"
|
graphicsMode = "BOTH"
|
||||||
codePage = haus.nightmare.a3270.storage.AppSettings.getDefaultCodePage(context)
|
codePage = haus.nightmare.a3270.storage.AppSettings.getDefaultCodePage(context)
|
||||||
|
proxyType = "NONE"
|
||||||
|
proxyHost = ""
|
||||||
|
proxyPortStr = ""
|
||||||
|
proxyUsername = ""
|
||||||
|
proxyPassword = ""
|
||||||
|
showProxySettings = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveCurrentProfile(): SavedHost? {
|
fun saveCurrentProfile(): SavedHost? {
|
||||||
@@ -118,7 +141,12 @@ fun ConnectDialog(
|
|||||||
tlsVerifyCert = tlsVerifyCert,
|
tlsVerifyCert = tlsVerifyCert,
|
||||||
tn3270e = tn3270e,
|
tn3270e = tn3270e,
|
||||||
graphicsMode = graphicsMode,
|
graphicsMode = graphicsMode,
|
||||||
codePage = codePage
|
codePage = codePage,
|
||||||
|
proxyType = proxyType,
|
||||||
|
proxyHost = proxyHost.trim(),
|
||||||
|
proxyPort = proxyPortStr.trim().toIntOrNull() ?: 0,
|
||||||
|
proxyUsername = proxyUsername.trim(),
|
||||||
|
proxyPassword = proxyPassword.trim()
|
||||||
)
|
)
|
||||||
HostStorage.saveHost(context, hostToSave)
|
HostStorage.saveHost(context, hostToSave)
|
||||||
savedHosts = HostStorage.getSavedHosts(context)
|
savedHosts = HostStorage.getSavedHosts(context)
|
||||||
@@ -251,7 +279,12 @@ fun ConnectDialog(
|
|||||||
hostToConn.tlsVerifyCert,
|
hostToConn.tlsVerifyCert,
|
||||||
hostToConn.tn3270e,
|
hostToConn.tn3270e,
|
||||||
hostToConn.graphicsMode,
|
hostToConn.graphicsMode,
|
||||||
hostToConn.codePage
|
hostToConn.codePage,
|
||||||
|
hostToConn.proxyType,
|
||||||
|
hostToConn.proxyHost,
|
||||||
|
hostToConn.proxyPort,
|
||||||
|
hostToConn.proxyUsername,
|
||||||
|
hostToConn.proxyPassword
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier.size(32.dp)
|
modifier = Modifier.size(32.dp)
|
||||||
@@ -611,6 +644,102 @@ fun ConnectDialog(
|
|||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Text("Auto-connect on app startup", fontSize = 12.sp, color = Color.White)
|
Text("Auto-connect on app startup", fontSize = 12.sp, color = Color.White)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Proxy Settings Expandable Section
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { showProxySettings = !showProxySettings }
|
||||||
|
) {
|
||||||
|
Checkbox(
|
||||||
|
checked = showProxySettings || proxyType != "NONE",
|
||||||
|
onCheckedChange = { checked ->
|
||||||
|
showProxySettings = checked
|
||||||
|
if (!checked) proxyType = "NONE"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(
|
||||||
|
text = if (proxyType != "NONE") "Proxy Active ($proxyType)" else "Configure Network Proxy",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = if (proxyType != "NONE") Color(0xFF339AF0) else Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showProxySettings || proxyType != "NONE") {
|
||||||
|
Card(
|
||||||
|
colors = CardDefaults.cardColors(containerColor = Color(0xFF25262B)),
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(start = 8.dp, end = 4.dp, top = 4.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
Text("Proxy Protocol:", fontSize = 11.sp, color = Color.Gray)
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
listOf("NONE", "HTTP", "SOCKS4", "SOCKS5").forEach { pType ->
|
||||||
|
val isSel = proxyType == pType
|
||||||
|
FilterChip(
|
||||||
|
selected = isSel,
|
||||||
|
onClick = { proxyType = pType },
|
||||||
|
label = { Text(pType, fontSize = 10.sp) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proxyType != "NONE") {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = proxyHost,
|
||||||
|
onValueChange = { proxyHost = it },
|
||||||
|
label = { Text("Proxy Host") },
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions,
|
||||||
|
modifier = Modifier.weight(0.7f)
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = proxyPortStr,
|
||||||
|
onValueChange = { proxyPortStr = it },
|
||||||
|
label = { Text("Port") },
|
||||||
|
placeholder = { Text(if (proxyType == "HTTP") "8080" else "1080") },
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||||
|
modifier = Modifier.weight(0.3f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = proxyUsername,
|
||||||
|
onValueChange = { proxyUsername = it },
|
||||||
|
label = { Text("User (Opt)") },
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions,
|
||||||
|
modifier = Modifier.weight(0.5f)
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = proxyPassword,
|
||||||
|
onValueChange = { proxyPassword = it },
|
||||||
|
label = { Text("Pass (Opt)") },
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions,
|
||||||
|
modifier = Modifier.weight(0.5f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -645,7 +774,12 @@ fun ConnectDialog(
|
|||||||
saved.tlsVerifyCert,
|
saved.tlsVerifyCert,
|
||||||
saved.tn3270e,
|
saved.tn3270e,
|
||||||
saved.graphicsMode,
|
saved.graphicsMode,
|
||||||
saved.codePage
|
saved.codePage,
|
||||||
|
saved.proxyType,
|
||||||
|
saved.proxyHost,
|
||||||
|
saved.proxyPort,
|
||||||
|
saved.proxyUsername,
|
||||||
|
saved.proxyPassword
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -85,7 +85,8 @@ fun ScriptDialog(
|
|||||||
) {
|
) {
|
||||||
listOf(
|
listOf(
|
||||||
"[enter]", "[tab]", "[backtab]", "[home]", "[clear]",
|
"[enter]", "[tab]", "[backtab]", "[home]", "[clear]",
|
||||||
"[reset]", "[eraseeof]", "[eraseinpt]", "[attn]", "[sysreq]"
|
"[reset]", "[eraseeof]", "[eraseinpt]", "[attn]", "[sysreq]",
|
||||||
|
"[wordleft]", "[wordright]", "[fieldend]", "[deleteword]", "[cursel]", "[lightpen]"
|
||||||
).forEach { tok ->
|
).forEach { tok ->
|
||||||
SuggestionChip(
|
SuggestionChip(
|
||||||
onClick = { insertToken(tok) },
|
onClick = { insertToken(tok) },
|
||||||
|
|||||||
@@ -26,13 +26,23 @@ class CodePageAndStorageTest {
|
|||||||
tlsVerifyCert = false,
|
tlsVerifyCert = false,
|
||||||
tn3270e = true,
|
tn3270e = true,
|
||||||
graphicsMode = "BOTH",
|
graphicsMode = "BOTH",
|
||||||
codePage = "1047"
|
codePage = "1047",
|
||||||
|
proxyType = "SOCKS5",
|
||||||
|
proxyHost = "proxy.example.com",
|
||||||
|
proxyPort = 1080,
|
||||||
|
proxyUsername = "testuser",
|
||||||
|
proxyPassword = "secretpassword"
|
||||||
)
|
)
|
||||||
|
|
||||||
val json = host.toJson()
|
val json = host.toJson()
|
||||||
assertEquals("1047", json.getString("codePage"))
|
assertEquals("1047", json.getString("codePage"))
|
||||||
assertEquals("Test Mainframe", json.getString("name"))
|
assertEquals("Test Mainframe", json.getString("name"))
|
||||||
assertTrue(json.getBoolean("autoConnect"))
|
assertTrue(json.getBoolean("autoConnect"))
|
||||||
|
assertEquals("SOCKS5", json.getString("proxyType"))
|
||||||
|
assertEquals("proxy.example.com", json.getString("proxyHost"))
|
||||||
|
assertEquals(1080, json.getInt("proxyPort"))
|
||||||
|
assertEquals("testuser", json.getString("proxyUsername"))
|
||||||
|
assertEquals("secretpassword", json.getString("proxyPassword"))
|
||||||
|
|
||||||
val deserialized = SavedHost.fromJson(json)
|
val deserialized = SavedHost.fromJson(json)
|
||||||
assertEquals(host.id, deserialized.id)
|
assertEquals(host.id, deserialized.id)
|
||||||
@@ -42,17 +52,25 @@ class CodePageAndStorageTest {
|
|||||||
assertEquals(host.model, deserialized.model)
|
assertEquals(host.model, deserialized.model)
|
||||||
assertEquals(host.codePage, deserialized.codePage)
|
assertEquals(host.codePage, deserialized.codePage)
|
||||||
assertEquals(host.graphicsMode, deserialized.graphicsMode)
|
assertEquals(host.graphicsMode, deserialized.graphicsMode)
|
||||||
|
assertEquals(host.proxyType, deserialized.proxyType)
|
||||||
|
assertEquals(host.proxyHost, deserialized.proxyHost)
|
||||||
|
assertEquals(host.proxyPort, deserialized.proxyPort)
|
||||||
|
assertEquals(host.proxyUsername, deserialized.proxyUsername)
|
||||||
|
assertEquals(host.proxyPassword, deserialized.proxyPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSavedHostDefaultCodePageFallback() {
|
fun testSavedHostDefaultCodePageFallback() {
|
||||||
// Old JSON without codePage field
|
// Old JSON without codePage or proxy fields
|
||||||
val json = org.json.JSONObject().apply {
|
val json = org.json.JSONObject().apply {
|
||||||
put("name", "Legacy")
|
put("name", "Legacy")
|
||||||
put("host", "mvs.local")
|
put("host", "mvs.local")
|
||||||
}
|
}
|
||||||
val deserialized = SavedHost.fromJson(json)
|
val deserialized = SavedHost.fromJson(json)
|
||||||
assertEquals("037", deserialized.codePage)
|
assertEquals("037", deserialized.codePage)
|
||||||
|
assertEquals("NONE", deserialized.proxyType)
|
||||||
|
assertEquals("", deserialized.proxyHost)
|
||||||
|
assertEquals(0, deserialized.proxyPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user