1 Commits

Author SHA1 Message Date
rudi 052a6619ae Add some cleaning
Build and Test a3270 / Build Android APK (push) Successful in 6m39s
2026-08-21 18:56:22 -04:00
3 changed files with 42 additions and 19 deletions
+1 -9
View File
@@ -2,7 +2,7 @@
[![Build & Test](https://git.hugfreevikings.wtf/rudi/a3270/actions/workflows/build.yaml/badge.svg)](https://git.hugfreevikings.wtf/rudi/a3270/actions)
[![Android](https://img.shields.io/badge/Android-API%2024%2B%20%7C%20Compose-green.svg)](https://developer.android.com)
[![License](https://img.shields.io/badge/License-MIT%20%2F%20BSD-green.svg)](LICENSE)
[![License: Unlicense](https://img.shields.io/badge/License-Unlicense-blue.svg)](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.
@@ -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
- [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.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
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.LocalSoftwareKeyboardController
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.unit.dp
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.SavedHost
private val TerminalKeyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.None,
autoCorrect = false,
keyboardType = KeyboardType.Ascii
)
@Composable
fun ConnectDialog(
onDismiss: () -> Unit,
@@ -87,13 +97,14 @@ fun ConnectDialog(
}
fun saveCurrentProfile(): SavedHost? {
val port = portStr.toIntOrNull() ?: if (useTls) 992 else 23
if (host.isBlank()) return null
val nameToSave = profileName.ifBlank { "${host.trim()}:$port" }
val port = portStr.trim().toIntOrNull() ?: if (useTls) 992 else 23
val trimmedHost = host.trim()
if (trimmedHost.isBlank()) return null
val nameToSave = profileName.trim().ifBlank { "$trimmedHost:$port" }
val hostToSave = SavedHost(
id = selectedHostId ?: java.util.UUID.randomUUID().toString(),
name = nameToSave,
host = host.trim(),
host = trimmedHost,
port = port,
model = modelNum,
luName = luName.trim(),
@@ -273,6 +284,7 @@ fun ConnectDialog(
onValueChange = { profileName = it },
label = { Text("Profile Name (Optional)") },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
modifier = Modifier.fillMaxWidth()
)
@@ -282,18 +294,20 @@ fun ConnectDialog(
) {
OutlinedTextField(
value = host,
onValueChange = { host = it },
onValueChange = { host = it.filterNot { c -> c.isWhitespace() } },
label = { Text("Host / IP Address") },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
modifier = Modifier
.weight(0.68f)
.focusRequester(focusRequester)
)
OutlinedTextField(
value = portStr,
onValueChange = { portStr = it },
onValueChange = { portStr = it.filter { c -> c.isDigit() } },
label = { Text("Port") },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
modifier = Modifier.weight(0.32f)
)
}
@@ -417,6 +431,7 @@ fun ConnectDialog(
onValueChange = { luName = it },
label = { Text("LU Name / Device Pool (Optional)") },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Done),
modifier = Modifier.fillMaxWidth()
)
@@ -2,6 +2,7 @@ package org.pubvm.a3270.ui
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
@@ -9,11 +10,20 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.sp
import org.lib3270j.ft.FTConfig
import org.pubvm.a3270.FTProgressState
private val TerminalKeyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.None,
autoCorrect = false,
keyboardType = KeyboardType.Ascii
)
@Composable
fun FileTransferDialog(
initialHostType: String = "TSO",
@@ -101,6 +111,7 @@ fun FileTransferDialog(
label = { Text("Host Dataset / File Name") },
placeholder = { Text(if (hostType == FTConfig.HostType.TSO) "'USER.DATA'" else "PROFILE EXEC A") },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
modifier = Modifier.fillMaxWidth()
)
@@ -111,6 +122,9 @@ fun FileTransferDialog(
placeholder = { Text("sample.txt") },
supportingText = { Text("Relative names will be stored in app Downloads/Files storage", fontSize = 10.sp) },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(
imeAction = if (!isReceive && hostType == FTConfig.HostType.TSO) ImeAction.Next else ImeAction.Done
),
modifier = Modifier.fillMaxWidth()
)
@@ -208,16 +222,18 @@ fun FileTransferDialog(
) {
OutlinedTextField(
value = lreclStr,
onValueChange = { lreclStr = it },
onValueChange = { lreclStr = it.filter { c -> c.isDigit() } },
label = { Text("LRECL", fontSize = 11.sp) },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Next),
modifier = Modifier.weight(1f)
)
OutlinedTextField(
value = blksizeStr,
onValueChange = { blksizeStr = it },
onValueChange = { blksizeStr = it.filter { c -> c.isDigit() } },
label = { Text("BLKSIZE", fontSize = 11.sp) },
singleLine = true,
keyboardOptions = TerminalKeyboardOptions.copy(imeAction = ImeAction.Done),
modifier = Modifier.weight(1f)
)
}
@@ -237,8 +253,8 @@ fun FileTransferDialog(
setExistAction(if (overwrite) FTConfig.ExistAction.REPLACE else FTConfig.ExistAction.KEEP)
if (!isReceive && hostType == FTConfig.HostType.TSO) {
setRecfm(recfm)
lreclStr.toIntOrNull()?.let { setLrecl(it) }
blksizeStr.toIntOrNull()?.let { setBlksize(it) }
lreclStr.trim().toIntOrNull()?.let { setLrecl(it) }
blksizeStr.trim().toIntOrNull()?.let { setBlksize(it) }
}
}
onStartTransfer(config)