Compare commits
1 Commits
v0.2.2-beta
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
052a6619ae
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://git.hugfreevikings.wtf/rudi/a3270/actions)
|
[](https://git.hugfreevikings.wtf/rudi/a3270/actions)
|
||||||
[](https://developer.android.com)
|
[](https://developer.android.com)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
|
|
||||||
An **x3270-aligned IBM 3270 mainframe terminal emulator for Android**, written in Kotlin using **Jetpack Compose** and powered by the `lib3270j` protocol engine from [j3270](https://git.hugfreevikings.wtf/rudi/j3270). Designed for mobile access to IBM z/OS, z/VM, CMS, and TSO systems over TN3270 and TN3270E.
|
An **x3270-aligned IBM 3270 mainframe terminal emulator for Android**, written in Kotlin using **Jetpack Compose** and powered by the `lib3270j` protocol engine from [j3270](https://git.hugfreevikings.wtf/rudi/j3270). Designed for mobile access to IBM z/OS, z/VM, CMS, and TSO systems over TN3270 and TN3270E.
|
||||||
|
|
||||||
@@ -93,14 +93,6 @@ build/outputs/apk/debug/a3270-debug.apk
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! Please feel free to open a bug report or feature request via the Gitea issue tracker.
|
|
||||||
|
|
||||||
I have contributed no code to this project, it was entirely written by LLMs with my guidance only.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 Acknowledgements
|
## 📜 Acknowledgements
|
||||||
|
|
||||||
- [j3270](https://git.hugfreevikings.wtf/rudi/j3270) — Desktop emulator and `lib3270j` protocol engine
|
- [j3270](https://git.hugfreevikings.wtf/rudi/j3270) — Desktop emulator and `lib3270j` protocol engine
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
@@ -17,6 +18,9 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -25,6 +29,12 @@ import androidx.compose.ui.window.DialogProperties
|
|||||||
import org.pubvm.a3270.storage.HostStorage
|
import org.pubvm.a3270.storage.HostStorage
|
||||||
import org.pubvm.a3270.storage.SavedHost
|
import org.pubvm.a3270.storage.SavedHost
|
||||||
|
|
||||||
|
private val TerminalKeyboardOptions = KeyboardOptions(
|
||||||
|
capitalization = KeyboardCapitalization.None,
|
||||||
|
autoCorrect = false,
|
||||||
|
keyboardType = KeyboardType.Ascii
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ConnectDialog(
|
fun ConnectDialog(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
@@ -87,13 +97,14 @@ fun ConnectDialog(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveCurrentProfile(): SavedHost? {
|
fun saveCurrentProfile(): SavedHost? {
|
||||||
val port = portStr.toIntOrNull() ?: if (useTls) 992 else 23
|
val port = portStr.trim().toIntOrNull() ?: if (useTls) 992 else 23
|
||||||
if (host.isBlank()) return null
|
val trimmedHost = host.trim()
|
||||||
val nameToSave = profileName.ifBlank { "${host.trim()}:$port" }
|
if (trimmedHost.isBlank()) return null
|
||||||
|
val nameToSave = profileName.trim().ifBlank { "$trimmedHost:$port" }
|
||||||
val hostToSave = SavedHost(
|
val hostToSave = SavedHost(
|
||||||
id = selectedHostId ?: java.util.UUID.randomUUID().toString(),
|
id = selectedHostId ?: java.util.UUID.randomUUID().toString(),
|
||||||
name = nameToSave,
|
name = nameToSave,
|
||||||
host = host.trim(),
|
host = trimmedHost,
|
||||||
port = port,
|
port = port,
|
||||||
model = modelNum,
|
model = modelNum,
|
||||||
luName = luName.trim(),
|
luName = luName.trim(),
|
||||||
@@ -273,6 +284,7 @@ fun ConnectDialog(
|
|||||||
onValueChange = { profileName = it },
|
onValueChange = { profileName = it },
|
||||||
label = { Text("Profile Name (Optional)") },
|
label = { Text("Profile Name (Optional)") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -282,18 +294,20 @@ fun ConnectDialog(
|
|||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = host,
|
value = host,
|
||||||
onValueChange = { host = it },
|
onValueChange = { host = it.filterNot { c -> c.isWhitespace() } },
|
||||||
label = { Text("Host / IP Address") },
|
label = { Text("Host / IP Address") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(0.68f)
|
.weight(0.68f)
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = portStr,
|
value = portStr,
|
||||||
onValueChange = { portStr = it },
|
onValueChange = { portStr = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("Port") },
|
label = { Text("Port") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
|
||||||
modifier = Modifier.weight(0.32f)
|
modifier = Modifier.weight(0.32f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -417,6 +431,7 @@ fun ConnectDialog(
|
|||||||
onValueChange = { luName = it },
|
onValueChange = { luName = it },
|
||||||
label = { Text("LU Name / Device Pool (Optional)") },
|
label = { Text("LU Name / Device Pool (Optional)") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Done),
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package org.pubvm.a3270.ui
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
@@ -9,11 +10,20 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import org.lib3270j.ft.FTConfig
|
import org.lib3270j.ft.FTConfig
|
||||||
import org.pubvm.a3270.FTProgressState
|
import org.pubvm.a3270.FTProgressState
|
||||||
|
|
||||||
|
private val TerminalKeyboardOptions = KeyboardOptions(
|
||||||
|
capitalization = KeyboardCapitalization.None,
|
||||||
|
autoCorrect = false,
|
||||||
|
keyboardType = KeyboardType.Ascii
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FileTransferDialog(
|
fun FileTransferDialog(
|
||||||
initialHostType: String = "TSO",
|
initialHostType: String = "TSO",
|
||||||
@@ -101,6 +111,7 @@ fun FileTransferDialog(
|
|||||||
label = { Text("Host Dataset / File Name") },
|
label = { Text("Host Dataset / File Name") },
|
||||||
placeholder = { Text(if (hostType == FTConfig.HostType.TSO) "'USER.DATA'" else "PROFILE EXEC A") },
|
placeholder = { Text(if (hostType == FTConfig.HostType.TSO) "'USER.DATA'" else "PROFILE EXEC A") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,6 +122,9 @@ fun FileTransferDialog(
|
|||||||
placeholder = { Text("sample.txt") },
|
placeholder = { Text("sample.txt") },
|
||||||
supportingText = { Text("Relative names will be stored in app Downloads/Files storage", fontSize = 10.sp) },
|
supportingText = { Text("Relative names will be stored in app Downloads/Files storage", fontSize = 10.sp) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions.copy(
|
||||||
|
imeAction = if (!isReceive && hostType == FTConfig.HostType.TSO) ImeAction.Next else ImeAction.Done
|
||||||
|
),
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -208,16 +222,18 @@ fun FileTransferDialog(
|
|||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = lreclStr,
|
value = lreclStr,
|
||||||
onValueChange = { lreclStr = it },
|
onValueChange = { lreclStr = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("LRECL", fontSize = 11.sp) },
|
label = { Text("LRECL", fontSize = 11.sp) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = blksizeStr,
|
value = blksizeStr,
|
||||||
onValueChange = { blksizeStr = it },
|
onValueChange = { blksizeStr = it.filter { c -> c.isDigit() } },
|
||||||
label = { Text("BLKSIZE", fontSize = 11.sp) },
|
label = { Text("BLKSIZE", fontSize = 11.sp) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
|
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Done),
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -237,8 +253,8 @@ fun FileTransferDialog(
|
|||||||
setExistAction(if (overwrite) FTConfig.ExistAction.REPLACE else FTConfig.ExistAction.KEEP)
|
setExistAction(if (overwrite) FTConfig.ExistAction.REPLACE else FTConfig.ExistAction.KEEP)
|
||||||
if (!isReceive && hostType == FTConfig.HostType.TSO) {
|
if (!isReceive && hostType == FTConfig.HostType.TSO) {
|
||||||
setRecfm(recfm)
|
setRecfm(recfm)
|
||||||
lreclStr.toIntOrNull()?.let { setLrecl(it) }
|
lreclStr.trim().toIntOrNull()?.let { setLrecl(it) }
|
||||||
blksizeStr.toIntOrNull()?.let { setBlksize(it) }
|
blksizeStr.trim().toIntOrNull()?.let { setBlksize(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onStartTransfer(config)
|
onStartTransfer(config)
|
||||||
|
|||||||
Reference in New Issue
Block a user