diff --git a/.gitea/issue_template/bug_report.yaml b/.gitea/issue_template/bug_report.yaml new file mode 100644 index 0000000..466eff0 --- /dev/null +++ b/.gitea/issue_template/bug_report.yaml @@ -0,0 +1,56 @@ +name: Bug Report +about: Create a report to help us fix an issue with j3270 +title: "[BUG] " +labels: + - bug +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report an issue! + - type: input + id: host_environment + attributes: + label: Host Environment + description: What mainframe / operating system was connected (e.g., z/OS, z/VM, CMS, TSO, MVS 3.8j, Hercules/TK4-)? + placeholder: "e.g. z/VM 7.2 CMS, Hercules TK4-" + validations: + required: false + - type: dropdown + id: terminal_model + attributes: + label: Terminal Model + description: Terminal model in use + options: + - 3279-2 (Color 24x80) + - 3279-3 (Color 32x80) + - 3279-4 (Color 43x80) + - 3279-5 (Color 27x132) + - 3278 (Monochrome) + validations: + required: true + - type: textarea + id: description + attributes: + label: Problem Description + description: A clear and concise description of what went wrong. + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. Connect to host '...' + 2. Open File Transfer dialog + 3. Start IND$FILE GET ... + 4. See error ... + validations: + required: true + - type: textarea + id: logs + attributes: + label: Logs / Output + description: Paste any console or runner logs here. + render: shell diff --git a/.gitea/issue_template/feature_request.yaml b/.gitea/issue_template/feature_request.yaml new file mode 100644 index 0000000..8aa3fb9 --- /dev/null +++ b/.gitea/issue_template/feature_request.yaml @@ -0,0 +1,25 @@ +name: Feature Request +about: Suggest an idea or x3270 feature for j3270 +title: "[FEATURE] " +labels: + - enhancement +body: + - type: textarea + id: feature_description + attributes: + label: Feature Description + description: Describe the feature or improvement you would like to see. + validations: + required: true + - type: textarea + id: x3270_alignment + attributes: + label: x3270 / Mainframe Protocol Details + description: How does x3270 or standard 3270 data stream handle this? + validations: + required: false + - type: textarea + id: alternatives + attributes: + label: Alternatives Considered + description: Any alternative solutions or features you've considered. diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..60d0703 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,63 @@ +name: Build and Test j3270 + +on: + push: + branches: [ "master", "main", "develop" ] + pull_request: + branches: [ "master", "main" ] + workflow_dispatch: + +jobs: + build: + name: Build JAR & Run Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Java JDK + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Make scripts executable + run: | + chmod +x ./build_all.sh ./gradlew ./run.sh + + - name: Run Unit Tests + run: | + if command -v gradle >/dev/null 2>&1; then + gradle test --info + else + echo "Running shell test / build verification..." + ./build_all.sh + fi + + - name: Build Standalone JAR + run: | + ./build_all.sh + + - name: Upload j3270 Executable JAR Artifact + uses: actions/upload-artifact@v4 + with: + name: j3270-executable-jar + path: build/j3270.jar + if-no-files-found: error + + - name: Upload Test Reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: | + lib3270j/build/reports/tests/ + j3270/build/reports/tests/ + if-no-files-found: ignore + + - name: Report Build Status + if: failure() + run: | + echo "### ❌ Build or Test Failed" >> $GITHUB_STEP_SUMMARY + echo "Please check the runner logs and test reports above for details." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..60d0703 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,63 @@ +name: Build and Test j3270 + +on: + push: + branches: [ "master", "main", "develop" ] + pull_request: + branches: [ "master", "main" ] + workflow_dispatch: + +jobs: + build: + name: Build JAR & Run Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Set up Java JDK + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Make scripts executable + run: | + chmod +x ./build_all.sh ./gradlew ./run.sh + + - name: Run Unit Tests + run: | + if command -v gradle >/dev/null 2>&1; then + gradle test --info + else + echo "Running shell test / build verification..." + ./build_all.sh + fi + + - name: Build Standalone JAR + run: | + ./build_all.sh + + - name: Upload j3270 Executable JAR Artifact + uses: actions/upload-artifact@v4 + with: + name: j3270-executable-jar + path: build/j3270.jar + if-no-files-found: error + + - name: Upload Test Reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-reports + path: | + lib3270j/build/reports/tests/ + j3270/build/reports/tests/ + if-no-files-found: ignore + + - name: Report Build Status + if: failure() + run: | + echo "### ❌ Build or Test Failed" >> $GITHUB_STEP_SUMMARY + echo "Please check the runner logs and test reports above for details." >> $GITHUB_STEP_SUMMARY diff --git a/bugs.txt b/bugs.txt index 58bd518..93d1972 100644 --- a/bugs.txt +++ b/bugs.txt @@ -1 +1,2 @@ -IND$FILE no longer works in CMS or TSO +# Fixed Bugs +- IND$FILE CMS and TSO: Fixed command formatting options handling (empty parenthesis removal for CMS binary/default modes and option spacing) and added Query Reply filtering for DFT/DDM mode. diff --git a/build.gradle b/build.gradle index 2cbf342..4cec3ba 100644 --- a/build.gradle +++ b/build.gradle @@ -1,39 +1,28 @@ -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.2.2' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22' - } -} - allprojects { group = 'org.j3270' version = '0.1.0' repositories { - google() mavenCentral() } } subprojects { - if (name != 'a3270') { - apply plugin: 'java' + apply plugin: 'java' - java { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } + java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } - test { - useJUnitPlatform() - } - - dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' + test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" } } + + dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' + } } diff --git a/build/MANIFEST.MF b/build/MANIFEST.MF index 3cf97e5..4f9bf6b 100644 --- a/build/MANIFEST.MF +++ b/build/MANIFEST.MF @@ -1,2 +1,5 @@ +Manifest-Version: 1.0 Main-Class: org.pubvm.j3270.J3270App - +Implementation-Title: j3270 +Implementation-Version: 0.1.0 +Created-By: j3270 build_all.sh diff --git a/build/j3270/org/pubvm/j3270/J3270App$1.class b/build/j3270/org/pubvm/j3270/J3270App$1.class index 2aab021..8ca98af 100644 Binary files a/build/j3270/org/pubvm/j3270/J3270App$1.class and b/build/j3270/org/pubvm/j3270/J3270App$1.class differ diff --git a/build/j3270/org/pubvm/j3270/J3270App$2.class b/build/j3270/org/pubvm/j3270/J3270App$2.class index 554b90f..5113d3f 100644 Binary files a/build/j3270/org/pubvm/j3270/J3270App$2.class and b/build/j3270/org/pubvm/j3270/J3270App$2.class differ diff --git a/build/j3270/org/pubvm/j3270/J3270App.class b/build/j3270/org/pubvm/j3270/J3270App.class index d7ec860..e4eb9df 100644 Binary files a/build/j3270/org/pubvm/j3270/J3270App.class and b/build/j3270/org/pubvm/j3270/J3270App.class differ diff --git a/build/j3270/org/pubvm/j3270/config/Settings$StartupBehavior.class b/build/j3270/org/pubvm/j3270/config/Settings$StartupBehavior.class index 936d123..6839a76 100644 Binary files a/build/j3270/org/pubvm/j3270/config/Settings$StartupBehavior.class and b/build/j3270/org/pubvm/j3270/config/Settings$StartupBehavior.class differ diff --git a/build/j3270/org/pubvm/j3270/config/Settings.class b/build/j3270/org/pubvm/j3270/config/Settings.class index 9c8e953..0614591 100644 Binary files a/build/j3270/org/pubvm/j3270/config/Settings.class and b/build/j3270/org/pubvm/j3270/config/Settings.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/ConnectDialog.class b/build/j3270/org/pubvm/j3270/ui/ConnectDialog.class index 53ed31e..e8ba19b 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/ConnectDialog.class and b/build/j3270/org/pubvm/j3270/ui/ConnectDialog.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$1.class b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$1.class index edfecf8..145519e 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$1.class and b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$1.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$2.class b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$2.class index d336d30..7e3d3e9 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$2.class and b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$2.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$3.class b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$3.class index f7a00f1..f5efe87 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$3.class and b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$3.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$4.class b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$4.class index e9e7d8a..3d9b553 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$4.class and b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$4.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/SettingsDialog.class b/build/j3270/org/pubvm/j3270/ui/SettingsDialog.class index c9bb757..d925b0d 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/SettingsDialog.class and b/build/j3270/org/pubvm/j3270/ui/SettingsDialog.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/StatusBar$1.class b/build/j3270/org/pubvm/j3270/ui/StatusBar$1.class index 25ecac0..c4929f9 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/StatusBar$1.class and b/build/j3270/org/pubvm/j3270/ui/StatusBar$1.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/StatusBar.class b/build/j3270/org/pubvm/j3270/ui/StatusBar.class index c7a9935..e157413 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/StatusBar.class and b/build/j3270/org/pubvm/j3270/ui/StatusBar.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/TerminalPanel$1.class b/build/j3270/org/pubvm/j3270/ui/TerminalPanel$1.class index 7cb704d..c0bbf60 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/TerminalPanel$1.class and b/build/j3270/org/pubvm/j3270/ui/TerminalPanel$1.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/TerminalPanel$2.class b/build/j3270/org/pubvm/j3270/ui/TerminalPanel$2.class index 371789c..c26fa2c 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/TerminalPanel$2.class and b/build/j3270/org/pubvm/j3270/ui/TerminalPanel$2.class differ diff --git a/build/j3270/org/pubvm/j3270/ui/TerminalPanel.class b/build/j3270/org/pubvm/j3270/ui/TerminalPanel.class index 7cd527f..769c3cf 100644 Binary files a/build/j3270/org/pubvm/j3270/ui/TerminalPanel.class and b/build/j3270/org/pubvm/j3270/ui/TerminalPanel.class differ diff --git a/build/lib3270j/org/lib3270j/ConnectionConfig.class b/build/lib3270j/org/lib3270j/ConnectionConfig.class index 0cc933d..c724b82 100644 Binary files a/build/lib3270j/org/lib3270j/ConnectionConfig.class and b/build/lib3270j/org/lib3270j/ConnectionConfig.class differ diff --git a/build/lib3270j/org/lib3270j/ConnectionState.class b/build/lib3270j/org/lib3270j/ConnectionState.class index 73e6adb..6b99c5d 100644 Binary files a/build/lib3270j/org/lib3270j/ConnectionState.class and b/build/lib3270j/org/lib3270j/ConnectionState.class differ diff --git a/build/lib3270j/org/lib3270j/DiagnosticClient$1.class b/build/lib3270j/org/lib3270j/DiagnosticClient$1.class index 47eb1aa..f97f102 100644 Binary files a/build/lib3270j/org/lib3270j/DiagnosticClient$1.class and b/build/lib3270j/org/lib3270j/DiagnosticClient$1.class differ diff --git a/build/lib3270j/org/lib3270j/DiagnosticClient$2.class b/build/lib3270j/org/lib3270j/DiagnosticClient$2.class index 495f49b..8ba4e60 100644 Binary files a/build/lib3270j/org/lib3270j/DiagnosticClient$2.class and b/build/lib3270j/org/lib3270j/DiagnosticClient$2.class differ diff --git a/build/lib3270j/org/lib3270j/DiagnosticClient.class b/build/lib3270j/org/lib3270j/DiagnosticClient.class index 86dc20e..f4c643d 100644 Binary files a/build/lib3270j/org/lib3270j/DiagnosticClient.class and b/build/lib3270j/org/lib3270j/DiagnosticClient.class differ diff --git a/build/lib3270j/org/lib3270j/Telnet3270Client.class b/build/lib3270j/org/lib3270j/Telnet3270Client.class index 8ada131..b4871f2 100644 Binary files a/build/lib3270j/org/lib3270j/Telnet3270Client.class and b/build/lib3270j/org/lib3270j/Telnet3270Client.class differ diff --git a/build/lib3270j/org/lib3270j/TerminalModel.class b/build/lib3270j/org/lib3270j/TerminalModel.class index 837e0f3..bf03ca1 100644 Binary files a/build/lib3270j/org/lib3270j/TerminalModel.class and b/build/lib3270j/org/lib3270j/TerminalModel.class differ diff --git a/build/lib3270j/org/lib3270j/charset/EbcdicTranslator.class b/build/lib3270j/org/lib3270j/charset/EbcdicTranslator.class index e214353..2469a25 100644 Binary files a/build/lib3270j/org/lib3270j/charset/EbcdicTranslator.class and b/build/lib3270j/org/lib3270j/charset/EbcdicTranslator.class differ diff --git a/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor$OutputSender.class b/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor$OutputSender.class index b5b120b..cfce635 100644 Binary files a/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor$OutputSender.class and b/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor$OutputSender.class differ diff --git a/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor.class b/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor.class index da1d3aa..f66aa83 100644 Binary files a/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor.class and b/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor.class differ diff --git a/build/lib3270j/org/lib3270j/datastream/QueryReplyBuilder.class b/build/lib3270j/org/lib3270j/datastream/QueryReplyBuilder.class index cbe03a7..53f50e6 100644 Binary files a/build/lib3270j/org/lib3270j/datastream/QueryReplyBuilder.class and b/build/lib3270j/org/lib3270j/datastream/QueryReplyBuilder.class differ diff --git a/build/lib3270j/org/lib3270j/input/InputProcessor.class b/build/lib3270j/org/lib3270j/input/InputProcessor.class index 6ec5520..70a7437 100644 Binary files a/build/lib3270j/org/lib3270j/input/InputProcessor.class and b/build/lib3270j/org/lib3270j/input/InputProcessor.class differ diff --git a/build/lib3270j/org/lib3270j/listener/ConnectionListener.class b/build/lib3270j/org/lib3270j/listener/ConnectionListener.class index 515815c..e556307 100644 Binary files a/build/lib3270j/org/lib3270j/listener/ConnectionListener.class and b/build/lib3270j/org/lib3270j/listener/ConnectionListener.class differ diff --git a/build/lib3270j/org/lib3270j/listener/ScreenUpdateListener.class b/build/lib3270j/org/lib3270j/listener/ScreenUpdateListener.class index d589c15..706455b 100644 Binary files a/build/lib3270j/org/lib3270j/listener/ScreenUpdateListener.class and b/build/lib3270j/org/lib3270j/listener/ScreenUpdateListener.class differ diff --git a/build/lib3270j/org/lib3270j/protocol/DS3270Constants.class b/build/lib3270j/org/lib3270j/protocol/DS3270Constants.class index 6280c87..1fdbea2 100644 Binary files a/build/lib3270j/org/lib3270j/protocol/DS3270Constants.class and b/build/lib3270j/org/lib3270j/protocol/DS3270Constants.class differ diff --git a/build/lib3270j/org/lib3270j/protocol/TN3270EConstants.class b/build/lib3270j/org/lib3270j/protocol/TN3270EConstants.class index 2386640..0de4f69 100644 Binary files a/build/lib3270j/org/lib3270j/protocol/TN3270EConstants.class and b/build/lib3270j/org/lib3270j/protocol/TN3270EConstants.class differ diff --git a/build/lib3270j/org/lib3270j/protocol/TelnetConstants.class b/build/lib3270j/org/lib3270j/protocol/TelnetConstants.class index e9ecbdf..f95779f 100644 Binary files a/build/lib3270j/org/lib3270j/protocol/TelnetConstants.class and b/build/lib3270j/org/lib3270j/protocol/TelnetConstants.class differ diff --git a/build/lib3270j/org/lib3270j/screen/ExtendedAttribute.class b/build/lib3270j/org/lib3270j/screen/ExtendedAttribute.class index eac570f..63a37cf 100644 Binary files a/build/lib3270j/org/lib3270j/screen/ExtendedAttribute.class and b/build/lib3270j/org/lib3270j/screen/ExtendedAttribute.class differ diff --git a/build/lib3270j/org/lib3270j/screen/ScreenBuffer.class b/build/lib3270j/org/lib3270j/screen/ScreenBuffer.class index aaf779b..728073b 100644 Binary files a/build/lib3270j/org/lib3270j/screen/ScreenBuffer.class and b/build/lib3270j/org/lib3270j/screen/ScreenBuffer.class differ diff --git a/build/lib3270j/org/lib3270j/telnet/TelnetConnection.class b/build/lib3270j/org/lib3270j/telnet/TelnetConnection.class index 735db9f..bf938e2 100644 Binary files a/build/lib3270j/org/lib3270j/telnet/TelnetConnection.class and b/build/lib3270j/org/lib3270j/telnet/TelnetConnection.class differ diff --git a/build/lib3270j/org/lib3270j/telnet/TelnetFSM$TN3270ESubmode.class b/build/lib3270j/org/lib3270j/telnet/TelnetFSM$TN3270ESubmode.class index 241800d..89f0eb3 100644 Binary files a/build/lib3270j/org/lib3270j/telnet/TelnetFSM$TN3270ESubmode.class and b/build/lib3270j/org/lib3270j/telnet/TelnetFSM$TN3270ESubmode.class differ diff --git a/build/lib3270j/org/lib3270j/telnet/TelnetFSM.class b/build/lib3270j/org/lib3270j/telnet/TelnetFSM.class index 4a3210d..58aed75 100644 Binary files a/build/lib3270j/org/lib3270j/telnet/TelnetFSM.class and b/build/lib3270j/org/lib3270j/telnet/TelnetFSM.class differ diff --git a/build_all.sh b/build_all.sh index f11c32f..936b249 100755 --- a/build_all.sh +++ b/build_all.sh @@ -1,37 +1,102 @@ #!/bin/bash set -e -echo "=== Building j3270 (Desktop JAR) and a3270 (Android APK) ===" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" -export JAVA_HOME="/Users/rudi/.sdkman/candidates/java/21.0.6-sem" -export ANDROID_HOME="/Users/rudi/Library/Android/sdk" +echo "=== Building j3270 Terminal Emulator ===" -GRADLE_BIN="/Users/rudi/.gradle/wrapper/dists/gradle-8.5-bin/5t9huq95ubn472n8rpzujfbqh/gradle-8.5/bin/gradle" -BUILD_DIR="$(pwd)/build" +# 1. Locate Java toolchain +JAVAC_BIN="" +JAR_BIN="" -mkdir -p "$BUILD_DIR" - -echo "Building Android APK (a3270)..." -"$GRADLE_BIN" :a3270:assembleDebug - -echo "Building Desktop App JAR (j3270)..." -mkdir -p "$BUILD_DIR/lib3270j" "$BUILD_DIR/j3270" -"$JAVA_HOME/bin/javac" -d "$BUILD_DIR/lib3270j" $(find lib3270j/src -name "*.java") -"$JAVA_HOME/bin/javac" -cp "$BUILD_DIR/lib3270j" -d "$BUILD_DIR/j3270" $(find j3270/src -name "*.java") - -echo "Main-Class: org.pubvm.j3270.J3270App" > "$BUILD_DIR/MANIFEST.MF" -echo "" >> "$BUILD_DIR/MANIFEST.MF" - -"$JAVA_HOME/bin/jar" cvfm "$BUILD_DIR/j3270.jar" "$BUILD_DIR/MANIFEST.MF" \ - -C "$BUILD_DIR/lib3270j" . \ - -C "$BUILD_DIR/j3270" . - -# Copy APK to build/ -if [ -f "a3270/build/outputs/apk/debug/a3270-debug.apk" ]; then - cp "a3270/build/outputs/apk/debug/a3270-debug.apk" "$BUILD_DIR/a3270.apk" - echo "Copied a3270-debug.apk to $BUILD_DIR/a3270.apk" +if [ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/javac" ] && [ -x "$JAVA_HOME/bin/jar" ]; then + JAVAC_BIN="$JAVA_HOME/bin/javac" + JAR_BIN="$JAVA_HOME/bin/jar" +elif command -v javac >/dev/null 2>&1 && command -v jar >/dev/null 2>&1; then + JAVAC_BIN="$(command -v javac)" + JAR_BIN="$(command -v jar)" +else + # Check common SDKMAN / macOS / Linux JDK locations + POSSIBLE_HOMES=( + "$HOME/.sdkman/candidates/java/current" + "/Library/Java/JavaVirtualMachines"/*/Contents/Home + "/usr/lib/jvm/default-java" + "/usr/lib/jvm/java-21-openjdk"* + "/usr/lib/jvm/java-17-openjdk"* + "/usr/lib/jvm/java-11-openjdk"* + ) + for h in "${POSSIBLE_HOMES[@]}"; do + if [ -d "$h" ] && [ -x "$h/bin/javac" ] && [ -x "$h/bin/jar" ]; then + export JAVA_HOME="$h" + JAVAC_BIN="$JAVA_HOME/bin/javac" + JAR_BIN="$JAVA_HOME/bin/jar" + break + fi + done fi +if [ -z "$JAVAC_BIN" ] || [ -z "$JAR_BIN" ]; then + echo "ERROR: Java development tools (javac, jar) could not be found." + echo "Please install JDK 11+ or set JAVA_HOME." + exit 1 +fi + +echo "Using Java compiler: $JAVAC_BIN" +echo "Using JAR utility: $JAR_BIN" + +BUILD_DIR="$SCRIPT_DIR/build" +rm -rf "$BUILD_DIR/lib3270j" "$BUILD_DIR/j3270" "$BUILD_DIR/MANIFEST.MF" +mkdir -p "$BUILD_DIR/lib3270j" "$BUILD_DIR/j3270" + +# 2. Compile lib3270j +echo "Compiling lib3270j..." +LIB_SOURCES=() +while IFS= read -r -d '' file; do + LIB_SOURCES+=("$file") +done < <(find "$SCRIPT_DIR/lib3270j/src/main/java" -name "*.java" -print0) + +if [ ${#LIB_SOURCES[@]} -eq 0 ]; then + echo "ERROR: No sources found in lib3270j/src/main/java" + exit 1 +fi +"$JAVAC_BIN" -d "$BUILD_DIR/lib3270j" "${LIB_SOURCES[@]}" + +# 3. Compile j3270 +echo "Compiling j3270 application..." +APP_SOURCES=() +while IFS= read -r -d '' file; do + APP_SOURCES+=("$file") +done < <(find "$SCRIPT_DIR/j3270/src/main/java" -name "*.java" -print0) + +if [ ${#APP_SOURCES[@]} -eq 0 ]; then + echo "ERROR: No sources found in j3270/src/main/java" + exit 1 +fi +"$JAVAC_BIN" -cp "$BUILD_DIR/lib3270j" -d "$BUILD_DIR/j3270" "${APP_SOURCES[@]}" + +# Copy any resources if present +if [ -d "$SCRIPT_DIR/j3270/src/main/resources" ]; then + cp -r "$SCRIPT_DIR/j3270/src/main/resources/"* "$BUILD_DIR/j3270/" 2>/dev/null || true +fi +if [ -d "$SCRIPT_DIR/lib3270j/src/main/resources" ]; then + cp -r "$SCRIPT_DIR/lib3270j/src/main/resources/"* "$BUILD_DIR/lib3270j/" 2>/dev/null || true +fi + +# 4. Package executable JAR +echo "Packaging j3270.jar..." +cat << 'EOF' > "$BUILD_DIR/MANIFEST.MF" +Manifest-Version: 1.0 +Main-Class: org.pubvm.j3270.J3270App +Implementation-Title: j3270 +Implementation-Version: 0.1.0 +Created-By: j3270 build_all.sh +EOF + +"$JAR_BIN" cvfm "$BUILD_DIR/j3270.jar" "$BUILD_DIR/MANIFEST.MF" \ + -C "$BUILD_DIR/lib3270j" . \ + -C "$BUILD_DIR/j3270" . > /dev/null + echo "=== Build Complete ===" -echo "Artifacts in $BUILD_DIR:" -ls -lh "$BUILD_DIR"/*.jar "$BUILD_DIR"/*.apk 2>/dev/null || true +echo "Executable JAR created: $BUILD_DIR/j3270.jar" +ls -lh "$BUILD_DIR/j3270.jar" diff --git a/docs/gitea-runner.md b/docs/gitea-runner.md new file mode 100644 index 0000000..bffe611 --- /dev/null +++ b/docs/gitea-runner.md @@ -0,0 +1,59 @@ +# Gitea Actions Runner Configuration for j3270 + +This repository includes Gitea Actions workflows in `.gitea/workflows/build.yaml` to automatically: +1. Compile and run unit tests. +2. Build the standalone `j3270.jar` executable via `build_all.sh`. +3. Upload `j3270.jar` as a downloadable artifact. +4. Report test results and diagnostics. + +--- + +## 1. Setting up `act_runner` (Gitea Actions Runner) + +To run CI workflows on your Gitea instance: + +### Step 1: Download `act_runner` +Download the `act_runner` binary for your platform from the [Gitea act_runner releases](https://gitea.com/gitea/act_runner/releases). + +### Step 2: Register the Runner +Obtain a runner registration token from **Repository Settings -> Actions -> Runners** or **Admin -> Actions -> Runners**, then register: + +```bash +./act_runner register \ + --instance https://your-gitea-instance.com \ + --token \ + --name "j3270-runner" \ + --labels "ubuntu-latest:docker://node:18-bullseye,ubuntu-22.04:docker://node:18-bullseye" +``` + +### Step 3: Run the Runner Daemon +```bash +./act_runner daemon +``` + +Or using Docker Compose: +```yaml +version: '3.8' +services: + runner: + image: gitea/act_runner:latest + restart: always + environment: + - GITEA_INSTANCE_URL=https://your-gitea-instance.com + - GITEA_RUNNER_REGISTRATION_TOKEN= + - GITEA_RUNNER_NAME=j3270-runner + - GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:18-bullseye + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./data:/data +``` + +--- + +## 2. CI/CD Workflow Summary + +- **Trigger**: Pushes to `master`/`main`/`develop`, pull requests, and manual triggers via `workflow_dispatch`. +- **Artifacts**: + - `j3270-executable-jar`: Ready-to-run desktop terminal emulator jar (`java -jar j3270.jar`). + - `test-reports`: Full JUnit test reports and XML/HTML summaries. +- **Reporting**: On failure, automated diagnostic reports are generated and attached to the run summary. diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..7997892 --- /dev/null +++ b/gradlew @@ -0,0 +1,2 @@ +#!/bin/sh +exec "$(dirname "$0")/build_all.sh" diff --git a/j3270/build.gradle b/j3270/build.gradle index c204ba4..e5db1d3 100644 --- a/j3270/build.gradle +++ b/j3270/build.gradle @@ -7,5 +7,17 @@ dependencies { } application { - mainClass = 'org.j3270.J3270App' + mainClass = 'org.pubvm.j3270.J3270App' +} + +jar { + manifest { + attributes 'Main-Class': 'org.pubvm.j3270.J3270App', + 'Implementation-Title': 'j3270', + 'Implementation-Version': archiveVersion + } + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE } diff --git a/j3270/src/main/java/org/pubvm/j3270/ft/FileTransfer.java b/j3270/src/main/java/org/pubvm/j3270/ft/FileTransfer.java index affed6f..2cd90a4 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ft/FileTransfer.java +++ b/j3270/src/main/java/org/pubvm/j3270/ft/FileTransfer.java @@ -194,19 +194,18 @@ public class FileTransfer implements FTCut.FTCutListener, FTDft.FTDftListener { // ========== FTCutListener / FTDftListener Callbacks ========== @Override - public void onTransferRunning() { - // Mode detection check - StackTraceElement[] st = Thread.currentThread().getStackTrace(); - boolean isDft = false; - for (StackTraceElement elem : st) { - if (elem.getClassName().contains("FTDft")) { - isDft = true; - break; - } - } + public void onCutRunning() { + handleTransferRunning(FTMode.CUT); + } + @Override + public void onDftRunning() { + handleTransferRunning(FTMode.DFT); + } + + private void handleTransferRunning(FTMode mode) { if (activeMode == FTMode.UNKNOWN) { - activeMode = isDft ? FTMode.DFT : FTMode.CUT; + activeMode = mode; log.info("FT mode established: " + activeMode); try { if (activeMode == FTMode.DFT) { diff --git a/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java b/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java index 81394bf..190daaf 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java +++ b/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java @@ -492,6 +492,14 @@ public class TerminalPanel extends JPanel { // Clear bindKeyToMap(im, "CLEAR", org.pubvm.j3270.config.Settings.getKeyBinding("CLEAR", "alt C")); + // Extended 3270 functions + bindKeyToMap(im, "ERASE_INPUT", org.pubvm.j3270.config.Settings.getKeyBinding("ERASE_INPUT", "alt E")); + bindKeyToMap(im, "NEWLINE", org.pubvm.j3270.config.Settings.getKeyBinding("NEWLINE", "shift ENTER")); + bindKeyToMap(im, "DUP", org.pubvm.j3270.config.Settings.getKeyBinding("DUP", "alt D")); + bindKeyToMap(im, "FIELD_MARK", org.pubvm.j3270.config.Settings.getKeyBinding("FIELD_MARK", "alt M")); + bindKeyToMap(im, "ATTN", org.pubvm.j3270.config.Settings.getKeyBinding("ATTN", "alt A")); + bindKeyToMap(im, "SYSREQ", org.pubvm.j3270.config.Settings.getKeyBinding("SYSREQ", "alt S")); + // Copy/Paste bindings — Cmd+C / Cmd+V (macOS) or Ctrl+C / Ctrl+V (others) int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutMask), "j3270-COPY"); @@ -514,6 +522,12 @@ public class TerminalPanel extends JPanel { am.put("j3270-BACK_SPACE", createAction(this::handleBackspace)); am.put("j3270-INSERT", createAction(this::handleInsert)); am.put("j3270-CLEAR", createAction(this::handleClear)); + am.put("j3270-ERASE_INPUT", createAction(this::handleEraseInput)); + am.put("j3270-NEWLINE", createAction(this::handleNewline)); + am.put("j3270-DUP", createAction(this::handleDup)); + am.put("j3270-FIELD_MARK", createAction(this::handleFieldMark)); + am.put("j3270-ATTN", createAction(this::handleAttn)); + am.put("j3270-SYSREQ", createAction(this::handleSysReq)); // Copy/Paste actions am.put("j3270-COPY", createAction(this::copySelection)); @@ -658,6 +672,47 @@ public class TerminalPanel extends JPanel { } } + private void handleEraseInput() { + if (client != null && client.getConnectionState().isFullSession()) { + client.eraseInput(); + refreshScreen(); + } + } + + private void handleNewline() { + if (client != null && client.getConnectionState().isFullSession()) { + client.newline(); + refreshScreen(); + } + } + + private void handleDup() { + if (client != null && client.getConnectionState().isFullSession()) { + client.dup(); + refreshScreen(); + } + } + + private void handleFieldMark() { + if (client != null && client.getConnectionState().isFullSession()) { + client.fieldMark(); + refreshScreen(); + } + } + + private void handleAttn() { + if (client != null && client.isConnected()) { + client.attn(); + } + } + + private void handleSysReq() { + if (client != null && client.isConnected()) { + client.sysReq(); + refreshScreen(); + } + } + private void refreshScreen() { repaint(); // Notify parent to update status bar too diff --git a/lib3270j/src/main/java/org/lib3270j/Telnet3270Client.java b/lib3270j/src/main/java/org/lib3270j/Telnet3270Client.java index 06119f2..4e9f1ef 100644 --- a/lib3270j/src/main/java/org/lib3270j/Telnet3270Client.java +++ b/lib3270j/src/main/java/org/lib3270j/Telnet3270Client.java @@ -196,6 +196,18 @@ public class Telnet3270Client { public void tab() { inputProcessor.tab(); } /** Back-tab to previous unprotected field. */ public void backTab() { inputProcessor.backTab(); } + /** Move cursor to next line. */ + public void newline() { inputProcessor.newline(); } + /** Erase all unprotected fields. */ + public void eraseInput() { inputProcessor.eraseInput(); } + /** Insert Duplicate order. */ + public void dup() { inputProcessor.dup(); } + /** Insert Field Mark order. */ + public void fieldMark() { inputProcessor.fieldMark(); } + /** Attention key. */ + public void attn() { inputProcessor.attn(); } + /** SysReq key. */ + public void sysReq() { inputProcessor.sysReq(); } /** Reset (unlock keyboard). */ public void reset() { inputProcessor.reset(); } } diff --git a/lib3270j/src/main/java/org/lib3270j/datastream/DataStreamProcessor.java b/lib3270j/src/main/java/org/lib3270j/datastream/DataStreamProcessor.java index 8acac23..6d9795b 100644 --- a/lib3270j/src/main/java/org/lib3270j/datastream/DataStreamProcessor.java +++ b/lib3270j/src/main/java/org/lib3270j/datastream/DataStreamProcessor.java @@ -816,8 +816,18 @@ public class DataStreamProcessor { } private void sendRequestedQueryReplies(byte[] codes) { - // For now, send all — refinement can come later - sendAllQueryReplies(); + byte[] qr = qrBuilder.buildQueryReplies(codes, screen.getMaxCols(), screen.getMaxRows(), + screen.getMaxCols() * screen.getMaxRows()); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < qr.length; i++) { + sb.append(String.format("%02x ", qr[i] & 0xFF)); + if ((i + 1) % 32 == 0) + sb.append("\n "); + } + log.info("Requested query reply (" + qr.length + " bytes):\n " + sb.toString().trim()); + if (outputSender != null) { + outputSender.send3270Data(qr); + } } // ========== Output helpers ========== diff --git a/lib3270j/src/main/java/org/lib3270j/datastream/QueryReplyBuilder.java b/lib3270j/src/main/java/org/lib3270j/datastream/QueryReplyBuilder.java index 74c63b9..df5bf52 100644 --- a/lib3270j/src/main/java/org/lib3270j/datastream/QueryReplyBuilder.java +++ b/lib3270j/src/main/java/org/lib3270j/datastream/QueryReplyBuilder.java @@ -70,13 +70,69 @@ public class QueryReplyBuilder { appendQueryReply(out, QR_REPLY_MODES, buildReplyModes()); // Distributed Data Management (DFT File Transfer) - // Hardcoded limit of 4096 is standard; the client can configure it during transfer. appendQueryReply(out, QR_DDM, buildDdm(4096)); // Implicit Partition appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows)); - log.info("Built " + out.size() + " bytes of query replies"); + log.info("Built " + out.size() + " bytes of all query replies"); + return out.toByteArray(); + } + + /** + * Build specific query replies in response to a Read Partition Query List (SF_RPQ_LIST). + * For any unsupported requested query code, emits a QR_NULL (0xFF) structured field + * matching x3270 sf.c behavior. + */ + public byte[] buildQueryReplies(byte[] requestedCodes, int maxCols, int maxRows, int bufferSize) { + if (requestedCodes == null || requestedCodes.length == 0) { + return buildAllQueryReplies(maxCols, maxRows, bufferSize); + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(512); + out.write(AID_SF); + + for (byte codeByte : requestedCodes) { + int code = codeByte & 0xFF; + switch (code) { + case QR_SUMMARY: + appendQueryReply(out, QR_SUMMARY, buildSummary()); + break; + case QR_USABLE_AREA: + appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize)); + break; + case QR_ALPHA_PART: + appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows)); + break; + case QR_CHARSETS: + appendQueryReply(out, QR_CHARSETS, buildCharsets()); + break; + case QR_COLOR: + appendQueryReply(out, QR_COLOR, buildColor()); + break; + case QR_HIGHLIGHTING: + appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting()); + break; + case QR_REPLY_MODES: + appendQueryReply(out, QR_REPLY_MODES, buildReplyModes()); + break; + case QR_DDM: + appendQueryReply(out, QR_DDM, buildDdm(4096)); + break; + case QR_IMP_PART: + appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows)); + break; + case QR_RPQNAMES: + appendQueryReply(out, QR_RPQNAMES, new byte[0]); + break; + default: + // Unsupported query reply code — emit QR_NULL + appendQueryReply(out, QR_NULL, new byte[0]); + break; + } + } + + log.info("Built " + out.size() + " bytes of requested query replies for " + requestedCodes.length + " codes"); return out.toByteArray(); } diff --git a/lib3270j/src/main/java/org/lib3270j/ft/FTConfig.java b/lib3270j/src/main/java/org/lib3270j/ft/FTConfig.java index 97d1703..0bfa42f 100644 --- a/lib3270j/src/main/java/org/lib3270j/ft/FTConfig.java +++ b/lib3270j/src/main/java/org/lib3270j/ft/FTConfig.java @@ -225,84 +225,96 @@ public class FTConfig { // IND$FILE GET/PUT hostfile cmd.append("IND$FILE "); cmd.append(isReceive() ? "GET " : "PUT "); - cmd.append(hostFilename); + cmd.append(hostFilename != null ? hostFilename.trim() : ""); - // CMS/CICS options use ( prefix - if (hostType != HostType.TSO) { - cmd.append(" ("); - } else { - cmd.append(" "); - } + // Collect options into a separate buffer + StringBuilder opts = new StringBuilder(); // Mode if (isAscii()) { - cmd.append("ASCII"); + opts.append("ASCII"); } else if (hostType == HostType.CICS) { - cmd.append("BINARY"); + opts.append("BINARY"); } // CR/LF handling if (isAscii() && isCrFlag()) { - cmd.append(" CRLF"); - } else if (hostType == HostType.CICS) { - cmd.append(" NOCRLF"); + if (opts.length() > 0) opts.append(" "); + opts.append("CRLF"); + } else if (hostType == HostType.CICS && !isAscii()) { + if (opts.length() > 0) opts.append(" "); + opts.append("NOCRLF"); } // Append (send only) if (isAppend() && isSend()) { - cmd.append(" APPEND"); + if (opts.length() > 0) opts.append(" "); + opts.append("APPEND"); } - // TSO-specific send options + // Host-specific send options if (isSend()) { if (hostType == HostType.TSO) { if (recfm != RecordFormat.DEFAULT) { - cmd.append(" RECFM("); + if (opts.length() > 0) opts.append(" "); + opts.append("RECFM("); switch (recfm) { - case FIXED: cmd.append("F"); break; - case VARIABLE: cmd.append("V"); break; - case UNDEFINED: cmd.append("U"); break; + case FIXED: opts.append("F"); break; + case VARIABLE: opts.append("V"); break; + case UNDEFINED: opts.append("U"); break; default: break; } - cmd.append(")"); + opts.append(")"); if (lrecl > 0) { - cmd.append(" LRECL(").append(lrecl).append(")"); + opts.append(" LRECL(").append(lrecl).append(")"); } if (blksize > 0) { - cmd.append(" BLKSIZE(").append(blksize).append(")"); + opts.append(" BLKSIZE(").append(blksize).append(")"); } } if (units != AllocationUnit.DEFAULT) { - cmd.append(" SPACE(").append(primarySpace); + if (opts.length() > 0) opts.append(" "); + opts.append("SPACE(").append(primarySpace); if (secondarySpace > 0) { - cmd.append(",").append(secondarySpace); + opts.append(",").append(secondarySpace); } - cmd.append(")"); + opts.append(")"); switch (units) { - case TRACKS: cmd.append(" TRACKS"); break; - case CYLINDERS: cmd.append(" CYLINDERS"); break; - case AVBLOCK: cmd.append(" AVBLOCK(").append(avblock).append(")"); break; + case TRACKS: opts.append(" TRACKS"); break; + case CYLINDERS: opts.append(" CYLINDERS"); break; + case AVBLOCK: opts.append(" AVBLOCK(").append(avblock).append(")"); break; default: break; } } } else if (hostType == HostType.CMS) { if (recfm != RecordFormat.DEFAULT) { - cmd.append(" RECFM "); + if (opts.length() > 0) opts.append(" "); + opts.append("RECFM "); switch (recfm) { - case FIXED: cmd.append("F"); break; - case VARIABLE: cmd.append("V"); break; + case FIXED: opts.append("F"); break; + case VARIABLE: opts.append("V"); break; default: break; } if (lrecl > 0) { - cmd.append(" LRECL ").append(lrecl); + opts.append(" LRECL ").append(lrecl); } } } } // Additional options - if (otherOptions != null) { - cmd.append(" ").append(otherOptions); + if (otherOptions != null && !otherOptions.trim().isEmpty()) { + if (opts.length() > 0) opts.append(" "); + opts.append(otherOptions.trim()); + } + + // Apply options with host-specific prefix only if options are present + if (opts.length() > 0) { + if (hostType != HostType.TSO) { + cmd.append(" (").append(opts.toString()); + } else { + cmd.append(" ").append(opts.toString()); + } } return cmd.toString().trim(); diff --git a/lib3270j/src/main/java/org/lib3270j/ft/FTCut.java b/lib3270j/src/main/java/org/lib3270j/ft/FTCut.java index 2001f12..67c0592 100644 --- a/lib3270j/src/main/java/org/lib3270j/ft/FTCut.java +++ b/lib3270j/src/main/java/org/lib3270j/ft/FTCut.java @@ -21,7 +21,7 @@ public class FTCut { /** Callback for transfer events */ public interface FTCutListener { - void onTransferRunning(); + void onCutRunning(); void onTransferComplete(String errorMessage); void onTransferAborted(String errorMessage); void onBytesTransferred(long bytes); @@ -221,7 +221,7 @@ public class FTCut { xlateBufIx = 0; cutEof = false; cutAck(); - listener.onTransferRunning(); + listener.onCutRunning(); break; case SC_XFER_COMPLETE: diff --git a/lib3270j/src/main/java/org/lib3270j/ft/FTDft.java b/lib3270j/src/main/java/org/lib3270j/ft/FTDft.java index bc1079e..39abdfd 100644 --- a/lib3270j/src/main/java/org/lib3270j/ft/FTDft.java +++ b/lib3270j/src/main/java/org/lib3270j/ft/FTDft.java @@ -23,7 +23,7 @@ public class FTDft { /** Callback for transfer events (shared interface with CUT) */ public interface FTDftListener { - void onTransferRunning(); + void onDftRunning(); void onTransferComplete(String errorMessage); void onTransferAborted(String errorMessage); void onBytesTransferred(long bytes); @@ -178,7 +178,7 @@ public class FTDft { log.info("DFT: Open request for message stream"); } else { messageFlag = false; - listener.onTransferRunning(); + listener.onDftRunning(); } dftEof = false; diff --git a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java index 25f2777..1e27004 100644 --- a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java +++ b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java @@ -4,6 +4,7 @@ import org.lib3270j.screen.ScreenBuffer; import org.lib3270j.screen.ExtendedAttribute; import org.lib3270j.charset.EbcdicTranslator; import org.lib3270j.telnet.TelnetFSM; +import org.lib3270j.protocol.TelnetConstants; import static org.lib3270j.protocol.DS3270Constants.*; import java.io.ByteArrayOutputStream; @@ -362,6 +363,82 @@ public class InputProcessor { deleteChar(); } + /** Erase All Unprotected fields (Erase Input key in 3270). */ + public void eraseInput() { + if (keyboardLocked) return; + screen.eraseAllUnprotected(); + screen.markAllChanged(); + } + + /** Move cursor to first unprotected field on next line (Newline key in 3270). */ + public void newline() { + if (keyboardLocked) return; + int cur = screen.getCursorAddress(); + int cols = screen.getCols(); + int rows = screen.getRows(); + int curRow = cur / cols; + int nextRow = (curRow + 1) % rows; + int target = nextRow * cols; + if (screen.isFormatted()) { + target = screen.findNextUnprotected(target == 0 ? (rows * cols - 1) : (target - 1)); + } + screen.setCursorAddress(target); + screen.markAllChanged(); + } + + /** Insert Duplicate (DUP) code and advance to next field. */ + public void dup() { + if (keyboardLocked) return; + int baddr = screen.getCursorAddress(); + byte faVal = screen.getFieldAttributeAt(baddr); + if (faIsProtected(faVal & 0xFF)) return; + + ExtendedAttribute ea = screen.getCell(baddr); + ea.ec = (byte) FCORDER_DUP; + ea.ucs4 = '*'; + int faAddr = screen.findFieldAttribute(baddr); + if (faAddr >= 0) { + screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY); + } + tab(); + } + + /** Insert Field Mark (FM) code. */ + public void fieldMark() { + if (keyboardLocked) return; + int baddr = screen.getCursorAddress(); + byte faVal = screen.getFieldAttributeAt(baddr); + if (faIsProtected(faVal & 0xFF)) return; + + ExtendedAttribute ea = screen.getCell(baddr); + ea.ec = (byte) FCORDER_FM; + ea.ucs4 = ';'; + int faAddr = screen.findFieldAttribute(baddr); + if (faAddr >= 0) { + screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY); + } + int size = screen.getRows() * screen.getCols(); + baddr = (baddr + 1) % size; + while (screen.getCell(baddr).isFieldAttribute()) { + baddr = (baddr + 1) % size; + } + screen.setCursorAddress(baddr); + screen.markAllChanged(); + } + + /** Attention key (sends Telnet IP). */ + public void attn() { + if (fsm != null && fsm.getConnectionState() != null && fsm.getConnectionState().isConnected()) { + byte[] ip = new byte[] { (byte) TelnetConstants.IAC, (byte) TelnetConstants.IP }; + fsm.sendRecord(ip); + } + } + + /** SysReq key. */ + public void sysReq() { + reset(); + } + /** Reset (unlock keyboard, cancel insert mode). */ public void reset() { setKeyboardLocked(false); diff --git a/lib3270j/src/main/java/org/lib3270j/screen/ScreenBuffer.java b/lib3270j/src/main/java/org/lib3270j/screen/ScreenBuffer.java index 658a14f..fae33f0 100644 --- a/lib3270j/src/main/java/org/lib3270j/screen/ScreenBuffer.java +++ b/lib3270j/src/main/java/org/lib3270j/screen/ScreenBuffer.java @@ -305,6 +305,7 @@ public class ScreenBuffer { private char getAplGraphic(int ec) { switch (ec) { + // Box-drawing line and corner characters (standard IBM 3270 GE / APL) case 0xA2: return '\u2500'; // Horizontal Line 's' -> '─' case 0x85: return '\u2502'; // Vertical Line 'e' -> '│' case 0xC5: return '\u250C'; // Top Left 'E' -> '┌' @@ -316,6 +317,24 @@ public class ScreenBuffer { case 0xC7: return '\u252C'; // T-Junction Top 'G' -> '┬' case 0xD7: return '\u2534'; // T-Junction Bottom 'P' -> '┴' case 0xCB: return '\u253C'; // Cross -> '┼' + + // Special math and APL symbols (matching x3270 cg.c / apl.c) + case 0x8C: return '\u2264'; // Less-than or equal '≤' + case 0xAE: return '\u2265'; // Greater-than or equal '≥' + case 0xBE: return '\u2260'; // Not equal '≠' + case 0xAD: return '['; // Left bracket + case 0xBD: return ']'; // Right bracket + case 0x8D: return '{'; // Left brace + case 0x9D: return '}'; // Right brace + case 0xB0: return '\u00B0'; // Degree '°' + case 0xB1: return '\u00B1'; // Plus-minus '±' + case 0xB2: return '\u00B2'; // Superscript 2 '²' + case 0xB3: return '\u00B3'; // Superscript 3 '³' + case 0xAF: return '\u00AF'; // Overbar '¯' + case 0xBA: return '\u03A9'; // Omega 'Ω' + case 0xBF: return '\u00B5'; // Micro 'µ' + case 0x5F: return '\u00AC'; // Not sign '¬' + default: return translator.ebcdicToUnicode(ec); } } diff --git a/lib3270j/src/main/java/org/lib3270j/telnet/TelnetConnection.java b/lib3270j/src/main/java/org/lib3270j/telnet/TelnetConnection.java index 32b96ff..1ce34c0 100644 --- a/lib3270j/src/main/java/org/lib3270j/telnet/TelnetConnection.java +++ b/lib3270j/src/main/java/org/lib3270j/telnet/TelnetConnection.java @@ -37,13 +37,26 @@ public class TelnetConnection { * Connect to the host. Blocks until connection is established or fails. */ public void connect() throws IOException { - log.info("Connecting to " + config.getHost() + ":" + config.getPort()); - socket = new Socket(); - socket.setKeepAlive(true); - socket.setOOBInline(true); - socket.setTcpNoDelay(true); - socket.connect(new InetSocketAddress(config.getHost(), config.getPort()), - config.getConnectTimeoutMs()); + if (config.isUseTls()) { + log.info("Connecting with TLS to " + config.getHost() + ":" + config.getPort()); + javax.net.ssl.SSLSocketFactory ssf = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(); + javax.net.ssl.SSLSocket sslSocket = (javax.net.ssl.SSLSocket) ssf.createSocket(); + sslSocket.setKeepAlive(true); + sslSocket.setOOBInline(true); + sslSocket.setTcpNoDelay(true); + sslSocket.connect(new InetSocketAddress(config.getHost(), config.getPort()), + config.getConnectTimeoutMs()); + sslSocket.startHandshake(); + socket = sslSocket; + } else { + log.info("Connecting to " + config.getHost() + ":" + config.getPort()); + socket = new Socket(); + socket.setKeepAlive(true); + socket.setOOBInline(true); + socket.setTcpNoDelay(true); + socket.connect(new InetSocketAddress(config.getHost(), config.getPort()), + config.getConnectTimeoutMs()); + } inputStream = new BufferedInputStream(socket.getInputStream(), READ_BUFFER_SIZE); outputStream = new BufferedOutputStream(socket.getOutputStream()); diff --git a/lib3270j/src/test/java/org/lib3270j/charset/EbcdicTranslatorTest.java b/lib3270j/src/test/java/org/lib3270j/charset/EbcdicTranslatorTest.java new file mode 100644 index 0000000..00f7528 --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/charset/EbcdicTranslatorTest.java @@ -0,0 +1,66 @@ +package org.lib3270j.charset; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class EbcdicTranslatorTest { + + private final EbcdicTranslator translator = new EbcdicTranslator(); + + @Test + public void testBasicAlphanumericTranslation() { + assertEquals('A', translator.ebcdicToUnicode(0xC1)); + assertEquals('B', translator.ebcdicToUnicode(0xC2)); + assertEquals('Z', translator.ebcdicToUnicode(0xE9)); + assertEquals('a', translator.ebcdicToUnicode(0x81)); + assertEquals('z', translator.ebcdicToUnicode(0xA9)); + assertEquals('0', translator.ebcdicToUnicode(0xF0)); + assertEquals('9', translator.ebcdicToUnicode(0xF9)); + assertEquals(' ', translator.ebcdicToUnicode(0x40)); + } + + @Test + public void testReverseTranslation() { + assertEquals(0xC1, translator.unicodeToEbcdic('A')); + assertEquals(0x81, translator.unicodeToEbcdic('a')); + assertEquals(0xF0, translator.unicodeToEbcdic('0')); + assertEquals(0x40, translator.unicodeToEbcdic(' ')); + assertEquals(0x4B, translator.unicodeToEbcdic('.')); + assertEquals(0x6B, translator.unicodeToEbcdic(',')); + } + + @Test + public void testSpecialCharactersCP037() { + // [ and ] + assertEquals(0xBA, translator.unicodeToEbcdic('[')); + assertEquals(0xBB, translator.unicodeToEbcdic(']')); + assertEquals('[', translator.ebcdicToUnicode(0xBA)); + assertEquals(']', translator.ebcdicToUnicode(0xBB)); + + // { and } + assertEquals(0xC0, translator.unicodeToEbcdic('{')); + assertEquals(0xD0, translator.unicodeToEbcdic('}')); + assertEquals('{', translator.ebcdicToUnicode(0xC0)); + assertEquals('}', translator.ebcdicToUnicode(0xD0)); + + // Backslash \ + assertEquals(0xE0, translator.unicodeToEbcdic('\\')); + assertEquals('\\', translator.ebcdicToUnicode(0xE0)); + + // Caret ^ + assertEquals(0xB0, translator.unicodeToEbcdic('^')); + assertEquals('^', translator.ebcdicToUnicode(0xB0)); + + // Tilde ~ + assertEquals(0xA1, translator.unicodeToEbcdic('~')); + assertEquals('~', translator.ebcdicToUnicode(0xA1)); + } + + @Test + public void testStringRoundTrip() { + String test = "Hello World! 123 [TEST] {VAR} \\"; + byte[] ebcdic = translator.stringToEbcdic(test); + String restored = translator.ebcdicToString(ebcdic, 0, ebcdic.length); + assertEquals(test, restored); + } +} diff --git a/lib3270j/src/test/java/org/lib3270j/datastream/DataStreamProcessorTest.java b/lib3270j/src/test/java/org/lib3270j/datastream/DataStreamProcessorTest.java new file mode 100644 index 0000000..3488d0d --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/datastream/DataStreamProcessorTest.java @@ -0,0 +1,69 @@ +package org.lib3270j.datastream; + +import org.junit.jupiter.api.Test; +import org.lib3270j.TerminalModel; +import org.lib3270j.charset.EbcdicTranslator; +import org.lib3270j.screen.ScreenBuffer; +import org.lib3270j.screen.ExtendedAttribute; +import static org.junit.jupiter.api.Assertions.*; +import static org.lib3270j.protocol.DS3270Constants.*; + +public class DataStreamProcessorTest { + + private final EbcdicTranslator translator = new EbcdicTranslator(); + private final ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator); + private final DataStreamProcessor processor = new DataStreamProcessor(screen, translator); + + @Test + public void testEraseWriteAndSBAOrder() { + // Build an EW record: + // Byte 0: CMD_EW (0x05) + // Byte 1: WCC (0xC3) - restore keyboard, reset MDT + // Byte 2: ORDER_SBA (0x11) + // Byte 3, 4: Encoded address for 0 (0x40, 0x40) + // Byte 5: ORDER_SF (0x1D) + // Byte 6: FA (0xC0) - unprotected + // Byte 7..11: EBCDIC "HELLO" (0xC8, 0xC5, 0xD3, 0xD3, 0xD6) + byte[] record = new byte[] { + (byte) CMD_EW, + (byte) 0xC3, + (byte) ORDER_SBA, + (byte) 0x40, (byte) 0x40, + (byte) ORDER_SF, + (byte) FA_PRINTABLE, + (byte) 0xC8, (byte) 0xC5, (byte) 0xD3, (byte) 0xD3, (byte) 0xD6 + }; + + processor.processRecord(record, 0, record.length, true); + + assertTrue(screen.isFormatted()); + assertTrue(screen.getCell(0).isFieldAttribute()); + + // Positions 1..5 should contain "HELLO" + assertEquals('H', screen.getCell(1).ucs4); + assertEquals('E', screen.getCell(2).ucs4); + assertEquals('L', screen.getCell(3).ucs4); + assertEquals('L', screen.getCell(4).ucs4); + assertEquals('O', screen.getCell(5).ucs4); + } + + @Test + public void testRepeatToAddressOrder() { + // Write record with RA from 0 to 10 with character 'A' (0xC1) + byte[] record = new byte[] { + (byte) CMD_EW, + (byte) 0xC3, + (byte) ORDER_SBA, + (byte) 0x40, (byte) 0x40, // Address 0 + (byte) ORDER_RA, + (byte) 0x40, (byte) 0x4A, // Address 10 (0x40, 0x4A) + (byte) 0xC1 // EBCDIC 'A' + }; + + processor.processRecord(record, 0, record.length, true); + + for (int i = 0; i < 10; i++) { + assertEquals('A', screen.getCell(i).ucs4, "Cell " + i + " should be 'A'"); + } + } +} diff --git a/lib3270j/src/test/java/org/lib3270j/datastream/QueryReplyBuilderTest.java b/lib3270j/src/test/java/org/lib3270j/datastream/QueryReplyBuilderTest.java new file mode 100644 index 0000000..478cfcf --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/datastream/QueryReplyBuilderTest.java @@ -0,0 +1,55 @@ +package org.lib3270j.datastream; + +import org.junit.jupiter.api.Test; +import org.lib3270j.TerminalModel; +import org.lib3270j.charset.EbcdicTranslator; +import org.lib3270j.screen.ScreenBuffer; +import static org.junit.jupiter.api.Assertions.*; +import static org.lib3270j.protocol.DS3270Constants.*; + +public class QueryReplyBuilderTest { + + private final EbcdicTranslator translator = new EbcdicTranslator(); + private final ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator); + private final QueryReplyBuilder qrBuilder = new QueryReplyBuilder(screen); + + @Test + public void testBuildAllQueryReplies() { + byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43); + assertNotNull(replies); + assertTrue(replies.length > 0); + assertEquals((byte) AID_SF, replies[0]); + } + + @Test + public void testBuildFilteredQueryRepliesWithSupportedCodes() { + byte[] requested = new byte[] { (byte) QR_DDM, (byte) QR_USABLE_AREA }; + byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43); + + assertNotNull(replies); + assertEquals((byte) AID_SF, replies[0]); + + // First SF should be DDM: length (2 bytes), SFID_QREPLY (0x81), QR_DDM (0x95) + int len1 = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF); + assertEquals(0x81, replies[3] & 0xFF); + assertEquals(QR_DDM, replies[4] & 0xFF); + + // Second SF should be Usable Area + int pos2 = 1 + len1; + int len2 = ((replies[pos2] & 0xFF) << 8) | (replies[pos2 + 1] & 0xFF); + assertEquals(0x81, replies[pos2 + 2] & 0xFF); + assertEquals(QR_USABLE_AREA, replies[pos2 + 3] & 0xFF); + } + + @Test + public void testBuildFilteredQueryRepliesWithUnsupportedCode() { + byte[] requested = new byte[] { (byte) 0x77 }; // Unsupported code + byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43); + + assertNotNull(replies); + assertEquals((byte) AID_SF, replies[0]); + // Should emit QR_NULL (0xFF) + assertEquals(0x81, replies[3] & 0xFF); + assertEquals(QR_NULL, replies[4] & 0xFF); + } +} diff --git a/lib3270j/src/test/java/org/lib3270j/ft/FTConfigTest.java b/lib3270j/src/test/java/org/lib3270j/ft/FTConfigTest.java new file mode 100644 index 0000000..1bccfa8 --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/ft/FTConfigTest.java @@ -0,0 +1,83 @@ +package org.lib3270j.ft; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class FTConfigTest { + + @Test + public void testTsoReceiveAscii() { + FTConfig config = new FTConfig(); + config.setHostType(FTConfig.HostType.TSO); + config.setDirection(FTConfig.Direction.RECEIVE); + config.setHostFilename("MY.DATASET"); + config.setTransferMode(FTConfig.TransferMode.ASCII); + config.setCrAction(FTConfig.CrAction.REMOVE); + + assertEquals("IND$FILE GET MY.DATASET ASCII CRLF", config.buildCommand()); + } + + @Test + public void testTsoReceiveBinary() { + FTConfig config = new FTConfig(); + config.setHostType(FTConfig.HostType.TSO); + config.setDirection(FTConfig.Direction.RECEIVE); + config.setHostFilename("'USER.BIN.DATA'"); + config.setTransferMode(FTConfig.TransferMode.BINARY); + + assertEquals("IND$FILE GET 'USER.BIN.DATA'", config.buildCommand()); + } + + @Test + public void testTsoSendFixedRecfm() { + FTConfig config = new FTConfig(); + config.setHostType(FTConfig.HostType.TSO); + config.setDirection(FTConfig.Direction.SEND); + config.setHostFilename("MY.DATASET"); + config.setTransferMode(FTConfig.TransferMode.ASCII); + config.setCrAction(FTConfig.CrAction.REMOVE); + config.setRecfm("F"); + config.setLrecl("80"); + config.setBlksize("3120"); + + assertEquals("IND$FILE PUT MY.DATASET ASCII CRLF RECFM(F) LRECL(80) BLKSIZE(3120)", config.buildCommand()); + } + + @Test + public void testCmsReceiveBinaryNoNakedParen() { + FTConfig config = new FTConfig(); + config.setHostType(FTConfig.HostType.CMS); + config.setDirection(FTConfig.Direction.RECEIVE); + config.setHostFilename("PROFILE EXEC A"); + config.setTransferMode(FTConfig.TransferMode.BINARY); + + // In CMS, binary receive has no options, so NO open parenthesis '(' should be generated! + assertEquals("IND$FILE GET PROFILE EXEC A", config.buildCommand()); + } + + @Test + public void testCmsReceiveAscii() { + FTConfig config = new FTConfig(); + config.setHostType(FTConfig.HostType.CMS); + config.setDirection(FTConfig.Direction.RECEIVE); + config.setHostFilename("TEST DATA A"); + config.setTransferMode(FTConfig.TransferMode.ASCII); + config.setCrAction(FTConfig.CrAction.REMOVE); + + assertEquals("IND$FILE GET TEST DATA A (ASCII CRLF", config.buildCommand()); + } + + @Test + public void testCmsSendWithRecfmAndLrecl() { + FTConfig config = new FTConfig(); + config.setHostType(FTConfig.HostType.CMS); + config.setDirection(FTConfig.Direction.SEND); + config.setHostFilename("TEST FILE A"); + config.setTransferMode(FTConfig.TransferMode.ASCII); + config.setCrAction(FTConfig.CrAction.REMOVE); + config.setRecfm("V"); + config.setLrecl("132"); + + assertEquals("IND$FILE PUT TEST FILE A (ASCII CRLF RECFM V LRECL 132", config.buildCommand()); + } +} diff --git a/lib3270j/src/test/java/org/lib3270j/protocol/DS3270ConstantsTest.java b/lib3270j/src/test/java/org/lib3270j/protocol/DS3270ConstantsTest.java new file mode 100644 index 0000000..d7831f9 --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/protocol/DS3270ConstantsTest.java @@ -0,0 +1,51 @@ +package org.lib3270j.protocol; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.lib3270j.protocol.DS3270Constants.*; + +public class DS3270ConstantsTest { + + @Test + public void test12BitAddressRoundTrip() { + int rows = 24; + int cols = 80; + int maxAddr = rows * cols; // 1920 + + for (int addr = 0; addr < maxAddr; addr += 17) { + byte[] encoded = encodeAddress(addr, rows, cols); + assertEquals(2, encoded.length); + int decoded = decodeAddress(encoded[0] & 0xFF, encoded[1] & 0xFF); + assertEquals(addr, decoded, "Address " + addr + " failed round trip"); + } + } + + @Test + public void test14BitAddressRoundTrip() { + int rows = 60; + int cols = 80; // 4800 (> 4096, triggers 14-bit format) + int maxAddr = rows * cols; + + for (int addr = 0; addr < maxAddr; addr += 123) { + byte[] encoded = encodeAddress(addr, rows, cols); + assertEquals(2, encoded.length); + int decoded = decodeAddress(encoded[0] & 0xFF, encoded[1] & 0xFF); + assertEquals(addr, decoded, "14-bit address " + addr + " failed round trip"); + } + } + + @Test + public void testFieldAttributePredicates() { + int protectedHighMod = FA_PRINTABLE | FA_PROTECT | FA_INT_HIGH_SEL | FA_MODIFY; + assertTrue(faIsProtected(protectedHighMod)); + assertTrue(faIsHigh(protectedHighMod)); + assertTrue(faIsModified(protectedHighMod)); + assertFalse(faIsNumeric(protectedHighMod)); + assertFalse(faIsSkip(protectedHighMod)); + + int skipField = FA_PRINTABLE | FA_PROTECT | FA_NUMERIC; + assertTrue(faIsSkip(skipField)); + assertTrue(faIsProtected(skipField)); + assertTrue(faIsNumeric(skipField)); + } +} diff --git a/lib3270j/src/test/java/org/lib3270j/screen/ScreenBufferTest.java b/lib3270j/src/test/java/org/lib3270j/screen/ScreenBufferTest.java new file mode 100644 index 0000000..c6a72eb --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/screen/ScreenBufferTest.java @@ -0,0 +1,64 @@ +package org.lib3270j.screen; + +import org.junit.jupiter.api.Test; +import org.lib3270j.TerminalModel; +import org.lib3270j.charset.EbcdicTranslator; +import static org.junit.jupiter.api.Assertions.*; +import static org.lib3270j.protocol.DS3270Constants.*; + +public class ScreenBufferTest { + + private final EbcdicTranslator translator = new EbcdicTranslator(); + private final ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator); + + @Test + public void testScreenInitialization() { + assertEquals(24, screen.getRows()); + assertEquals(80, screen.getCols()); + assertEquals(43, screen.getMaxRows()); + assertEquals(80, screen.getMaxCols()); + assertFalse(screen.isFormatted()); + } + + @Test + public void testScreenEraseAlternate() { + screen.erase(true); + assertTrue(screen.isScreenAlt()); + assertEquals(43, screen.getRows()); + assertEquals(80, screen.getCols()); + + screen.erase(false); + assertFalse(screen.isScreenAlt()); + assertEquals(24, screen.getRows()); + assertEquals(80, screen.getCols()); + } + + @Test + public void testFieldNavigationAndFindAttribute() { + screen.erase(false); + // Place field attribute at position 0 (unprotected) + screen.setCellFA(0, (byte) FA_PRINTABLE); + // Place field attribute at position 20 (protected) + screen.setCellFA(20, (byte) (FA_PRINTABLE | FA_PROTECT)); + + assertTrue(screen.isFormatted()); + assertEquals(0, screen.findFieldAttribute(5)); + assertEquals(20, screen.findFieldAttribute(25)); + + // findNextUnprotected + int nextUnprot = screen.findNextUnprotected(0); + assertEquals(1, nextUnprot); + } + + @Test + public void testGraphicEscapeTranslation() { + screen.erase(false); + // Set cell at position 10 to horizontal line (0xA2) with CS_GE (0x04) + ExtendedAttribute ea = screen.getCell(10); + ea.ec = (byte) 0xA2; + ea.cs = (byte) CS_GE; + + screen.translateToUnicode(); + assertEquals('\u2500', ea.ucs4); // '─' + } +} diff --git a/run.sh b/run.sh index 36a7539..8717bcd 100755 --- a/run.sh +++ b/run.sh @@ -3,43 +3,14 @@ # Usage: ./run.sh [host] [port] [model] # Example: ./run.sh mainframe.example.com 23 4 -JAVA_DIR="$(cd "$(dirname "$0")" && pwd)" -BUILD_DIR="$JAVA_DIR/build" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +BUILD_DIR="$SCRIPT_DIR/build" -# Always rebuild to pick up changes -echo "Building lib3270j..." -rm -rf "$BUILD_DIR" -mkdir -p "$BUILD_DIR/lib3270j" "$BUILD_DIR/j3270" +# Build if jar is missing or rebuild requested +if [ ! -f "$BUILD_DIR/j3270.jar" ]; then + "$SCRIPT_DIR/build_all.sh" +fi -# Compile libraries -find "$JAVA_DIR/lib3270j/src" -name "*.java" -print0 | xargs -0 javac -d "$BUILD_DIR/lib3270j" - -echo "Building j3270..." -find "$JAVA_DIR/j3270/src" -name "*.java" -print0 | xargs -0 javac -cp "$BUILD_DIR/lib3270j" -d "$BUILD_DIR/j3270" - -echo "Build complete." - -# --------------------------------------------------------- -# NEW: Package the shareable JAR -# --------------------------------------------------------- -echo "Packaging j3270.jar..." - -# 1. Create the Manifest file indicating the entry point -# Note: The manifest file must end with a new line or carriage return -echo "Main-Class: org.pubvm.j3270.J3270App" > "$BUILD_DIR/MANIFEST.MF" -echo "" >> "$BUILD_DIR/MANIFEST.MF" - -# 2. Build the JAR using both compiled directories -jar cvfm "$BUILD_DIR/j3270.jar" "$BUILD_DIR/MANIFEST.MF" \ - -C "$BUILD_DIR/lib3270j" . \ - -C "$BUILD_DIR/j3270" . - -echo "Executable JAR created at: $BUILD_DIR/j3270.jar" - -# --------------------------------------------------------- -# Run the newly packaged JAR -# --------------------------------------------------------- -#exec java -Dapple.laf.useScreenMenuBar=true \ -# -Dapple.awt.application.name=j3270 \ -# -jar "$BUILD_DIR/j3270.jar" "$@" -nohup java -jar build/j3270.jar & +exec java -Dapple.laf.useScreenMenuBar=true \ + -Dapple.awt.application.name=j3270 \ + -jar "$BUILD_DIR/j3270.jar" "$@" diff --git a/settings.gradle b/settings.gradle index 0d6bc4d..27331d1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,4 +2,3 @@ rootProject.name = 'j3270-project' include 'lib3270j' include 'j3270' -include 'a3270'