1 Commits

Author SHA1 Message Date
Rsclub2_2 56fd15f1d6 Added recommended minimal Resolution of Display 2026-03-27 15:07:38 +01:00
36 changed files with 17820 additions and 2656 deletions
-60
View File
@@ -1,60 +0,0 @@
---
name: Bug Report
about: Report a problem with KST4Contest / Ein Problem mit KST4Contest melden
title: "[BUG] "
labels: bug
---
**DE:** Bitte fülle alle Felder so vollständig wie möglich aus. Das hilft, den Fehler schneller zu finden.
**EN:** Please fill in all fields as completely as possible. This helps to find the bug faster.
## Description / Beschreibung
<!-- EN: A clear and concise description of the bug. -->
<!-- DE: Eine klare und präzise Beschreibung des Problems. -->
## Steps to reproduce / Schritte zum Reproduzieren
<!-- EN: Step-by-step instructions to reproduce the bug. -->
<!-- DE: Schritt-für-Schritt-Anleitung, um den Fehler zu reproduzieren. -->
1.
2.
3.
## Expected behaviour / Erwartetes Verhalten
<!-- EN: What did you expect to happen? / DE: Was hätte passieren sollen? -->
## Actual behaviour / Tatsächliches Verhalten
<!-- EN: What actually happened? / DE: Was ist stattdessen passiert? -->
## Log file content / Inhalt der Logdatei
**EN:** Please paste the content of the error log file here. It is written automatically and contains error messages only — no personal data.
**DE:** Bitte füge hier den Inhalt der Fehler-Logdatei ein. Sie wird automatisch geschrieben und enthält nur Fehlermeldungen — keine persönlichen Daten.
| OS | Path / Pfad |
|----|-------------|
| Linux / macOS | `~/.praktiKST/kst4contest-errors.log` |
| Windows | `C:\Users\<YourName>\.praktiKST\kst4contest-errors.log` |
```
Paste log content here / Loginhalt hier einfügen
```
## Version
**KST4Contest version / Version** (e.g. 1.41.0):
**Java version / Java-Version** (`java -version`, e.g. 17.0.9):
**Operating system / Betriebssystem:** <!-- Linux / Windows / macOS -->
**Logging software / Logprogramm** (if applicable / falls relevant, e.g. UCXLog, N1MM+, WinTest):
## Checklist / Checkliste
- [ ] I have attached the log file / Ich habe die Logdatei angehängt
- [ ] I have checked that this issue has not been reported before / Ich habe geprüft, dass dieses Problem noch nicht gemeldet wurde
-1
View File
@@ -1 +0,0 @@
blank_issues_enabled: false
-33
View File
@@ -1,33 +0,0 @@
---
name: Feature Request
about: Suggest a new feature or improvement / Neue Funktion oder Verbesserung vorschlagen
title: "[FEATURE] "
labels: enhancement
---
**DE:** Bitte beschreibe deine Idee so genau wie möglich.
**EN:** Please describe your idea as precisely as possible.
## Summary / Zusammenfassung
<!-- EN: A short summary of the feature you'd like. -->
<!-- DE: Eine kurze Zusammenfassung der gewünschten Funktion. -->
## Motivation / Begründung
<!-- EN: Why would this feature be useful? What problem does it solve? -->
<!-- DE: Warum wäre diese Funktion nützlich? Welches Problem löst sie? -->
## Detailed description / Detaillierte Beschreibung
<!-- EN: Describe the feature in detail. How should it work? -->
<!-- DE: Beschreibe die Funktion im Detail. Wie soll sie funktionieren? -->
## Alternatives considered / Geprüfte Alternativen
<!-- EN: Have you considered any alternative solutions or workarounds? -->
<!-- DE: Hast du alternative Lösungen oder Workarounds in Betracht gezogen? -->
## Checklist / Checkliste
- [ ] I have checked that this feature has not been requested before / Ich habe geprüft, dass diese Funktion noch nicht angefragt wurde
+2 -5
View File
@@ -21,9 +21,6 @@ $endif$
%% ─── Page layout ──────────────────────────────────────────────────────────
\usepackage[a4paper, top=2.5cm, bottom=2.5cm, left=2.5cm, right=2.5cm]{geometry}
%% ─── Text decorations (strikethrough via ~~...~~ in Markdown → \st{}) ────
\usepackage{soul}
%% ─── Colors ───────────────────────────────────────────────────────────────
\usepackage[dvipsnames,svgnames,x11names]{xcolor}
\definecolor{brand-green}{RGB}{7,166,54}
@@ -194,7 +191,7 @@ $endif$
\fancyhf{}
\fancyhead[L]{\small\color{brand-green}\textbf{KST4Contest}}
\fancyhead[R]{\small\color{brand-green}$if(version)$$version$$endif$}
\fancyfoot[L]{\small\color{gray}DO5AMF \textbar\ DN9APW}
\fancyfoot[L]{\small\color{gray}DO5AMF (Marc Fröhlich) \textbar\ DN9APW (Philipp Wagner)}
\fancyfoot[C]{\small\color{gray}\thepage}
\fancyfoot[R]{\small\color{gray}$title$}
\renewcommand{\headrulewidth}{0.4pt}
@@ -239,7 +236,7 @@ $endif$
{\fontsize{22}{28}\selectfont\color{white!75!brand-green}pratiKST (ON4KST Chat Client)}\\[2.8cm]
\color{white!40!brand-green}\rule{10cm}{0.6pt}\\[1.8cm]
{\LARGE\bfseries\color{white}$title$}\\[1cm]
$if(version)${\large\color{white!80!brand-green}Version:\space{}$version$}\\[0.6cm]$endif$
$if(version)${\large\color{white!80!brand-green}Version:\space$version$}\\[0.6cm]$endif$
\vfill
{\large\color{white}DO5AMF · Marc Fröhlich · DM5M · DN9APW · Philipp Wagner}\\[0.4cm]
{\color{white!70!brand-green}\today}\\[2cm]
+17 -396
View File
@@ -11,10 +11,6 @@ on:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
packages: write
jobs:
build-windows-zip:
name: Build Windows ZIP
@@ -87,7 +83,9 @@ jobs:
run: |
VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')
SHORT_SHA="${GITHUB_SHA::7}"
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "SHORT_SHA=$SHORT_SHA" >> "$GITHUB_ENV"
echo "ASSET_BASENAME=praktiKST-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
@@ -108,7 +106,7 @@ jobs:
mkdir -p dist
jpackage \
--type app-image \
--name KST4Contest \
--name praktiKST \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
@@ -118,416 +116,39 @@ jobs:
- name: Create AppDir metadata
run: |
rm -rf target/KST4Contest.AppDir
cp -a dist/KST4Contest target/KST4Contest.AppDir
rm -rf target/praktiKST.AppDir
cp -a dist/praktiKST target/praktiKST.AppDir
cat > target/KST4Contest.AppDir/AppRun << 'EOF'
cat > target/praktiKST.AppDir/AppRun << 'EOF'
#!/bin/sh
HERE="$(dirname "$(readlink -f "$0")")"
exec "$HERE/bin/KST4Contest" "$@"
exec "$HERE/bin/praktiKST" "$@"
EOF
chmod +x target/KST4Contest.AppDir/AppRun
chmod +x target/praktiKST.AppDir/AppRun
cat > target/KST4Contest.AppDir/KST4Contest.desktop << 'EOF'
cat > target/praktiKST.AppDir/praktiKST.desktop << 'EOF'
[Desktop Entry]
Type=Application
Name=KST4Contest
Exec=KST4Contest
Icon=KST4Contest
Name=praktiKST
Exec=praktiKST
Icon=praktiKST
Categories=Network;HamRadio;
Terminal=false
EOF
if [ -f target/KST4Contest.AppDir/lib/KST4Contest.png ]; then
cp target/KST4Contest.AppDir/lib/KST4Contest.png target/KST4Contest.AppDir/KST4Contest.png
if [ -f target/praktiKST.AppDir/lib/praktiKST.png ]; then
cp target/praktiKST.AppDir/lib/praktiKST.png target/praktiKST.AppDir/praktiKST.png
fi
- name: Build AppImage
run: |
wget -q -O target/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x target/appimagetool.AppImage
APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/KST4Contest.AppDir "dist/${ASSET_BASENAME}-linux-x86_64.AppImage"
APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/praktiKST.AppDir "dist/${ASSET_BASENAME}-linux-x86_64.AppImage"
- name: Upload Linux artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-appimage
path: dist/KST4Contest-*-linux-x86_64.AppImage
retention-days: 14
build-linux-deb:
name: Build Debian package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Resolve nightly version info
run: |
VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')
SHORT_SHA="${GITHUB_SHA::7}"
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Install packaging dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends fakeroot
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build Debian package
run: |
mkdir -p dist
jpackage \
--type deb \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
DEB="$(ls dist/*.deb | head -n 1)"
if [ -z "$DEB" ]; then
echo "No DEB produced by jpackage" && exit 1
fi
mv "$DEB" "dist/${ASSET_BASENAME}-debian-amd64.deb"
- name: Upload Debian artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-debian
path: dist/KST4Contest-*-debian-amd64.deb
retention-days: 14
build-linux-rpm:
name: Build Fedora package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Resolve nightly version info
run: |
VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')
SHORT_SHA="${GITHUB_SHA::7}"
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Install packaging dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends rpm
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build Fedora package
run: |
mkdir -p dist
jpackage \
--type rpm \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
RPM="$(ls dist/*.rpm | head -n 1)"
if [ -z "$RPM" ]; then
echo "No RPM produced by jpackage" && exit 1
fi
mv "$RPM" "dist/${ASSET_BASENAME}-fedora-x86_64.rpm"
- name: Upload Fedora artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-fedora
path: dist/KST4Contest-*-fedora-x86_64.rpm
retention-days: 14
build-linux-arch:
name: Build Arch Linux package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Resolve nightly version info
run: |
VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')
SHORT_SHA="${GITHUB_SHA::7}"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "SHORT_SHA=$SHORT_SHA" >> "$GITHUB_ENV"
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Install packaging dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends zstd
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build app-image with jpackage
run: |
mkdir -p dist
jpackage \
--type app-image \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
- name: Build Arch Linux package artifact
run: |
ARCH=$(uname -m)
PKGVER=$(printf '%s' "${VERSION}-${SHORT_SHA}" | sed 's/[^[:alnum:].+_]/_/g')
PKGROOT="target/archpkg"
rm -rf "$PKGROOT"
mkdir -p "$PKGROOT/usr/lib/KST4Contest" "$PKGROOT/usr/bin"
cp -a dist/KST4Contest/. "$PKGROOT/usr/lib/KST4Contest/"
cat > "$PKGROOT/usr/bin/KST4Contest" << 'EOF'
#!/bin/sh
exec /usr/lib/KST4Contest/bin/KST4Contest "$@"
EOF
chmod 755 "$PKGROOT/usr/bin/KST4Contest"
mkdir -p "$PKGROOT/usr/share/applications" "$PKGROOT/usr/share/icons/hicolor/256x256/apps"
cat > "$PKGROOT/usr/share/applications/KST4Contest.desktop" << 'EOF'
[Desktop Entry]
Type=Application
Name=KST4Contest
Comment=ON4KST Chat Client for VHF/UHF contest operation
Exec=KST4Contest
Icon=KST4Contest
Categories=Network;HamRadio;
Terminal=false
EOF
if [ -f "$PKGROOT/usr/lib/KST4Contest/lib/KST4Contest.png" ]; then
cp "$PKGROOT/usr/lib/KST4Contest/lib/KST4Contest.png" "$PKGROOT/usr/share/icons/hicolor/256x256/apps/KST4Contest.png"
fi
INSTALLED_SIZE=$(du -sk "$PKGROOT" | cut -f1)
cat > "$PKGROOT/.PKGINFO" << EOF
pkgname = kst4contest
pkgbase = kst4contest
pkgver = ${PKGVER}-1
pkgdesc = KST4Contest amateur radio contest logger
url = https://github.com/${{ github.repository }}
builddate = $(date +%s)
packager = GitHub Actions
size = ${INSTALLED_SIZE}
arch = ${ARCH}
license = custom
depend = java-runtime
EOF
tar --zstd -cf "dist/${ASSET_BASENAME}-archlinux-${ARCH}.pkg.tar.zst" -C "$PKGROOT" .
- name: Upload Arch Linux artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-arch
path: dist/KST4Contest-*-archlinux-*.pkg.tar.zst
retention-days: 14
build-flatpak:
name: Build Flatpak
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Resolve nightly version info
run: |
VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')
SHORT_SHA="${GITHUB_SHA::7}"
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "SHORT_SHA=$SHORT_SHA" >> "$GITHUB_ENV"
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Install Flatpak tooling
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends flatpak flatpak-builder elfutils
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak --user install -y flathub org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08
- name: Build app-image with jpackage
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
mkdir -p target/flatpak-src
jpackage \
--type app-image \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest target/flatpak-src
- name: Create Flatpak manifest
run: |
mkdir -p dist
cat > target/de.x08.KST4Contest.yml << 'EOF'
app-id: de.x08.KST4Contest
runtime: org.freedesktop.Platform
runtime-version: "24.08"
sdk: org.freedesktop.Sdk
command: KST4Contest
finish-args:
- --socket=wayland
- --socket=x11
- --share=network
- --share=ipc
- --device=dri
modules:
- name: kst4contest
buildsystem: simple
build-commands:
- install -d /app/lib/KST4Contest /app/bin /app/share/applications
- cp -a . /app/lib/KST4Contest/
- printf '#!/bin/sh\nexec /app/lib/KST4Contest/bin/KST4Contest "$@"\n' > /app/bin/KST4Contest
- chmod 755 /app/bin/KST4Contest
- echo '[Desktop Entry]' > /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Type=Application' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Name=KST4Contest' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Comment=ON4KST Chat Client for VHF/UHF contest operation' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Exec=KST4Contest' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Icon=de.x08.KST4Contest' >> /app/share/applications/de.x08.KST4Contest.desktop
- printf 'Categories=Network;HamRadio;\n' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Terminal=false' >> /app/share/applications/de.x08.KST4Contest.desktop
- test -f /app/lib/KST4Contest/lib/KST4Contest.png && install -Dm644 /app/lib/KST4Contest/lib/KST4Contest.png /app/share/icons/hicolor/256x256/apps/de.x08.KST4Contest.png || true
sources:
- type: dir
path: flatpak-src/KST4Contest
EOF
- name: Build Flatpak bundle
run: |
flatpak-builder --force-clean target/flatpak-build target/de.x08.KST4Contest.yml
flatpak build-export target/flatpak-repo target/flatpak-build
flatpak build-bundle target/flatpak-repo "dist/${ASSET_BASENAME}-linux-x86_64.flatpak" de.x08.KST4Contest
- name: Upload Flatpak artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-flatpak
path: dist/KST4Contest-*-linux-x86_64.flatpak
retention-days: 14
build-macos-dmg:
name: Build macOS DMG (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, macos-15-intel]
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Resolve nightly version info
run: |
VERSION=$(grep -m1 '<version>' pom.xml | sed 's/.*<version>\(.*\)<\/version>.*/\1/')
SHORT_SHA="${GITHUB_SHA::7}"
ARCH=$(uname -m)
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "SHORT_SHA=$SHORT_SHA" >> "$GITHUB_ENV"
echo "ASSET_BASENAME=KST4Contest-${VERSION}-${SHORT_SHA}" >> "$GITHUB_ENV"
echo "ARCH=$ARCH" >> "$GITHUB_ENV"
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build macOS DMG with jpackage
run: |
mkdir -p dist
jpackage \
--type dmg \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
env:
MACOSX_DEPLOYMENT_TARGET: "13.0"
- name: Rename DMG artifact
run: |
DMG=$(ls dist/*.dmg | head -n 1)
if [ -z "$DMG" ]; then
echo "No DMG produced by jpackage" && exit 1
fi
mv "$DMG" "dist/${ASSET_BASENAME}-macos-${ARCH}.dmg"
- name: Upload macOS artifact
uses: actions/upload-artifact@v4.3.4
with:
name: macos-dmg-${{ matrix.os }}
path: dist/KST4Contest-*-macos-*.dmg
path: dist/praktiKST-*-linux-x86_64.AppImage
retention-days: 14
+15 -449
View File
@@ -8,7 +8,6 @@ on:
permissions:
contents: write
packages: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@@ -89,7 +88,7 @@ jobs:
mkdir -p dist
jpackage \
--type app-image \
--name KST4Contest \
--name praktiKST \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
@@ -99,404 +98,41 @@ jobs:
- name: Create AppDir metadata
run: |
rm -rf target/KST4Contest.AppDir
cp -a dist/KST4Contest target/KST4Contest.AppDir
rm -rf target/praktiKST.AppDir
cp -a dist/praktiKST target/praktiKST.AppDir
cat > target/KST4Contest.AppDir/AppRun << 'EOF'
cat > target/praktiKST.AppDir/AppRun << 'EOF'
#!/bin/sh
HERE="$(dirname "$(readlink -f "$0")")"
exec "$HERE/bin/KST4Contest" "$@"
exec "$HERE/bin/praktiKST" "$@"
EOF
chmod +x target/KST4Contest.AppDir/AppRun
chmod +x target/praktiKST.AppDir/AppRun
cat > target/KST4Contest.AppDir/KST4Contest.desktop << 'EOF'
cat > target/praktiKST.AppDir/praktiKST.desktop << 'EOF'
[Desktop Entry]
Type=Application
Name=KST4Contest
Exec=KST4Contest
Icon=KST4Contest
Name=praktiKST
Exec=praktiKST
Icon=praktiKST
Categories=Network;HamRadio;
Terminal=false
EOF
if [ -f target/KST4Contest.AppDir/lib/KST4Contest.png ]; then
cp target/KST4Contest.AppDir/lib/KST4Contest.png target/KST4Contest.AppDir/KST4Contest.png
if [ -f target/praktiKST.AppDir/lib/praktiKST.png ]; then
cp target/praktiKST.AppDir/lib/praktiKST.png target/praktiKST.AppDir/praktiKST.png
fi
- name: Build AppImage
run: |
wget -q -O target/appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x target/appimagetool.AppImage
APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/KST4Contest.AppDir dist/KST4Contest-${{ github.ref_name }}-linux-x86_64.AppImage
APPIMAGE_EXTRACT_AND_RUN=1 ARCH=x86_64 target/appimagetool.AppImage target/praktiKST.AppDir dist/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage
- name: Upload Linux artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-appimage
path: dist/KST4Contest-${{ github.ref_name }}-linux-x86_64.AppImage
build-linux-deb:
name: Build Debian package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Install packaging dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends fakeroot
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build Debian package
run: |
mkdir -p dist
jpackage \
--type deb \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
DEB="$(ls dist/*.deb | head -n 1)"
if [ -z "$DEB" ]; then
echo "No DEB produced by jpackage" && exit 1
fi
mv "$DEB" "dist/KST4Contest-${{ github.ref_name }}-debian-amd64.deb"
- name: Upload Debian artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-debian
path: dist/KST4Contest-${{ github.ref_name }}-debian-amd64.deb
build-linux-rpm:
name: Build Fedora package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Install packaging dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends rpm
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build Fedora package
run: |
mkdir -p dist
jpackage \
--type rpm \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
RPM="$(ls dist/*.rpm | head -n 1)"
if [ -z "$RPM" ]; then
echo "No RPM produced by jpackage" && exit 1
fi
mv "$RPM" "dist/KST4Contest-${{ github.ref_name }}-fedora-x86_64.rpm"
- name: Upload Fedora artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-fedora
path: dist/KST4Contest-${{ github.ref_name }}-fedora-x86_64.rpm
build-linux-arch:
name: Build Arch Linux package
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Install packaging dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends zstd
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build app-image with jpackage
run: |
mkdir -p dist
jpackage \
--type app-image \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
- name: Build Arch Linux package artifact
run: |
ARCH=$(uname -m)
PKGVER=$(printf '%s' "${{ github.ref_name }}" | sed 's/[^[:alnum:].+_]/_/g')
PKGROOT="target/archpkg"
rm -rf "$PKGROOT"
mkdir -p "$PKGROOT/usr/lib/KST4Contest" "$PKGROOT/usr/bin"
cp -a dist/KST4Contest/. "$PKGROOT/usr/lib/KST4Contest/"
cat > "$PKGROOT/usr/bin/KST4Contest" << 'EOF'
#!/bin/sh
exec /usr/lib/KST4Contest/bin/KST4Contest "$@"
EOF
chmod 755 "$PKGROOT/usr/bin/KST4Contest"
mkdir -p "$PKGROOT/usr/share/applications" "$PKGROOT/usr/share/icons/hicolor/256x256/apps"
cat > "$PKGROOT/usr/share/applications/KST4Contest.desktop" << 'EOF'
[Desktop Entry]
Type=Application
Name=KST4Contest
Comment=ON4KST Chat Client for VHF/UHF contest operation
Exec=KST4Contest
Icon=KST4Contest
Categories=Network;HamRadio;
Terminal=false
EOF
if [ -f "$PKGROOT/usr/lib/KST4Contest/lib/KST4Contest.png" ]; then
cp "$PKGROOT/usr/lib/KST4Contest/lib/KST4Contest.png" "$PKGROOT/usr/share/icons/hicolor/256x256/apps/KST4Contest.png"
fi
INSTALLED_SIZE=$(du -sk "$PKGROOT" | cut -f1)
cat > "$PKGROOT/.PKGINFO" << EOF
pkgname = kst4contest
pkgbase = kst4contest
pkgver = ${PKGVER}-1
pkgdesc = KST4Contest amateur radio contest logger
url = https://github.com/${{ github.repository }}
builddate = $(date +%s)
packager = GitHub Actions
size = ${INSTALLED_SIZE}
arch = ${ARCH}
license = custom
depend = java-runtime
EOF
tar --zstd -cf "dist/KST4Contest-${{ github.ref_name }}-archlinux-${ARCH}.pkg.tar.zst" -C "$PKGROOT" .
- name: Upload Arch Linux artifact
uses: actions/upload-artifact@v4.3.4
with:
name: linux-arch
path: dist/KST4Contest-${{ github.ref_name }}-archlinux-*.pkg.tar.zst
build-flatpak:
name: Build Flatpak
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Install Flatpak tooling
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends flatpak flatpak-builder elfutils
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak --user install -y flathub org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08
- name: Build app-image with jpackage
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
mkdir -p target/flatpak-src
jpackage \
--type app-image \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest target/flatpak-src
- name: Create Flatpak manifest
run: |
mkdir -p dist
cat > target/de.x08.KST4Contest.yml << 'EOF'
app-id: de.x08.KST4Contest
runtime: org.freedesktop.Platform
runtime-version: "24.08"
sdk: org.freedesktop.Sdk
command: KST4Contest
finish-args:
- --socket=wayland
- --socket=x11
- --share=network
- --share=ipc
- --device=dri
modules:
- name: kst4contest
buildsystem: simple
build-commands:
- install -d /app/lib/KST4Contest /app/bin /app/share/applications
- cp -a . /app/lib/KST4Contest/
- printf '#!/bin/sh\nexec /app/lib/KST4Contest/bin/KST4Contest "$@"\n' > /app/bin/KST4Contest
- chmod 755 /app/bin/KST4Contest
- echo '[Desktop Entry]' > /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Type=Application' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Name=KST4Contest' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Comment=ON4KST Chat Client for VHF/UHF contest operation' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Exec=KST4Contest' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Icon=de.x08.KST4Contest' >> /app/share/applications/de.x08.KST4Contest.desktop
- printf 'Categories=Network;HamRadio;\n' >> /app/share/applications/de.x08.KST4Contest.desktop
- echo 'Terminal=false' >> /app/share/applications/de.x08.KST4Contest.desktop
- test -f /app/lib/KST4Contest/lib/KST4Contest.png && install -Dm644 /app/lib/KST4Contest/lib/KST4Contest.png /app/share/icons/hicolor/256x256/apps/de.x08.KST4Contest.png || true
sources:
- type: dir
path: flatpak-src/KST4Contest
EOF
- name: Import Flatpak signing key
run: |
echo "${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}" | gpg --batch --import
FLATPAK_GPG_KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr/{print $10; exit}')
echo "FLATPAK_GPG_KEY_ID=$FLATPAK_GPG_KEY_ID" >> "$GITHUB_ENV"
- name: Build Flatpak repo
run: |
flatpak-builder --force-clean target/flatpak-build target/de.x08.KST4Contest.yml
flatpak build-export --gpg-sign="$FLATPAK_GPG_KEY_ID" target/flatpak-repo target/flatpak-build stable
flatpak build-update-repo --gpg-sign="$FLATPAK_GPG_KEY_ID" target/flatpak-repo
- name: Create flatpakref
run: |
REPO_NAME="${GITHUB_REPOSITORY#*/}"
PAGES_URL="https://${GITHUB_REPOSITORY_OWNER}.github.io/${REPO_NAME}/"
GPG_KEY_B64=$(gpg --export "$FLATPAK_GPG_KEY_ID" | base64 -w 0)
cat > "dist/de.x08.KST4Contest.flatpakref" << EOF
[Flatpak Ref]
Name=de.x08.KST4Contest
Branch=stable
Title=KST4Contest ON4KST Chat Client
Url=${PAGES_URL}
RuntimeRepo=https://flathub.org/repo/flathub.flatpakrepo
GPGKey=${GPG_KEY_B64}
IsRuntime=false
EOF
- name: Upload flatpakref
uses: actions/upload-artifact@v4.3.4
with:
name: flatpakref
path: dist/de.x08.KST4Contest.flatpakref
- name: Upload Flatpak OSTree repo
uses: actions/upload-artifact@v4.3.4
with:
name: flatpak-ostree-repo
path: target/flatpak-repo/
build-macos-dmg:
name: Build macOS DMG (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, macos-15-intel]
steps:
- name: Checkout
uses: actions/checkout@v4.1.7
- name: Set up Java 17
uses: actions/setup-java@v4.1.0
with:
distribution: temurin
java-version: "17"
- name: Ensure mvnw is executable
run: chmod +x mvnw
- name: Build JAR and copy runtime dependencies
run: |
./mvnw -B -DskipTests package dependency:copy-dependencies -DincludeScope=runtime -DoutputDirectory=target/dist-libs
cp "$(ls -t target/praktiKST-*.jar | head -n 1)" target/dist-libs/app.jar
- name: Build macOS DMG with jpackage
run: |
mkdir -p dist
jpackage \
--type dmg \
--name KST4Contest \
--input target/dist-libs \
--main-jar app.jar \
--main-class kst4contest.view.Kst4ContestApplication \
--module-path target/dist-libs \
--add-modules javafx.controls,javafx.graphics,javafx.fxml,javafx.web,javafx.media,java.sql \
--dest dist
env:
MACOSX_DEPLOYMENT_TARGET: "13.0"
- name: Rename DMG artifact
run: |
ARCH=$(uname -m)
DMG=$(ls dist/*.dmg | head -n 1)
if [ -z "$DMG" ]; then
echo "No DMG produced by jpackage" && exit 1
fi
mv "$DMG" "dist/KST4Contest-${{ github.ref_name }}-macos-${ARCH}.dmg"
- name: Upload macOS artifact
uses: actions/upload-artifact@v4.3.4
with:
name: macos-dmg-${{ matrix.os }}
path: dist/KST4Contest-${{ github.ref_name }}-macos-*.dmg
path: dist/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage
build-docs-pdf:
name: Build Documentation PDF
@@ -575,47 +211,13 @@ jobs:
name: docs-pdf
path: dist/KST4Contest-${{ github.ref_name }}-manual-*.pdf
publish-flatpak-repo:
name: Publish Flatpak OSTree Repo to GitHub Pages
runs-on: ubuntu-latest
needs: build-flatpak
steps:
- name: Download OSTree repo artifact
uses: actions/download-artifact@v4.1.3
with:
name: flatpak-ostree-repo
path: flatpak-ostree-repo/
- name: Download flatpakref
uses: actions/download-artifact@v4.1.3
with:
name: flatpakref
path: flatpak-ostree-repo/
- name: Push to flatpak-repo branch
run: |
cd flatpak-ostree-repo
git init
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config user.name "github-actions[bot]"
git add -A
git commit -m "Flatpak repo: ${{ github.ref_name }}"
git push --force https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:flatpak-repo
release-tag:
name: Publish Tagged Release
runs-on: ubuntu-latest
needs:
- build-windows-zip
- build-linux-appimage
- build-linux-deb
- build-linux-rpm
- build-linux-arch
- build-macos-dmg
- build-flatpak
- build-docs-pdf
- publish-flatpak-repo
steps:
- name: Download Windows artifact
@@ -630,37 +232,6 @@ jobs:
name: linux-appimage
path: release-assets/linux
- name: Download macOS artifacts
uses: actions/download-artifact@v4.1.3
with:
pattern: macos-dmg-*
merge-multiple: true
path: release-assets/macos
- name: Download Debian artifact
uses: actions/download-artifact@v4.1.3
with:
name: linux-debian
path: release-assets/debian
- name: Download Fedora artifact
uses: actions/download-artifact@v4.1.3
with:
name: linux-fedora
path: release-assets/fedora
- name: Download Arch Linux artifact
uses: actions/download-artifact@v4.1.3
with:
name: linux-arch
path: release-assets/archlinux
- name: Download flatpakref
uses: actions/download-artifact@v4.1.3
with:
name: flatpakref
path: release-assets/flatpakref
- name: Download PDF manuals
uses: actions/download-artifact@v4.1.3
with:
@@ -680,11 +251,6 @@ jobs:
generateReleaseNotes: true
artifacts: >-
release-assets/windows/praktiKST-${{ github.ref_name }}-windows-x64.zip,
release-assets/linux/KST4Contest-${{ github.ref_name }}-linux-x86_64.AppImage,
release-assets/debian/KST4Contest-${{ github.ref_name }}-debian-amd64.deb,
release-assets/fedora/KST4Contest-${{ github.ref_name }}-fedora-x86_64.rpm,
release-assets/archlinux/KST4Contest-${{ github.ref_name }}-archlinux-*.pkg.tar.zst,
release-assets/flatpakref/de.x08.KST4Contest.flatpakref,
release-assets/macos/KST4Contest-${{ github.ref_name }}-macos-*.dmg,
release-assets/linux/praktiKST-${{ github.ref_name }}-linux-x86_64.AppImage,
release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-en.pdf,
release-assets/docs/KST4Contest-${{ github.ref_name }}-manual-de.pdf
+2
View File
@@ -0,0 +1,2 @@
dr2x
oe3cin
+15832
View File
File diff suppressed because it is too large Load Diff
-2
View File
@@ -32,10 +32,8 @@ Für diesen Dienst ist ein Account erforderlich. Bitte eine Spende für Thomas i
1. AirScout starten.
2. In den AirScout-Einstellungen den OV3T-Feed-Account eintragen (Benutzername, Passwort, URL).
![AirscoutSchritt1](as_plane_feed_1.png)
![AirscoutSchritt2](as_plane_feed_2.png)
3. Verbindung testen.
### Schritt 2: UDP-Kommunikation für KST4Contest aktivieren
+1 -78
View File
@@ -8,83 +8,6 @@ Versionsverlauf von KST4Contest / PraktiKST.
letzter Changelog bitte aus GitHub entnehmen. Der bisherige Changelog
## v1.40 (2026-02-16)
**Großes Feature-Release: Score-System, AP-Timeline, Win-Test, PSTRotator**
**Neu:**
- **Chatmember Score-System**: Jeder Chatmember erhält automatisch eine Prioritätsbewertung anhand von Antennenrichtung, Aktivitätszeit, Nachrichtenanzahl, aktiven Bändern, Frequenzen, Sked-Richtung und anderen Faktoren. Die Top-Kandidaten werden in einer eigenen Liste hervorgehoben.
- **AP-Timeline**: Für jeden möglichen AP-Ankunftsminuten-Slot werden bis zu 4 hochbewertete Stationen angezeigt, die erreichbar wären. Bevorzugt werden APs mit dem höchsten Potenzial, nicht die schnellste Ankunft. Stationen, auf die die eigene Antenne nicht zeigt, werden transparent dargestellt.
- **Win-Test-Unterstützung** (ab v1.31 als Beta, jetzt vollständig konfigurierbar): Log-Synchronisation, Frequenzauswertung und **Sked-Übergabe via UDP** vollständig integriert. In den Preferences aktivier-/deaktivierbar.
- **PSTRotator-Interface** (ab v1.31 als Beta, jetzt vollständig konfigurierbar): Aktualisierung der Rotatorposition direkt aus KST4Contest. In den Preferences aktivier-/deaktivierbar.
- **QSO-Sniffer**: Nachrichten von konfigurierbaren Rufzeichen-Listen werden automatisch in das PM-Fenster weitergeleitet.
- **Band-Alert bei gearbeiteten Stationen**: Wenn eine Station geloggt wird, erscheint ein Hinweis, wenn diese Station ein weiteres Band aktiv hat, auf dem man selbst ebenfalls QRV ist.
- **Sked-Erinnerungs-ALERT**: Pro Chatmember kann ein Sked-Alarm mit automatischen Nachrichten in konfigurierbaren Intervallen (2+1 / 5+2+1 / 10+5+2+1 Minuten vor dem Sked) eingerichtet werden, plus akustische und optische Benachrichtigung.
- **Chat-Historie beim Start laden**: Beim Verbindungsaufbau wird die Serverhistorie geladen, um aktive Chatmember und letzte Nachrichten sofort sichtbar zu machen.
- **Skedfail-Button**: Im FurtherInfo-Panel kann ein Sked-Misserfolg für einen Chatmember markiert werden, was dessen Score senkt.
**Geändert:**
- AP-Notizen in DX-Cluster-Spots integriert.
- Scrolling der Chatmember-Tabelle folgt automatisch der aktuellen Nachrichtenauswahl.
- Generic Auto-Antwort und QRG-Auto-Antwort senden max. einmal pro 45 Sekunden pro Rufzeichen (verhindert Spam-Schleifen).
- Speicherbare Einstellungen erweitert: ServerDNS/Port, PSTRotator-Interface, Win-Test-Interface, Callsign-Sniffer, Dark-Mode-Standard.
- Datum in der Chat-Tabelle entfernt (nur Uhrzeit verbleibt spart Platz).
**Behoben:**
- Benutzerliste wird jetzt bei jedem Neu-Login automatisch sortiert.
- Posonpill-Nachrichten beenden jetzt nur genau eine Client-Instanz (nicht alle und nicht wtKST).
- wtKST: Absturz bei KST4Contest-Trennung behoben.
- Mehrere Probleme mit Rufzeichen-Suffixen wie `/p`, `-2` etc. behoben.
- `QTFDefault` wurde nicht korrekt gespeichert → behoben.
- AirScout-Watchlist (ASWATCHLIST) wurde nicht korrekt aktualisiert → behoben.
- Dark Mode: QRG-Felder wurden nicht vollständig angezeigt → behoben.
- Versionsnummer-Anzeige korrigiert.
---
## v1.31 (2025-12-13)
**Win-Test + PSTRotator Beta, QSO-Sniffer, DNS-Hotfix**
**Neu:**
- **Win-Test-Unterstützung** (Beta, noch nicht deaktivierbar): Log-Synchronisation und Frequenzauswertung.
- **PSTRotator-Unterstützung** (Beta, noch nicht deaktivierbar).
- **QSO-Sniffer**: Nachrichten von konfigurierbaren Rufzeichen werden ins PM-Fenster weitergeleitet.
**Geändert:**
- **DNS-Server geändert**: Von `www.on4kst.info` auf `www.on4kst.org` (Hotfix). Der DNS-Server ist ab sofort in den Preferences änderbar.
**Behoben:**
- Endlosschleife im Fehlerfall friert den Client ein → behoben.
---
## v1.266 (2025-10-03)
**AirScout-Fix für Rufzeichen mit Suffix**
**Behoben:**
- AirScout-Interface funktionierte nicht, wenn das Login-Rufzeichen einen Suffix enthielt (z. B. `9A1W-2`). AirScout kann mit diesem Format nicht umgehen es wird jetzt nur noch das Basis-Rufzeichen ohne Suffix an AirScout übergeben.
*(Fehler gemeldet und getestet von 9A2HM / Kreso herzlichen Dank!)*
---
## v1.265 (2025-09-28)
**Richtungs-Buttons bleiben aktiviert eingefärbt**
**Behoben:**
- Richtungs-Buttons (N / NE / E usw.) behalten jetzt ihre Farbe, wenn sie aktiviert sind, sodass der Aktivierungsstatus auf einen Blick erkennbar ist.
---
## v1.264 (2025-08-02)
**Simplelogfile: Rufzeichen-Erkennung verbessert**
**Behoben:**
- Rufzeichen wie `S53CC`, `S51A` usw. wurden in der SimpleLogFile-Auswertung nicht als gearbeitet markiert → Erkennungsmuster verbessert.
*(Fehler gemeldet von Boris, S53CC danke!)*
---
## v1.263 (2025-06-08)
**AirScout-Kommunikation und Login-Name**
@@ -230,6 +153,6 @@ Erste öffentlich veröffentlichte Version. Grundfunktionen:
## Geplante Features
- `MYQTF`-Variable (eigene Antennenrichtung als Text)
- ~~Lebensdauer für den Worked-Status (automatisches Zurücksetzen)~~ ✅ **Umgesetzt in v1.40** (3-Tage-Lebensdauer, kein manuelles Zurücksetzen mehr nötig)
- Lebensdauer für den Worked-Status (automatisches Zurücksetzen)
- Filterung des „Cluster & QSO der anderen"-Fensters auf eigenes QTF
- Weitere Topografie-basierte Berechnungen für die Richtungswarnung
+7 -71
View File
@@ -135,86 +135,22 @@ Für ausgewählte Stationen in der Benutzerliste gibt es direkte Buttons, um das
---
## Sked-Erinnerungen mit ALERT (ab v1.40)
## Sked-Erinnerungen (Sked Reminder Service)
Für jeden Chatmember kann ein Sked-Erinnerungsdienst mit automatischen Nachrichten aktiviert werden. Konfigurierbare Intervallmuster:
- **2+1 Minuten**: Nachrichten bei 2 min und 1 min vor dem Sked.
- **5+2+1 Minuten**: Nachrichten bei 5, 2 und 1 min vor dem Sked.
- **10+5+2+1 Minuten**: Nachrichten bei 10, 5, 2 und 1 min vor dem Sked.
Zusätzlich zu den Nachrichten an die Gegenstation gibt es eine **akustische und optische Benachrichtigung** für den eigenen Operator, sodass kein Sked vergessen wird.
Aktivierung: FurtherInfo-Panel der entsprechenden Station.
Für vereinbarte Skeds können automatische Erinnerungs-PMs konfiguriert werden, die X Minuten vor dem vereinbarten Zeitpunkt gesendet werden. Die Erinnerungen werden aus dem FurtherInfo-Panel heraus aktiviert.
---
## QSO-Sniffer (ab v1.31)
## Prioritätsliste / Score-Service
Der QSO-Sniffer überwacht den Chat auf Nachrichten von einer konfigurierbaren Rufzeichen-Liste und leitet diese automatisch in das **PM-Fenster** weiter. So gehen keine relevanten Nachrichten im allgemeinen Chat-Rauschen unter.
KST4Contest berechnet automatisch eine **Prioritätsliste** der interessantesten Gesprächspartner, basierend auf:
Konfiguration: [Konfiguration Sniffer-Einstellungen](de-Konfiguration#sniffer-einstellungen-ab-v131)
---
## Win-Test-Integration (ab v1.31, vollständig ab v1.40)
KST4Contest unterstützt [Win-Test](https://www.win-test.com/) vollständig als Logprogramm:
- **Log-Synchronisation**: Gearbeitete Stationen werden automatisch aus Win-Test übernommen und in der Benutzerliste markiert.
- **Frequenz-Auswertung**: Die aktuelle TRX-Frequenz wird aus Win-Test-UDP-Paketen ausgewertet und befüllt die `MYQRG`-Variable.
- **Sked-Übergabe (SKED Push via UDP)**: Vereinbarte Skeds aus KST4Contest können direkt an Win-Test übertragen werden, sodass das Rufzeichen der Gegenstation im Win-Test-Sked-Fenster erscheint.
Details zur Konfiguration: [Konfiguration Win-Test-Netzwerk-Listener](de-Konfiguration#win-test-netzwerk-listener)
---
## PSTRotator-Interface (ab v1.31, vollständig ab v1.40)
KST4Contest kann die Antennenrichtung direkt über **PSTRotator** steuern. Wenn in der Benutzerliste eine Station ausgewählt wird, kann der Rotator automatisch auf den QTF zur ausgewählten Station gedreht werden.
Konfiguration: [Konfiguration PSTRotator-Einstellungen](de-Konfiguration#pstrotator-einstellungen-ab-v131)
---
## Band-Alert bei neuen QSOs (ab v1.40)
Wenn eine Station geloggt wird, prüft KST4Contest automatisch, ob diese Station im Chat weitere aktive Bänder angezeigt hat, auf denen man selbst ebenfalls QRV ist. Falls ja, erscheint ein **Hinweis-Alert**, damit keine Multi-Band-Möglichkeit übersehen wird.
---
## Worked-Tag-Lebensdauer (ab v1.40)
Gearbeitete Stationen werden nach **3 Tagen** automatisch aus der Datenbank entfernt. Ein manuelles Zurücksetzen der Worked-Datenbank vor jedem Contest ist damit nicht mehr zwingend notwendig die Datenbank hält sich selbst aktuell.
---
## Chatmember Score-System / Prioritätsliste (ab v1.40)
KST4Contest berechnet automatisch eine **Prioritätsbewertung** für jeden aktiven Chatmember. Der Score setzt sich zusammen aus:
- Antennenrichtung der Gegenstation (zeigt sie auf mich?)
- Richtungserkennung
- QRB (Entfernung)
- Aktivitätszeit und Nachrichtenanzahl
- Aktive Bänder und Frequenzen
- AP-Verfügbarkeit (AirScout)
- Sked-Richtung
- Sked-Erfolgsrate und Skedfail-Markierungen
- Worked-Status
Die Top-Kandidaten werden in einer eigenen Prioritätsliste hervorgehoben und helfen, im Contest-Stress die wichtigsten Stationen nicht zu übersehen.
Stationen, bei denen ein Sked gescheitert ist, können über den **Skedfail-Button** im FurtherInfo-Panel markiert werden das senkt ihren Score vorübergehend.
---
## AP-Timeline (ab v1.40)
Eine visuelle Zeitleiste zeigt für jeden möglichen AP-Ankunftsminuten-Slot bis zu 4 hochbewertete Stationen, die per Aircraft Scatter erreichbar wären. Priorisierungskriterien:
- Bevorzugt werden APs mit dem **höchsten Reflexionspotenzial** (nicht unbedingt die schnellste Ankunft).
- Stationen, auf die die eigene Antenne nicht zeigt, werden **transparent** dargestellt.
So kann der Contest-Operator auf einem Blick sehen, welche Stationen wann und über welche Flugzeuge erreichbar sein werden.
Die Top-Kandidaten werden in einer eigenen Liste angezeigt und helfen, im Contest-Stress die wichtigsten Stationen nicht zu übersehen.
---
-11
View File
@@ -44,17 +44,6 @@ Der ON4KST-Chat ist der De-facto-Standard für Skeds auf den 144-MHz-und-höher-
- **GitHub**: https://github.com/praktimarc/kst4contest
- **Download**: https://github.com/praktimarc/kst4contest/releases/latest
### Fehler melden (Issue erstellen)
Beim Melden eines Fehlers bitte **immer die Logdatei anhängen**. KST4Contest schreibt automatisch eine Fehler-Logdatei (nur Fehlermeldungen, keine persönlichen Daten):
| Betriebssystem | Pfad zur Logdatei |
|---|---|
| Linux / macOS | `~/.praktiKST/kst4contest-errors.log` |
| Windows | `C:\Users\<Benutzername>\.praktiKST\kst4contest-errors.log` |
Beim Erstellen eines Issues auf GitHub steht eine Vorlage bereit, die alle wichtigen Felder abfragt.
---
## Danksagungen
+9 -80
View File
@@ -42,29 +42,11 @@ Der Dateiname hat das Format `praktiKST-v<Versionsnummer>-windows-x64.zip `.
### Linux
Mehrere Paketformate stehen auf der Releases-Seite zur Verfügung:
Die aktuelle Version kann als AppImage heruntergeladen werden:
**https://github.com/praktimarc/kst4contest/releases/latest**
| Format | Dateiname | Geeignet für |
|---|---|---|
| AppImage | `KST4Contest-v<Version>-linux-x86_64.AppImage` | Alle Distributionen |
| Debian-Paket | `KST4Contest-v<Version>-debian-amd64.deb` | Debian, Ubuntu, Linux Mint, … |
| RPM-Paket | `KST4Contest-v<Version>-fedora-x86_64.rpm` | Fedora, RHEL, openSUSE, … |
| Arch-Paket | `KST4Contest-v<Version>-archlinux-x86_64.pkg.tar.zst` | Arch Linux, Manjaro, … |
| Flatpak | `de.x08.KST4Contest.flatpakref` | Alle Distributionen mit Flatpak |
> **Empfehlung für Linux:** Die Flatpak-Installation ist der einfachste Weg, immer aktuell zu bleiben `flatpak update` erledigt alle zukünftigen Updates automatisch. Das Repository ist GPG-signiert.
### macOS
> ⚠️ **Best-Effort-Support:** macOS-Builds werden als zusätzliche Option bereitgestellt, sind aber **nicht umfassend getestet**. Wir bauen und veröffentlichen macOS-Binaries mit jedem Release, können allerdings nicht alle Szenarien unter macOS testen. Bei Problemen freuen wir uns über eine Rückmeldung wir versuchen unser Bestes, können aber nicht den gleichen Support-Umfang wie für Windows und Linux garantieren.
Die aktuelle Version kann als DMG-Disk-Image heruntergeladen werden (für Apple-Silicon- und Intel-Macs verfügbar):
**https://github.com/praktimarc/kst4contest/releases/latest**
Der Dateiname hat das Format `KST4Contest-v<Versionsnummer>-macos-<Architektur>.dmg`, wobei `<Architektur>` entweder `arm64` (Apple Silicon) oder `x86_64` (Intel) ist.
Der Dateiname hat das Format `praktiKST-v<Versionsnummer>-linux-x86_64.AppImage`.
---
@@ -80,56 +62,10 @@ Der Dateiname hat das Format `KST4Contest-v<Versionsnummer>-macos-<Architektur>.
Die Einstellungen werden unter `%USERPROFILE%\.praktikst\preferences.xml` gespeichert.
### Linux
Die Einstellungen werden immer unter `~/.praktikst/preferences.xml` gespeichert.
#### AppImage
1. AppImage herunterladen.
2. Ausführbar machen: `chmod +x KST4Contest-v<Version>-linux-x86_64.AppImage`
3. Starten.
#### Debian / Ubuntu
```bash
sudo apt install ./KST4Contest-v<Version>-debian-amd64.deb
```
Oder die `.deb`-Datei im Dateimanager doppelklicken.
#### Fedora / RHEL
```bash
sudo dnf install ./KST4Contest-v<Version>-fedora-x86_64.rpm
```
#### Arch Linux
```bash
sudo pacman -U KST4Contest-v<Version>-archlinux-x86_64.pkg.tar.zst
```
#### Flatpak
Die Datei `de.x08.KST4Contest.flatpakref` aus dem [aktuellen Release](https://github.com/praktimarc/kst4contest/releases/latest) herunterladen und öffnen, oder:
```bash
flatpak install de.x08.KST4Contest.flatpakref
```
Oder den Remote manuell hinzufügen:
```bash
flatpak remote-add kst4contest https://praktimarc.github.io/kst4contest/
flatpak install kst4contest de.x08.KST4Contest
```
### macOS
1. DMG-Datei für die eigene Architektur herunterladen (Apple Silicon oder Intel).
2. DMG-Datei öffnen.
3. `KST4Contest.app` in den **Programme**-Ordner ziehen.
4. Beim ersten Start zeigt macOS ggf. eine Warnung, da die App nicht notarisiert ist. Zum Öffnen:
- Rechtsklick (oder Ctrl-Klick) auf `KST4Contest.app` im Finder → **Öffnen** wählen.
- Alternativ: **Systemeinstellungen → Datenschutz & Sicherheit****Trotzdem öffnen** klicken.
5. KST4Contest aus dem Programme-Ordner oder dem Launchpad starten.
2. AppImage in gewünschten Ordner entpacken.
3. AppImage ausführbar machen (geht im Terminal mit `chmod +x praktiKST-v<Versionsnummer>-linux-x86_64.AppImage`)
4. AppImage ausführen.
Die Einstellungen werden unter `~/.praktikst/preferences.xml` gespeichert.
@@ -157,17 +93,10 @@ Die Einstellungsdatei (`preferences.xml`) bleibt erhalten, da sie im Benutzerord
#### Linux
- **AppImage**: Neues AppImage herunterladen, ausführbar machen (`chmod +x`), altes optional löschen.
- **Debian/Ubuntu**: `sudo apt install ./KST4Contest-v<Version>-debian-amd64.deb`
- **Fedora/RHEL**: `sudo dnf upgrade ./KST4Contest-v<Version>-fedora-x86_64.rpm`
- **Arch Linux**: `sudo pacman -U KST4Contest-v<Version>-archlinux-x86_64.pkg.tar.zst`
- **Flatpak (Repository)**: `flatpak update` aktualisiert alle Flatpak-Apps einschließlich KST4Contest.
#### macOS
1. Neue DMG-Datei herunterladen.
2. DMG öffnen.
3. Die neue `KST4Contest.app` in den **Programme**-Ordner ziehen und die alte Version ersetzen.
Derzeit folgendermaßen:
1. neues AppImage herunterladen
2. neues AppImage ausführbar makieren
3. (optional) altes AppImage löschen.
---
+2 -54
View File
@@ -39,17 +39,6 @@ Maximale Entfernung (in km), für die Richtungs-Warnungen ausgelöst werden soll
---
## Server-Einstellungen (ab v1.31)
Der Chat-Server-DNS und -Port sind in den Preferences konfigurierbar:
- **Server-DNS**: Standard `www.on4kst.org` (ab v1.31 geändert von `www.on4kst.info`).
- **Port**: Standardport des ON4KST-Servers.
Eine Änderung ist nur notwendig, wenn der Server umzieht oder ein alternativer Endpunkt genutzt wird.
---
## Log-Sync-Einstellungen
Drei Methoden stehen zur Verfügung, um gearbeitete Stationen automatisch zu markieren. Details: [Log-Synchronisation](de-Log-Synchronisation).
@@ -133,55 +122,14 @@ Neuer Einstellungsbereich mit folgenden Optionen:
---
## Win-Test-Netzwerk-Listener (ab v1.31)
Dedizierter Empfänger für Win-Test-spezifische UDP-Pakete. Ermöglicht:
- **Log-Synchronisation**: Gearbeitete Stationen werden aus Win-Test übernommen und in der Benutzerliste markiert.
- **Frequenz-Auswertung**: Die aktuelle TRX-Frequenz aus Win-Test befüllt die `MYQRG`-Variable.
- **Sked-Übergabe (SKED Push)**: Skeds aus KST4Contest werden via UDP direkt an Win-Test übergeben. Der UDP-Broadcast-Standardport von Win-Test (9871) wird verwendet.
Einstellungen:
- **Aktivieren/Deaktivieren**: Checkbox in den Preferences (ab v1.40).
- **Port**: Konfigurierbarer UDP-Port für den Win-Test-Listener.
- **Sked-UDP-Adresse und Port**: Zieladresse und Port für die SKED-Übergabe an Win-Test.
> **Hinweis**: Der Win-Test-Listener ist ein **zusätzlicher** Listener der Standard-QSO-UDP-Broadcast-Listener auf Port 12060 bleibt davon unabhängig.
---
## PSTRotator-Einstellungen (ab v1.31)
KST4Contest kann die Antennenrichtung über PSTRotator steuern.
Einstellungen:
- **Aktivieren/Deaktivieren**: Checkbox in den Preferences (ab v1.40).
- **IP-Adresse**: IP-Adresse des PSTRotator-Rechners (Standard: `127.0.0.1` bei Betrieb auf demselben PC).
- **Port**: Kommunikationsport von PSTRotator.
> **Hinweis**: Nach einem Klick auf den Richtungs-Button wartet KST4Contest kurz auf die Rotatorantwort. Bei langsamen Rotoren (z. B. SPID) kann es zu einer kleinen Verzögerung kommen.
---
## Sniffer-Einstellungen (ab v1.31)
Der QSO-Sniffer filtert Chat-Nachrichten von konfigurierbaren Rufzeichen und leitet sie ins PM-Fenster weiter.
Einstellungen:
- **Rufzeichen-Liste**: Kommagetrennte Liste von Rufzeichen, deren Nachrichten immer in das PM-Fenster weitergeleitet werden sollen.
Anwendungsfall: Wichtige Stationen (z. B. DX-Peditionen oder feste Verbündete im Contest) im Auge behalten, ohne den Haupt-Chat ständig zu beobachten.
---
## Worked Station Database Settings (Gearbeitete-Stationen-Datenbank)
Die interne Worked-Datenbank enthält:
Vor jedem Contest die interne Worked-Datenbank zurücksetzen! Enthält:
- Worked-Status aller Stationen (pro Band)
- NOT-QRV-Tags (seit v1.2)
**Ab v1.40**: Einträge haben eine automatische Lebensdauer von **3 Tagen** ein manuelles Zurücksetzen vor jedem Contest ist nicht mehr zwingend notwendig. Für ein vollständiges Reset kann trotzdem die Schaltfläche **„Reinitialize"** verwendet werden.
Schaltfläche **„Reinitialize"** unter der Tabelle verwenden. Eine geplante Funktion ist eine automatische Ablaufzeit für den Worked-Status.
---
+6 -17
View File
@@ -87,22 +87,16 @@ Win-Test wird mit einem dedizierten UDP-Netzwerk-Listener unterstützt, der das
**Vorteile der Win-Test Integration:**
- Automatische QSO-Synchronisation zur Markierung gearbeiteter Stationen.
- **Sked-Übergabe (ADDSKED):** Über den Button "Create sked" im Stationsinfo-Panel wird nicht nur in KST4Contest ein Sked angelegt, sondern dieser auch *direkt per UDP an das Win-Test Netzwerk als ADDSKED-Paket gesendet* automatisch, sobald der Listener aktiv ist.
- **Sked-Übergabe (ADDSKED):** Über den Button "Create sked" im Stationsinfo-Panel wird nicht nur in KST4Contest ein Sked angelegt, sondern dieses auch *direkt per UDP an das Win-Test Netzwerk als ADDSKED-Paket gesendet*.
- Es kann zwischen den Sked-Modi "AUTO", "SSB" oder "CW" gewählt werden.
- **Automatische QRG-Auflösung für SKEDs:** KST4Contest wählt die Sked-Frequenz intelligent:
1. Hat die Gegenstation in einer Chat-Nachricht ihre QRG genannt, wird diese verwendet.
2. Sonst wird die eigene aktuelle QRG verwendet (aus Win-Test STATUS oder manueller Eingabe).
**Einstellungen im Reiter „Log-Synchronisation":**
- `Receive Win-Test network based UDP log messages` aktivieren.
**Notwendige Einstellungen in KST4Contest:**
- `UDP-Port for Win-Test listener` (Standard: 9871).
- `Receive Win-Test network based UDP log messages` aktivieren.
- `Win-Test sked transmission (push via ADDSKED to Win-Test network)` aktivieren.
- `KST station name in Win-Test network (src of SKED packets)`: Legt fest, unter welchem Stationsnamen KST4Contest im WT-Netzwerk auftritt (z.B. "KST").
- `Win-Test network broadcast address`: Wird i.d.R. automatisch erkannt; erforderlich für das Senden von Sked-Paketen.
**Einstellungen im Reiter „TRX-Synchronisation":**
- `Win-Test STATUS QRG Sync`: Wenn aktiviert, übernimmt KST4Contest die aktuelle Transceiverfrequenz aus dem Win-Test STATUS-Paket als eigene QRG (MYQRG).
- `Use pass frequency from Win-Test STATUS`: Statt der eigenen TRX-QRG wird die im STATUS-Paket enthaltene Pass-Frequenz als MYQRG verwendet (für Multi-Op-Setups, bei denen mit einer Pass-QRG gearbeitet wird).
- `Win-Test station name filter`: Wird hier ein Name eingetragen (z.B. "STN1"), verarbeitet KST4Contest nur Pakete dieser Win-Test-Instanz. Leer lassen, um alle zu akzeptieren.
- `Win-Test station name filter`: Wenn hier ein Name eingetragen wird (z.B. "STN1"), werden nur QSOs von dieser bestimmten Win-Test Instanz verarbeitet. Leer lassen, um alle zu akzeptieren.
- `Win-Test network broadcast address`: Wird idR automatisch erkannt und ist erforderlich, um die Sked-Pakete ins Netzwerk zu senden.
**Einstellungen in Win-Test:**
- Das Netzwerk in Win-Test muss aktiv sein.
@@ -118,11 +112,6 @@ Neben der QSO-Synchronisation übertragen UCXLog und andere Programme auch die *
**Ergebnis**: Die eigene QRG muss im Chat nie mehr manuell eingegeben werden ein Klick auf den MYQRG-Button oder die Verwendung der Variable im Beacon genügt.
**Quellen für die eigene QRG (MYQRG):**
- UCXLog, N1MM+, DXLog.net, QARTest via UDP-Port 12060
- Win-Test STATUS-Paket (optional, konfigurierbar im Reiter „TRX-Synchronisation" unter „Win-Test STATUS QRG Sync")
- Manuelle Eingabe im QRG-Feld
> **Hinweis für Multi-Setup**: Bei zwei Logprogrammen an zwei Computern sollte nur **eines** die Frequenzpakete senden. KST4Contest kann nicht zwischen den Quellen unterscheiden und verarbeitet alle eingehenden Pakete.
---
-2
View File
@@ -32,10 +32,8 @@ An account is required for this service. Please consider donating to Thomas
1. Start AirScout.
2. Enter your OV3T feed account details (username, password, URL) in the AirScout settings.
![AirscoutStep1](as_plane_feed_1.png)
![AirscoutStep2](as_plane_feed_2.png)
3. Test the connection.
### Step 2: Enable UDP Communication for KST4Contest
+1 -78
View File
@@ -8,83 +8,6 @@ Version history of KST4Contest / PraktiKST.
For the latest changelog, please refer to GitHub. The previous changelog is below.
## v1.40 (2026-02-16)
**Major Feature Release: Score System, AP Timeline, Win-Test, PSTRotator**
**New:**
- **Chatmember Score System**: Every chat member is automatically scored based on antenna direction, activity time, message count, active bands, frequencies, sked direction (degrees), and other factors. Top candidates are highlighted in a dedicated list.
- **AP Timeline**: For each minute of possible aircraft arrival, up to 4 highly-scored stations are shown that should be workable. Aircraft with the highest potential are preferred over the fastest arrival. Chat members whose antenna is not pointing towards you are shown transparently.
- **Win-Test Support** (Beta since v1.31, now fully configurable): Log synchronisation, frequency parsing and **sked handover via UDP** fully integrated. Can be enabled/disabled in Preferences.
- **PSTRotator Interface** (Beta since v1.31, now fully configurable): Rotator position updates directly from KST4Contest. Can be enabled/disabled in Preferences.
- **QSO Sniffer**: Messages from configurable callsign lists are automatically forwarded to the PM window.
- **Band Alert for logged stations**: When a station is logged, a hint appears if that station has another active band that you are also QRV on.
- **Sked Reminder ALERT**: A sked alarm with automatic messages in configurable intervals (2+1 / 5+2+1 / 10+5+2+1 minutes before the sked) can be set up for each chat member, plus acoustic and visual notification.
- **Load chat history on startup**: Chat server history is loaded on connect to immediately see active members and recent messages.
- **Skedfail button**: In the FurtherInfo panel, a sked failure can be marked for a chat member, which lowers their priority score.
**Changed:**
- AP notes added to internal DX cluster spots.
- Chat member table scrolling follows the current message selection automatically.
- Generic auto-reply and QRG auto-reply now fire a maximum of once every 45 seconds per callsign (prevents spam and message ping-pong).
- New saveable settings: ServerDNS/Port, PSTRotator interface, Win-Test interface, callsign sniffer, Dark Mode on by default.
- Date column removed from chat table (time only saves space).
**Fixed:**
- User list now automatically sorted on every new member sign-on.
- Posonpill messages now terminate exactly one client instance (no longer affects all instances or wtKST).
- wtKST: crash on KST4Contest disconnection fixed.
- Multiple issues with callsign suffixes like `/p`, `-2`, etc. fixed throughout.
- `QTFDefault` was not saved correctly → fixed.
- AirScout watchlist (ASWATCHLIST) was not being updated → fixed.
- Dark Mode: QRG fields not displayed at full size → fixed.
- Version number display corrected.
---
## v1.31 (2025-12-13)
**Win-Test + PSTRotator Beta, QSO Sniffer, DNS Hotfix**
**New:**
- **Win-Test support** (Beta, not yet deactivatable): Log synchronisation and frequency parsing.
- **PSTRotator support** (Beta, not yet deactivatable).
- **QSO Sniffer**: Messages from configurable callsigns are forwarded to the PM window.
**Changed:**
- **DNS server changed**: From `www.on4kst.info` to `www.on4kst.org` (hotfix). The DNS server is now configurable in Preferences.
**Fixed:**
- Endless loop in error case freezes the client → fixed.
---
## v1.266 (2025-10-03)
**AirScout Fix for Callsigns with Suffix**
**Fixed:**
- AirScout interface did not work when the login callsign contained a suffix (e.g. `9A1W-2`). AirScout cannot handle this format only the base callsign without suffix is now passed to AirScout.
*(Bug reported and tested by 9A2HM / Kreso many thanks!)*
---
## v1.265 (2025-09-28)
**Direction Buttons Stay Coloured When Active**
**Fixed:**
- Direction buttons (N / NE / E etc.) now keep their highlight colour when activated, making the active state immediately visible.
---
## v1.264 (2025-08-02)
**Simplelogfile: Improved Callsign Recognition**
**Fixed:**
- Callsigns like `S53CC`, `S51A`, etc. were not being marked as worked in the SimpleLogFile interpreter → recognition pattern improved.
*(Bug reported by Boris, S53CC thank you!)*
---
## v1.263 (2025-06-08)
**AirScout Communication and Login Name**
@@ -230,6 +153,6 @@ First publicly released version. Core features:
## Planned Features
- `MYQTF` variable (own antenna direction as text)
- ~~Lifetime for worked status (automatic reset)~~ ✅ **Implemented in v1.40** (3-day lifetime, no manual reset needed anymore)
- Lifetime for worked status (automatic reset)
- Filtering the "Cluster & QSO of others" window to own QTF
- Further topography-based calculations for direction warnings
+3 -55
View File
@@ -39,20 +39,9 @@ Maximum distance (in km) for which direction warnings should be triggered. A rea
---
## Server Settings (from v1.31)
The chat server DNS and port are configurable in the Preferences:
- **Server DNS**: Default `www.on4kst.org` (changed from `www.on4kst.info` in v1.31 hotfix).
- **Port**: Default port of the ON4KST server.
A change is only needed if the server moves or an alternative endpoint is used.
---
## Log Sync Settings
Three methods are available for automatically marking worked stations. Details: [Log Synchronisation](en-Log-Sync).
Two methods are available for automatically marking worked stations. Details: [Log Synchronisation](en-Log-Sync).
### Universal File Based Callsign Interpreter (Simplelogfile)
@@ -133,55 +122,14 @@ New settings section with the following options:
---
## Win-Test Network Listener (from v1.31)
A dedicated listener for Win-Test-specific UDP packets. Enables:
- **Log synchronisation**: Worked stations are retrieved from Win-Test and marked in the user list.
- **Frequency parsing**: The current TRX frequency from Win-Test populates the `MYQRG` variable.
- **Sked handover (SKED push)**: Skeds from KST4Contest are passed directly to Win-Test via UDP. Win-Test's default UDP broadcast port (9871) is used.
Settings:
- **Enable/Disable**: Checkbox in Preferences (from v1.40).
- **Port**: Configurable UDP port for the Win-Test listener.
- **Sked UDP address and port**: Target address and port for SKED handover to Win-Test.
> **Note**: The Win-Test listener is an **additional** listener the standard QSO UDP broadcast listener on port 12060 remains independent.
---
## PSTRotator Settings (from v1.31)
KST4Contest can control antenna direction via PSTRotator.
Settings:
- **Enable/Disable**: Checkbox in Preferences (from v1.40).
- **IP address**: IP address of the PSTRotator computer (default: `127.0.0.1` when running on the same PC).
- **Port**: Communication port of PSTRotator.
> **Note**: After clicking a direction button, KST4Contest waits briefly for the rotator response. With slow rotors (e.g. SPID) there may be a small delay.
---
## Sniffer Settings (from v1.31)
The QSO sniffer filters chat messages from configurable callsigns and forwards them to the PM window.
Settings:
- **Callsign list**: Comma-separated list of callsigns whose messages are always forwarded to the PM window.
Use case: Keep track of important stations (e.g. DX expeditions or trusted contest allies) without constantly monitoring the main chat.
---
## Worked Station Database Settings
The internal worked database contains:
Reset the internal worked database before each contest! It contains:
- Worked status of all stations (per band)
- NOT-QRV tags (since v1.2)
**From v1.40**: Entries have an automatic lifetime of **3 days** manually resetting before each contest is no longer strictly necessary. For a full reset, the **"Reinitialize"** button is still available.
Use the **"Reinitialize"** button below the table. A planned feature is an automatic expiration time for the worked status.
---
+7 -71
View File
@@ -135,86 +135,22 @@ For selected stations in the user list, there are direct buttons to open the **Q
---
## Sked Reminders with ALERT (from v1.40)
## Sked Reminders (Sked Reminder Service)
A sked reminder service with automatic messages can be activated for each chat member. Configurable interval patterns:
- **2+1 minutes**: Messages at 2 min and 1 min before the sked.
- **5+2+1 minutes**: Messages at 5, 2 and 1 min before the sked.
- **10+5+2+1 minutes**: Messages at 10, 5, 2 and 1 min before the sked.
In addition to the automated messages to the remote station, there is an **acoustic and visual notification** for your own operator so no sked is ever missed.
Activate from the FurtherInfo panel of the corresponding station.
For agreed skeds, automatic reminder PMs can be configured, sent X minutes before the agreed time. Reminders are activated from the FurtherInfo panel.
---
## QSO Sniffer (from v1.31)
## Priority List / Score Service
The QSO sniffer monitors the chat for messages from a configurable callsign list and automatically forwards them to the **PM window**. This prevents relevant messages from being lost in the general chat traffic.
KST4Contest automatically calculates a **priority list** of the most interesting contacts, based on:
Configuration: [Configuration Sniffer Settings](en-Configuration#sniffer-settings-from-v131)
---
## Win-Test Integration (from v1.31, fully configurable from v1.40)
KST4Contest fully supports [Win-Test](https://www.win-test.com/) as a logging programme:
- **Log synchronisation**: Worked stations are automatically retrieved from Win-Test and marked in the user list.
- **Frequency parsing**: The current TRX frequency is read from Win-Test UDP packets and populates the `MYQRG` variable.
- **Sked handover (SKED push via UDP)**: Agreed skeds from KST4Contest can be pushed directly to Win-Test, so the remote callsign appears in Win-Test's sked window.
Details: [Configuration Win-Test Network Listener](en-Configuration#win-test-network-listener)
---
## PSTRotator Interface (from v1.31, fully configurable from v1.40)
KST4Contest can control antenna direction directly via **PSTRotator**. When a station is selected in the user list, the rotator can automatically be turned to the QTF of the selected station.
Configuration: [Configuration PSTRotator Settings](en-Configuration#pstrotator-settings-from-v131)
---
## Band Alert for New QSOs (from v1.40)
When a station is logged, KST4Contest automatically checks whether that station has shown any other active bands in the chat that you are also QRV on. If so, a **hint alert** appears so no multi-band opportunity is missed.
---
## Worked Tag Lifetime (from v1.40)
Worked stations are automatically removed from the database after **3 days**. Manually resetting the worked database before each contest is therefore no longer strictly necessary the database keeps itself up to date.
---
## Chatmember Score System / Priority List (from v1.40)
KST4Contest automatically calculates a **priority score** for each active chat member. The score is derived from:
- Antenna direction of the remote station (is it pointing towards me?)
- Direction detection
- QRB (distance)
- Activity time and message count
- Active bands and frequencies
- AP availability (AirScout)
- Sked direction (degrees)
- Sked success rate and skedfail markings
- Worked status
The top candidates are highlighted in a dedicated priority list, helping you not to miss the most important contacts during contest stress.
Stations with a failed sked can be marked using the **Skedfail button** in the FurtherInfo panel this temporarily lowers their score.
---
## AP Timeline (from v1.40)
A visual timeline shows up to 4 highly-scored stations per minute slot that should be workable via aircraft scatter. Prioritisation criteria:
- **Highest reflection potential** is preferred (not necessarily the fastest arrival).
- Stations towards which your antenna is not pointing are shown **transparently**.
This gives the contest operator a quick overview of which stations will be reachable via which aircraft and at what time.
The top candidates are shown in a separate list, helping you not to miss the most important stations during contest stress.
---
-11
View File
@@ -44,17 +44,6 @@ The ON4KST Chat is the de-facto standard for skeds on the 144 MHz and higher ban
- **GitHub**: https://github.com/praktimarc/kst4contest
- **Download**: https://github.com/praktimarc/kst4contest/releases/latest
### Reporting a bug (creating an issue)
When reporting a bug, please **always attach the log file**. KST4Contest automatically writes an error log (error messages only, no personal data):
| Operating system | Log file location |
|---|---|
| Linux / macOS | `~/.praktiKST/kst4contest-errors.log` |
| Windows | `C:\Users\<YourName>\.praktiKST\kst4contest-errors.log` |
When opening an issue on GitHub, a template is provided that guides you through all relevant fields.
---
## Acknowledgements
+9 -80
View File
@@ -42,29 +42,11 @@ The filename has the format `praktiKST-v<version_number>-windows-x64.zip`.
### Linux
Multiple package formats are available from the releases page:
The latest version can be downloaded as an AppImage:
**https://github.com/praktimarc/kst4contest/releases/latest**
| Format | Filename | Suitable for |
|---|---|---|
| AppImage | `KST4Contest-v<version>-linux-x86_64.AppImage` | All distributions |
| Debian package | `KST4Contest-v<version>-debian-amd64.deb` | Debian, Ubuntu, Linux Mint, … |
| RPM package | `KST4Contest-v<version>-fedora-x86_64.rpm` | Fedora, RHEL, openSUSE, … |
| Arch package | `KST4Contest-v<version>-archlinux-x86_64.pkg.tar.zst` | Arch Linux, Manjaro, … |
| Flatpak | `de.x08.KST4Contest.flatpakref` | All distributions with Flatpak |
> **Recommended for Linux:** The Flatpak installation is the easiest way to stay up to date — `flatpak update` handles all future updates automatically. The repository is GPG-signed for security.
### macOS
> ⚠️ **Best-Effort Support:** macOS builds are provided as a convenience but are **not fully tested**. We build and release macOS binaries with every release, but we cannot test every scenario on macOS. If you encounter issues, please report them we will do our best to address them, but cannot guarantee the same level of support as for Windows and Linux.
The latest version can be downloaded as a DMG disk image (available for both Apple Silicon and Intel Macs):
**https://github.com/praktimarc/kst4contest/releases/latest**
The filename has the format `KST4Contest-v<version_number>-macos-<arch>.dmg`, where `<arch>` is `arm64` (Apple Silicon) or `x86_64` (Intel).
The filename has the format `praktiKST-v<version_number>-linux-x86_64.AppImage`.
---
@@ -80,56 +62,10 @@ The filename has the format `KST4Contest-v<version_number>-macos-<arch>.dmg`, wh
Settings are stored at `%USERPROFILE%\.praktikst\preferences.xml`.
### Linux
Settings are always stored at `~/.praktikst/preferences.xml`.
#### AppImage
1. Download the AppImage.
2. Make it executable: `chmod +x KST4Contest-v<version>-linux-x86_64.AppImage`
3. Run it.
#### Debian / Ubuntu
```bash
sudo apt install ./KST4Contest-v<version>-debian-amd64.deb
```
Or double-click the `.deb` file in your file manager.
#### Fedora / RHEL
```bash
sudo dnf install ./KST4Contest-v<version>-fedora-x86_64.rpm
```
#### Arch Linux
```bash
sudo pacman -U KST4Contest-v<version>-archlinux-x86_64.pkg.tar.zst
```
#### Flatpak
Download `de.x08.KST4Contest.flatpakref` from the [latest release](https://github.com/praktimarc/kst4contest/releases/latest) and open it, or run:
```bash
flatpak install de.x08.KST4Contest.flatpakref
```
Or add the remote manually:
```bash
flatpak remote-add kst4contest https://praktimarc.github.io/kst4contest/
flatpak install kst4contest de.x08.KST4Contest
```
### macOS
1. Download the DMG file for your architecture (Apple Silicon or Intel).
2. Open the DMG file.
3. Drag `KST4Contest.app` into your **Applications** folder.
4. On first launch, macOS may show a warning because the app is not notarised. To open it:
- Right-click (or Control-click) on `KST4Contest.app` in Finder and choose **Open**.
- Alternatively, go to **System Settings → Privacy & Security** and click **Open Anyway**.
5. Run KST4Contest from your Applications folder or Launchpad.
2. Unzip the AppImage into a folder of your choice.
3. Make the AppImage executable (in the terminal with `chmod +x praktiKST-v<version_number>-linux-x86_64.AppImage`)
4. Run the AppImage.
Settings are stored at `~/.praktikst/preferences.xml`.
@@ -157,17 +93,10 @@ The settings file (`preferences.xml`) is preserved because it is stored in the u
#### Linux
- **AppImage**: Download the new AppImage, make it executable (`chmod +x`), optionally delete the old one.
- **Debian/Ubuntu**: `sudo apt install ./KST4Contest-v<version>-debian-amd64.deb`
- **Fedora/RHEL**: `sudo dnf upgrade ./KST4Contest-v<version>-fedora-x86_64.rpm`
- **Arch Linux**: `sudo pacman -U KST4Contest-v<version>-archlinux-x86_64.pkg.tar.zst`
- **Flatpak (repository)**: `flatpak update` updates all Flatpak apps including KST4Contest.
#### macOS
1. Download the new DMG file.
2. Open the DMG.
3. Drag the new `KST4Contest.app` into your **Applications** folder, replacing the old version.
Currently as follows:
1. Download the new AppImage
2. Mark the new AppImage as executable
3. (optional) Delete the old AppImage.
---
+7 -18
View File
@@ -87,22 +87,16 @@ Win-Test is supported with a dedicated UDP network listener that understands the
**Advantages of Win-Test Integration:**
- Automatic QSO synchronization to mark worked stations.
- **Sked Handover (ADDSKED):** Using the "Create sked" button in the station info panel not only creates a sked in KST4Contest but also *sends it directly via UDP to the Win-Test network as an ADDSKED packet* automatically, as soon as the listener is active. No separate toggle is needed.
- **Sked Handover (ADDSKED):** Using the "Create sked" button in the station info panel not only creates a sked in KST4Contest but also *sends it directly via UDP to the Win-Test network as an ADDSKED packet*.
- You can choose between "AUTO", "SSB", or "CW" sked modes.
- **Automatic QRG resolution for SKEDs:** KST4Contest selects the sked frequency intelligently:
1. If the other station mentioned their QRG in a recent chat message, that frequency is used.
2. Otherwise, your own current QRG is used (from Win-Test STATUS or manual entry).
**Settings in the "Log Synchronisation" tab:**
**Required Settings in KST4Contest:**
- `UDP-Port for Win-Test listener` (Default: 9871).
- Enable `Receive Win-Test network based UDP log messages`.
- `UDP-Port for Win-Test listener` (default: 9871).
- `KST station name in Win-Test network (src of SKED packets)`: Defines the station name KST4Contest uses in the WT network (e.g. "KST").
- `Win-Test network broadcast address`: Usually detected automatically; required to send sked packets to the network.
**Settings in the "TRX Synchronisation" tab:**
- `Win-Test STATUS QRG Sync`: When enabled, KST4Contest takes the current transceiver frequency from the Win-Test STATUS packet and uses it as your own QRG (MYQRG).
- `Use pass frequency from Win-Test STATUS`: Instead of the main TRX frequency, the pass frequency contained in the STATUS packet is used as MYQRG (useful for multi-op setups that operate with a dedicated pass QRG).
- `Win-Test station name filter`: If a name is entered here (e.g. "STN1"), KST4Contest only processes packets from that specific Win-Test instance. Leave empty to accept all.
- Enable `Win-Test sked transmission (push via ADDSKED to Win-Test network)`.
- `KST station name in Win-Test network (src of SKED packets)`: Defines the station name KST4Contest uses in the WT network (e.g., "KST").
- `Win-Test station name filter`: If a name is entered here (e.g., "STN1"), only QSOs from that specific Win-Test instance will be processed. Leave empty to accept all.
- `Win-Test network broadcast address`: Is usually detected automatically and is required to send sked packets to the network.
**Settings in Win-Test:**
- The network in Win-Test must be active.
@@ -118,11 +112,6 @@ In addition to QSO synchronisation, UCXLog and other programs also transmit the
**Result**: Your own QRG never needs to be typed manually in the chat clicking the MYQRG button or using the variable in the beacon is sufficient.
**Sources for your own QRG (MYQRG):**
- UCXLog, N1MM+, DXLog.net, QARTest via UDP port 12060
- Win-Test STATUS packet (optional, configurable in the "TRX Synchronisation" tab under "Win-Test STATUS QRG Sync")
- Manual entry in the QRG field
> **Note for multi-setup**: With two logging programs on two computers, only **one** should send frequency packets. KST4Contest cannot distinguish between sources and processes all incoming packets.
---
-528
View File
@@ -1,528 +0,0 @@
import socket
import threading
import time
import random
import traceback
from datetime import datetime, timedelta
# =====================================
# KST-Server-Simulator / DO5AMF
# Usage: change configuration below and
# run. Enter 127.0.0.1 : 23001 as a
# target in KST4Contest or another
# KST chat client.
# =====================================
# ==========================================
# KONFIGURATION
# ==========================================
PORT = 23001
HOST = '127.0.0.1'
MSG_TO_USER_INTERVAL = 300.0
LOGIN_LOGOUT_INTERVAL = 60.0
KEEP_ALIVE_INTERVAL = 10.0
CLIENT_WARMUP_TIME = 5.0
PROB_INACTIVE = 0.10
PROB_REACTIVE = 0.20
# QSY Wahrscheinlichkeit (Wie oft wechselt ein User seine Frequenz?)
# 0.05 = 5% Chance pro Nachricht, dass er die Frequenz ändert. Sonst bleibt er stabil.
PROB_QSY = 0.05
BANDS_VHF = { "2m": (144.150, 144.400), "70cm": (432.100, 432.300) }
BANDS_UHF = { "23cm": (1296.100, 1296.300), "3cm": (10368.100, 10368.250) }
CHANNELS_SETUP = {
"2": {
"NAME": "144/432 MHz",
"NUM_USERS": 777,
"BANDS": BANDS_VHF,
"RATES": {"PUBLIC": 0.5, "DIRECTED": 3.0},
"PERMANENT": [
{"call": "DK5EW", "name": "Erwin", "loc": "JN47NX"},
{"call": "DL1TEST", "name": "TestOp", "loc": "JO50XX"}
]
},
"3": {
"NAME": "Microwave",
"NUM_USERS": 333,
"BANDS": BANDS_UHF,
"RATES": {"PUBLIC": 0.2, "DIRECTED": 0.5},
"PERMANENT": [
{"call": "ON4KST", "name": "Alain", "loc": "JO20HI"},
{"call": "G4CBW", "name": "MwTest", "loc": "IO83AA"}
]
}
}
COUNTRY_MAPPING = {
"DL": ["JO", "JN"], "DA": ["JO", "JN"], "DF": ["JO", "JN"], "DJ": ["JO", "JN"], "DK": ["JO", "JN"], "DO": ["JO", "JN"],
"F": ["JN", "IN", "JO"], "G": ["IO", "JO"], "M": ["IO", "JO"], "2E": ["IO", "JO"],
"PA": ["JO"], "ON": ["JO"], "OZ": ["JO"], "SM": ["JO", "JP"], "LA": ["JO", "JP"],
"OH": ["KP"], "SP": ["JO", "KO"], "OK": ["JO", "JN"], "OM": ["JN", "KN"],
"HA": ["JN", "KN"], "S5": ["JN"], "9A": ["JN"], "HB9": ["JN"], "OE": ["JN"],
"I": ["JN", "JM"], "IK": ["JN", "JM"], "IU": ["JN", "JM"], "EA": ["IN", "IM"],
"CT": ["IM"], "EI": ["IO"], "GM": ["IO"], "GW": ["IO"], "YO": ["KN"],
"YU": ["KN"], "LZ": ["KN"], "SV": ["KM", "KN"], "UR": ["KO", "KN"],
"LY": ["KO"], "YL": ["KO"], "ES": ["KO"]
}
NAMES = ["Hans", "Peter", "Jo", "Alain", "Mike", "Sven", "Ole", "Jean", "Bob", "Tom", "Giovanni", "Mario", "Frank", "Steve", "Dave"]
MSG_TEMPLATES_WITH_FREQ = [
"QSY {freq}", "PSE QSY {freq}", "Calling CQ on {freq}", "I am QRV on {freq}",
"Listening on {freq}", "Can you try {freq}?", "Signals strong on {freq}",
"Scattering on {freq}", "Please go to {freq}", "Running test on {freq}",
"Any takers for {freq}?", "Back to {freq}", "QRG {freq}?", "Aircraft scatter {freq}"
]
MSG_TEMPLATES_TEXT_ONLY = [
"TNX for QSO", "73 all", "Anyone for sked?", "Good conditions",
"Nothing heard", "Rain scatter?", "Waiting for moonrise", "CQ Contest",
"QRZ?", "My locator is {loc}", "Band is open"
]
REPLY_TEMPLATES = [
"Hello {user}, 599 here", "Rgr {user}, tnx for report", "Yes {user}, QSY?",
"Sorry {user}, no copy", "Pse wait 5 min {user}", "Ok {user}, 73",
"Locator is {loc}", "Go to {freq} please", "Rgr {user}, gl"
]
# ==========================================
# CLIENT WRAPPER
# ==========================================
class ConnectedClient:
def __init__(self, sock, addr):
self.sock = sock
self.addr = addr
self.call = f"GUEST_{random.randint(1000,9999)}"
self.channels = {"2"}
self.login_time = time.time()
self.lock = threading.Lock()
def send_safe(self, data_str):
if not data_str: return True
with self.lock:
try:
self.sock.sendall(data_str.encode('latin-1', errors='replace'))
return True
except:
return False
def close(self):
try: self.sock.close()
except: pass
# ==========================================
# LOGIK KLASSEN
# ==========================================
class MessageFactory:
@staticmethod
def get_stable_frequency(user, band_name, min_f, max_f):
"""Liefert eine stabile Frequenz für diesen User auf diesem Band"""
# Wenn noch keine Frequenz da ist ODER Zufall zuschlägt (QSY)
if band_name not in user['freqs'] or random.random() < PROB_QSY:
freq_val = round(random.uniform(min_f, max_f), 3)
user['freqs'][band_name] = f"{freq_val:.3f}"
return user['freqs'][band_name]
@staticmethod
def get_chat_message(bands_config, user):
try:
# Entscheidung: Text mit Frequenz oder ohne?
if random.random() < 0.7:
# Wähle zufälliges Band aus den verfügbaren
band_name = random.choice(list(bands_config.keys()))
min_f, max_f = bands_config[band_name]
# Hole STABILE Frequenz für diesen User
freq_str = MessageFactory.get_stable_frequency(user, band_name, min_f, max_f)
return random.choice(MSG_TEMPLATES_WITH_FREQ).format(freq=freq_str)
else:
return random.choice(MSG_TEMPLATES_TEXT_ONLY).format(loc=user['loc'])
except: return "TNX 73"
@staticmethod
def get_reply_msg(bands, target_call, my_loc):
try:
tmpl = random.choice(REPLY_TEMPLATES)
freq_str = "QSY?"
# Bei Replies simulieren wir oft nur "QSY?" ohne konkrete Frequenz,
# oder nutzen eine zufällige, da der Kontext fehlt.
if "{freq}" in tmpl and bands:
band_name = random.choice(list(bands.keys()))
min_f, max_f = bands[band_name]
freq_str = f"{round(random.uniform(min_f, max_f), 3):.3f}"
return tmpl.format(user=target_call, loc=my_loc, freq=freq_str)
except: return "TNX 73"
class UserFactory:
registry = {}
@classmethod
def get_or_create_user(cls, channel_id, current_channel_users):
# 1. Reuse existing
candidates = [u for call, u in cls.registry.items() if call not in current_channel_users]
if candidates and random.random() < 0.5:
return random.choice(candidates)
# 2. Create new
return cls._create_new_unique_user(channel_id, current_channel_users)
@classmethod
def _create_new_unique_user(cls, channel_id, current_channel_users):
while True:
prefix = random.choice(list(COUNTRY_MAPPING.keys()))
num = random.randint(0, 9)
suffix = "".join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=random.randint(1,3)))
call = f"{prefix}{num}{suffix}"
if call in current_channel_users: continue
if call in cls.registry: return cls.registry[call]
valid_grids = COUNTRY_MAPPING[prefix]
grid_prefix = random.choice(valid_grids)
sq_num = f"{random.randint(0,99):02d}"
sub = "".join(random.choices("ABCDEFGHIJKLMNOPQRSTUVWXYZ", k=2))
loc = f"{grid_prefix}{sq_num}{sub}"
name = random.choice(NAMES)
rand = random.random()
if rand < PROB_INACTIVE: role = "INACTIVE"
elif rand < (PROB_INACTIVE + PROB_REACTIVE): role = "REACTIVE"
else: role = "ACTIVE"
# Neu V31: Frequenz-Gedächtnis
user_data = {
"call": call,
"name": name,
"loc": loc,
"role": role,
"freqs": {} # Speicher für { '2m': '144.300' }
}
cls.registry[call] = user_data
return user_data
@classmethod
def register_permanent(cls, user_data):
# Sicherstellen, dass auch Permanent User Freq-Memory haben
if "freqs" not in user_data:
user_data["freqs"] = {}
cls.registry[user_data['call']] = user_data
# ==========================================
# CHANNEL INSTANCE
# ==========================================
class ChannelInstance:
def __init__(self, cid, config, server):
self.id = cid
self.config = config
self.server = server
self.users_pool = []
self.online_users = {}
self.history_chat = []
self.last_pub = time.time()
self.last_dir = time.time()
self.last_me = time.time()
self.last_login = time.time()
self.rate_pub = 1.0 / config["RATES"]["PUBLIC"]
self.rate_dir = 1.0 / config["RATES"]["DIRECTED"]
self._init_data()
def _init_data(self):
print(f"[*] Init Channel {self.id} ({self.config['NAME']})...")
for u in self.config["PERMANENT"]:
u_full = u.copy()
u_full["role"] = "ACTIVE"
UserFactory.register_permanent(u_full)
self.online_users[u['call']] = u_full
for _ in range(self.config["NUM_USERS"]):
new_u = UserFactory.get_or_create_user(self.id, self.online_users.keys())
self.users_pool.append(new_u)
fill = int(self.config["NUM_USERS"] * 0.9)
for i in range(fill):
u = self.users_pool[i]
if u['call'] not in self.online_users:
self.online_users[u['call']] = u
print(f"[*] Channel {self.id} ready: {len(self.online_users)} Users.")
self._prefill_history()
def _prefill_history(self):
actives = [u for u in self.online_users.values() if u['role'] == "ACTIVE"]
if not actives: return
start = datetime.now() - timedelta(minutes=15)
for i in range(30):
msg_time = start + timedelta(seconds=i*30)
ts = str(int(msg_time.timestamp()))
sender = random.choice(actives)
if i % 2 == 0:
text = MessageFactory.get_chat_message(self.config["BANDS"], sender)
frame = f"CH|{self.id}|{ts}|{sender['call']}|{sender['name']}|0|{text}|0|\r\n"
else:
target = random.choice(list(self.online_users.values()))
text = MessageFactory.get_reply_msg(self.config["BANDS"], target['call'], sender['loc'])
frame = f"CH|{self.id}|{ts}|{sender['call']}|{sender['name']}|0|{text}|{target['call']}|\r\n"
self.history_chat.append(frame)
def tick(self, now):
actives = [u for u in self.online_users.values() if u['role'] == "ACTIVE"]
if not actives: return
# PUBLIC
if now - self.last_pub > self.rate_pub:
self.last_pub = now
u = random.choice(actives)
# V31: Nutzt jetzt get_chat_message, das das Freq-Memory abfragt
text = MessageFactory.get_chat_message(self.config["BANDS"], u)
ts = str(int(now))
frame = f"CH|{self.id}|{ts}|{u['call']}|{u['name']}|0|{text}|0|\r\n"
self._add_hist(frame)
self.server.broadcast_to_channel(self.id, frame)
# DIRECTED
if now - self.last_dir > self.rate_dir:
self.last_dir = now
if len(actives) > 5:
u1 = random.choice(actives)
u2 = random.choice(list(self.online_users.values()))
if u1 != u2:
if random.random() < 0.5:
# Auch hier Frequenzstabilität beachten
text = MessageFactory.get_chat_message(self.config["BANDS"], u1)
else:
text = MessageFactory.get_reply_msg(self.config["BANDS"], u2['call'], u1['loc'])
ts = str(int(now))
frame = f"CH|{self.id}|{ts}|{u1['call']}|{u1['name']}|0|{text}|{u2['call']}|\r\n"
self.server.broadcast_to_channel(self.id, frame)
if u2['role'] != "INACTIVE":
threading.Thread(target=self._schedule_reply, args=(u2['call'], u1['call']), daemon=True).start()
# MSG TO YOU
if now - self.last_me > MSG_TO_USER_INTERVAL:
self.last_me = now
target_client = self.server.get_random_subscriber(self.id)
if target_client and actives:
if not target_client.call.startswith("GUEST"):
sender = random.choice(actives)
text = MessageFactory.get_chat_message(self.config["BANDS"], sender)
print(f"[SIM Ch{self.id}] MSG TO YOU ({target_client.call})")
self.process_msg(sender['call'], sender['name'], text, target_client.call)
# LOGIN/LOGOUT
if now - self.last_login > LOGIN_LOGOUT_INTERVAL:
self.last_login = now
if random.choice(['IN', 'OUT']) == 'OUT' and len(self.online_users) > 20:
cands = [c for c in self.online_users if c not in [p['call'] for p in self.config["PERMANENT"]]]
if cands:
l = random.choice(cands)
del self.online_users[l]
self.server.broadcast_to_channel(self.id, f"UR6|{self.id}|{l}|\r\n")
else:
candidates = [u for u in self.users_pool if u['call'] not in self.online_users]
if candidates:
n = random.choice(candidates)
self.online_users[n['call']] = n
self.server.broadcast_to_channel(self.id, f"UA5|{self.id}|{n['call']}|{n['name']}|{n['loc']}|2|\r\n")
def process_msg(self, sender, name, text, target):
ts = str(int(time.time()))
frame = f"CH|{self.id}|{ts}|{sender}|{name}|0|{text}|{target}|\r\n"
if target == "0": self._add_hist(frame)
self.server.broadcast_to_channel(self.id, frame)
if target in self.online_users:
threading.Thread(target=self._schedule_reply, args=(target, sender), daemon=True).start()
def _schedule_reply(self, sim_sender, real_target):
if sim_sender not in self.online_users: return
u = self.online_users[sim_sender]
if u['role'] == "INACTIVE": return
time.sleep(random.uniform(2.0, 5.0))
if sim_sender in self.online_users:
text = MessageFactory.get_reply_msg(self.config["BANDS"], real_target, u['loc'])
ts = str(int(time.time()))
if self.server.is_real_user(real_target):
print(f"[REPLY Ch{self.id}] {sim_sender} -> {real_target}")
frame = f"CH|{self.id}|{ts}|{sim_sender}|{u['name']}|0|{text}|{real_target}|\r\n"
self.server.broadcast_to_channel(self.id, frame)
def _add_hist(self, frame):
self.history_chat.append(frame)
if len(self.history_chat) > 50: self.history_chat.pop(0)
def get_full_init_blob(self):
blob = ""
for u in self.online_users.values():
blob += f"UA0|{self.id}|{u['call']}|{u['name']}|{u['loc']}|0|\r\n"
for h in self.history_chat: blob += h
blob += f"UE|{self.id}|{len(self.online_users)}|\r\n"
return blob.encode('latin-1', errors='replace')
# ==========================================
# SERVER
# ==========================================
class KSTServerV31:
def __init__(self):
self.lock = threading.Lock()
self.running = True
self.clients = {}
self.channels = {}
for cid, cfg in CHANNELS_SETUP.items():
self.channels[cid] = ChannelInstance(cid, cfg, self)
def start(self):
threading.Thread(target=self._sim_loop, daemon=True).start()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
s.bind((HOST, PORT))
s.listen(5)
s.settimeout(1.0)
print(f"[*] ON4KST V31 (Stable Frequencies) running on {HOST}:{PORT}")
while self.running:
try:
sock, addr = s.accept()
print(f"[*] CONNECT: {addr}")
threading.Thread(target=self._handle_client, args=(sock,), daemon=True).start()
except socket.timeout: continue
except OSError: break
except KeyboardInterrupt:
print("\n[!] Stop.")
finally:
self.running = False
try: s.close()
except: pass
def _handle_client(self, sock):
client_obj = ConnectedClient(sock, None)
with self.lock:
self.clients[sock] = client_obj
buffer = ""
try:
while self.running:
try: data = sock.recv(2048)
except: break
if not data: break
buffer += data.decode('latin-1', errors='replace')
while '\n' in buffer:
line, buffer = buffer.split('\n', 1)
line = line.strip()
if not line: continue
parts = line.split('|')
cmd = parts[0]
if cmd == 'LOGIN' or cmd == 'LOGINC':
if len(parts) > 1:
client_obj.call = parts[1].strip().upper()
print(f"[LOGIN] {client_obj.call} (Ch 2)")
client_obj.send_safe(f"LOGSTAT|100|2|PySimV31|KEY|Conf|3|\r\n")
if cmd == 'LOGIN':
self._send_channel_init(client_obj, "2")
elif cmd == 'SDONE':
self._send_channel_init(client_obj, "2")
elif cmd.startswith('ACHAT'):
if len(parts) >= 2:
new_chan = parts[1]
if new_chan in self.channels:
client_obj.channels.add(new_chan)
print(f"[ACHAT] {client_obj.call} -> Ch {new_chan}")
self._send_channel_init(client_obj, new_chan)
elif cmd == 'MSG':
if len(parts) >= 4:
cid = parts[1]
target = parts[2]
text = parts[3]
if text.lower().startswith("/cq"):
spl = text.split(' ', 2)
if len(spl) >= 3:
target = spl[1]; text = spl[2]
if cid in self.channels:
self.channels[cid].process_msg(client_obj.call, "Me", text, target)
elif cmd == 'CK': pass
except Exception as e:
print(f"[!] Err: {e}")
finally:
with self.lock:
if sock in self.clients: del self.clients[sock]
client_obj.close()
def _send_channel_init(self, client_obj, cid):
if cid in self.channels:
full_blob = self.channels[cid].get_full_init_blob()
client_obj.send_safe(full_blob.decode('latin-1'))
def broadcast_to_channel(self, cid, frame):
now = time.time()
with self.lock:
targets = list(self.clients.values())
for c in targets:
if cid in c.channels:
if now - c.login_time > CLIENT_WARMUP_TIME:
c.send_safe(frame)
def get_random_subscriber(self, cid):
with self.lock:
subs = [c for c in self.clients.values() if cid in c.channels and not c.call.startswith("GUEST")]
return random.choice(subs) if subs else None
def is_real_user(self, call):
with self.lock:
for c in self.clients.values():
if c.call.upper() == call.upper() and not c.call.startswith("GUEST"):
return True
return False
def _sim_loop(self):
print("[*] Sim Loop running...")
last_ka = time.time()
while self.running:
now = time.time()
time.sleep(0.02)
for c in self.channels.values():
c.tick(now)
if now - last_ka > KEEP_ALIVE_INTERVAL:
last_ka = now
self.broadcast_global("CK|\r\n")
def broadcast_global(self, frame):
with self.lock:
targets = list(self.clients.values())
for c in targets:
c.send_safe(frame)
if __name__ == '__main__':
KSTServerV31().start()
@@ -9,8 +9,6 @@ import java.net.NoRouteToHostException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.collections.ObservableList;
import kst4contest.locatorUtils.Location;
@@ -19,7 +17,6 @@ import kst4contest.model.ChatMember;
public class AirScoutPeriodicalAPReflectionInquirerTask extends TimerTask {
private static final Logger LOGGER = Logger.getLogger(AirScoutPeriodicalAPReflectionInquirerTask.class.getName());
private ChatController client;
public AirScoutPeriodicalAPReflectionInquirerTask(ChatController client) {
@@ -58,7 +55,7 @@ public class AirScoutPeriodicalAPReflectionInquirerTask extends TimerTask {
ownCallSign = this.client.getChatPreferences().getStn_loginCallSign();
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] Error parsing callsign", e);
System.out.println("[ASPERIODICAL, Error]: " + e.getMessage());
}
String myCallAndMyLocString = ownCallSign + "," + this.client.getChatPreferences().getStn_loginLocatorMainCat(); //bugfix, Airscout do not process 9A1W-2 but 9A1W like formatted calls
@@ -108,13 +105,13 @@ public class AirScoutPeriodicalAPReflectionInquirerTask extends TimerTask {
dsocket.send(packet);
dsocket.close();
} catch (UnknownHostException e1) {
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] Unknown host", e1);
e1.printStackTrace();
} catch (NoRouteToHostException e) {
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] No route to host", e);
e.printStackTrace();
} catch (SocketException e) {
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] Socket error", e);
e.printStackTrace();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] IO error sending query", e);
e.printStackTrace();
}
// System.out.println("[ASUDPTask, info:] sent query " + queryStringToAirScout);
@@ -139,7 +136,7 @@ public class AirScoutPeriodicalAPReflectionInquirerTask extends TimerTask {
dsocket.send(packet);
dsocket.close();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "[ASPERIODICAL] IO error sending watchlist", e);
e.printStackTrace();
}
// System.out.println("[ASUDPTask, info:] set watchlist: " + asWatchListStringSuffix);
@@ -23,7 +23,6 @@ import kst4contest.locatorUtils.DirectionUtils;
import kst4contest.logic.PriorityCalculator;
import kst4contest.model.*;
import kst4contest.test.MockKstServer;
import kst4contest.utils.BoundedDequeObservableList;
import kst4contest.utils.PlayAudioUtils;
import kst4contest.view.Kst4ContestApplication;
@@ -811,24 +810,6 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
private final Map<String, ChatCategory> lastInboundCategoryByCallSignRaw =
new java.util.concurrent.ConcurrentHashMap<>();
/** Tracks the last time WE sent a message containing a QRG to a specific callsign (UPPERCASE).
* Compared against knownActiveBands.timestampEpoch to decide whose QRG to use in a SKED. */
private final Map<String, Long> lastSentQRGToCallsign =
new java.util.concurrent.ConcurrentHashMap<>();
/** Call this whenever we send a PM to {@code receiverCallsign} that contains our QRG. */
public void recordOutboundQRG(String receiverCallsign) {
if (receiverCallsign == null) return;
lastSentQRGToCallsign.put(receiverCallsign.trim().toUpperCase(), System.currentTimeMillis());
System.out.println("[ChatController] Recorded outbound QRG to: " + receiverCallsign);
}
/** Returns epoch-ms of when we last sent our QRG to this callsign, or 0 if never. */
public long getLastSentQRGTimestamp(String callsign) {
if (callsign == null) return 0L;
return lastSentQRGToCallsign.getOrDefault(callsign.trim().toUpperCase(), 0L);
}
private final ScoreService scoreService = new ScoreService(this, new PriorityCalculator(), 15);
private ScheduledExecutorService scoreScheduler;
private final StationMetricsService stationMetricsService = new StationMetricsService();
@@ -846,7 +827,8 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
});
// Push sked to Win-Test via UDP if enabled
if (chatPreferences.isLogsynch_wintestNetworkListenerEnabled()) {
if (chatPreferences.isLogsynch_wintestNetworkSkedPushEnabled()
&& chatPreferences.isLogsynch_wintestNetworkListenerEnabled()) {
pushSkedToWinTest(sked);
}
}
@@ -865,69 +847,16 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
WinTestSkedSender sender = new WinTestSkedSender(stationName, broadcastAddr, port, this);
// Frequency resolution:
// Compare WHO sent a QRG most recently in the PM conversation:
// - OM sent their QRG last → use OM's Last Known QRG (ChatMember.frequency)
// - WE sent our QRG last → use our own Win-Test QRG (MYQRG)
// Fallback chain if no timestamps exist: OM's Last Known QRG → hardcoded default
double freqKHz = -1.0;
final long SKED_FREQ_MAX_AGE_MS = 60 * 60 * 1000L; // 60 minutes
ChatMember targetMember = resolveSkedTargetMember(sked.getTargetCallsign());
// Collect timestamps: when did the OM last mention their QRG? When did WE last send ours?
long omLastQRGTimestamp = 0L;
double omLastQRGMhz = 0.0;
if (targetMember != null && sked.getBand() != null) {
ChatMember.ActiveFrequencyInfo fi = targetMember.getKnownActiveBands().get(sked.getBand());
if (fi != null && fi.frequency > 0
&& (System.currentTimeMillis() - fi.timestampEpoch) <= SKED_FREQ_MAX_AGE_MS) {
omLastQRGTimestamp = fi.timestampEpoch;
omLastQRGMhz = fi.frequency;
}
}
long ourLastQRGTimestamp = getLastSentQRGTimestamp(sked.getTargetCallsign());
// Decision: who was more recent?
if (omLastQRGTimestamp > 0 && omLastQRGTimestamp >= ourLastQRGTimestamp) {
// OM mentioned their QRG MORE RECENTLY (or at same time) → use their QRG
freqKHz = omLastQRGMhz * 1000.0;
System.out.println("[ChatController] SKED freq: OM sent last → "
+ omLastQRGMhz + " MHz → " + freqKHz + " kHz");
} else if (ourLastQRGTimestamp > 0) {
// WE sent our QRG more recently → use our Win-Test QRG
// Get current frequency from QRG property (set by Win-Test STATUS or user)
double freqKHz = 144300.0; // fallback default
try {
String qrgStr = chatPreferences.getMYQRGFirstCat().get();
if (qrgStr != null && !qrgStr.isBlank()) {
// QRG is in display format like "144.300.00" strip dots → "14430000" → / 100 → 144300.0 kHz
String cleaned = qrgStr.trim().replace(".", "");
double parsed = Double.parseDouble(cleaned) / 100.0;
if (parsed > 50000) {
freqKHz = parsed;
System.out.println("[ChatController] SKED freq: WE sent last → "
+ freqKHz + " kHz (raw: " + qrgStr + ")");
}
freqKHz = Double.parseDouble(cleaned) / 100.0;
}
} catch (NumberFormatException ignored) { }
}
// Fallback A: OM's Last Known QRG from KST field (if no PM QRG exchange found at all)
if (freqKHz < 0 && targetMember != null) {
try {
String memberQrg = targetMember.getFrequency().get();
if (memberQrg != null && !memberQrg.isBlank()) {
double mhz = Double.parseDouble(memberQrg.trim());
freqKHz = mhz * 1000.0;
System.out.println("[ChatController] SKED freq: fallback Last Known QRG → "
+ mhz + " MHz → " + freqKHz + " kHz");
}
} catch (NumberFormatException ignored) { }
}
// Fallback B: hardcoded default
if (freqKHz < 0) {
freqKHz = 144300.0;
}
// Build notes string with target locator/azimuth info like reference: [JO02OB - 279°]
String targetLocator = resolveSkedTargetLocator(sked.getTargetCallsign());
@@ -954,22 +883,6 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
}, "WinTestSkedPush").start();
}
private ChatMember resolveSkedTargetMember(String targetCallsignRaw) {
if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) {
return null;
}
String normalizedTargetCall = normalizeCallRaw(targetCallsignRaw);
synchronized (getLst_chatMemberList()) {
for (ChatMember member : getLst_chatMemberList()) {
if (member == null || member.getCallSignRaw() == null) continue;
if (normalizeCallRaw(member.getCallSignRaw()).equals(normalizedTargetCall)) {
return member;
}
}
}
return null;
}
private String resolveSkedTargetLocator(String targetCallsignRaw) {
if (targetCallsignRaw == null || targetCallsignRaw.isBlank()) {
return null;
@@ -1094,8 +1007,7 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
// ******All abstract types below here are used by the messageprocessor!
// ***************
private static final int MAX_CHAT_MESSAGES = 10000;
private final BoundedDequeObservableList<ChatMessage> lst_globalChatMessageList = new BoundedDequeObservableList<>(MAX_CHAT_MESSAGES); //All chatmessages will be put in there, later create filtered message lists
private ObservableList<ChatMessage> lst_globalChatMessageList = FXCollections.observableArrayList(); //All chatmessages will be put in there, later create filtered message lists
// private ObservableList<ChatMessage> lst_toAllMessageList = FXCollections.observableArrayList(); // directed to all
// (beacon)
private FilteredList<ChatMessage> lst_toAllMessageList = new FilteredList<>(lst_globalChatMessageList); // directed to all
@@ -1240,14 +1152,13 @@ public class ChatController implements ThreadStatusCallback, PstRotatorEventList
this.lst_selectedCallSignInfofilteredMessageList = lst_selectedCallSignInfofilteredMessageList;
}
public void addChatMessage(ChatMessage message) {
lst_globalChatMessageList.addFirst(message);
}
public ObservableList<ChatMessage> getLst_globalChatMessageList() {
return lst_globalChatMessageList;
}
public void setLst_globalChatMessageList(ObservableList<ChatMessage> lst_globalChatMessageList) {
this.lst_globalChatMessageList = lst_globalChatMessageList;
}
public String getHostname() {
return hostname;
@@ -10,12 +10,9 @@ import java.net.Socket;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DXClusterThreadPooledServer implements Runnable{
private static final Logger LOGGER = Logger.getLogger(DXClusterThreadPooledServer.class.getName());
private List<Socket> clientSockets = Collections.synchronizedList(new ArrayList<>()); //list of all connected clients
private ThreadStatusCallback callBackToController;
@@ -114,7 +111,8 @@ public class DXClusterThreadPooledServer implements Runnable{
System.out.println("-------------> ORIGINALEE VAL: " + aChatMember.getFrequency().getValue());
System.out.println("-------------> NORMALIZED VAL: " + Utils4KST.normalizeFrequencyString(aChatMember.getFrequency().getValue(), chatController.getChatPreferences().getNotify_optionalFrequencyPrefix()) + " ");
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "DXCThPooledServer: Error accessing value in chatmember object", e);
System.out.println("DXCThPooledServer: Error accessing value in chatmember object: " + e.getMessage());
// e.printStackTrace();
}
for (Socket socket : clientSockets) {
@@ -148,7 +146,8 @@ public class DXClusterThreadPooledServer implements Runnable{
callBackToController.onThreadStatus(ThreadNickName,threadStateMessage);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "[DXClusterSrvr] broadcasting DXC-message to clients went wrong", e);
e.printStackTrace();
System.out.println("[DXClusterSrvr, Error:] broadcasting DXC-message to clients went wrong!");
return false;
}
}
@@ -160,7 +159,6 @@ public class DXClusterThreadPooledServer implements Runnable{
class DXClusterServerWorkerRunnable implements Runnable{
private static final Logger LOGGER = Logger.getLogger(DXClusterServerWorkerRunnable.class.getName());
protected Socket clientSocket = null;
protected String serverText = null;
private ChatController client = null;
@@ -199,13 +197,14 @@ class DXClusterServerWorkerRunnable implements Runnable{
output.write(("\r\n").getBytes());
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "[DXClusterSrvr] keep-alive broadcast to client failed", e);
e.printStackTrace();
System.out.println("[DXClusterSrvr, Error:] broadcasting DXC-message to clients went wrong!");
dXCkeepAliveTimer.purge();
try {
socket.close();
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "[DXClusterSrvr] error closing client socket", ex);
ex.printStackTrace();
}
finally {
this.cancel();
@@ -225,7 +224,7 @@ class DXClusterServerWorkerRunnable implements Runnable{
System.out.println("[DXClusterThreadPooledServer, Info:] New cluster client connected! "); //TODO: maybe integrate non blocking reader for client identification
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "[DXClusterSrvr] error in worker runnable", e);
e.printStackTrace();
} finally {
synchronized(dxClusterClientSocketsConnectedList) {
dxClusterClientSocketsConnectedList.remove(clientSocket); // Entferne den Client nach Verarbeitung
@@ -2,8 +2,6 @@ package kst4contest.controller;
import java.io.*;
import java.net.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import kst4contest.model.ChatMessage;
@@ -15,7 +13,6 @@ import kst4contest.model.ChatMessage;
* No need for it as it´s not longer a console application
*/
public class InputReaderThread extends Thread {
private static final Logger LOGGER = Logger.getLogger(InputReaderThread.class.getName());
private PrintWriter writer;
private Socket socket;
private ChatController client;
@@ -42,7 +39,8 @@ public class InputReaderThread extends Thread {
try {
sendThisMessage23001 = reader.readLine();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Error reading from stdin", e);
// TODO Auto-generated catch block
e.printStackTrace();
}
ownMSG.setMessageText("MSG|" + this.client.getChatCategoryMain().getCategoryNumber() + "|0|" + sendThisMessage23001 + "|0|");
@@ -55,8 +53,8 @@ public class InputReaderThread extends Thread {
try {
this.sleep(500);
} catch (InterruptedException e) {
LOGGER.log(Level.SEVERE, "InputReaderThread interrupted", e);
Thread.currentThread().interrupt();
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@@ -772,7 +772,7 @@ public class MessageBusManagementThread extends Thread {
dummy.setCallSign("ALL");
newMessageArrived.setReceiver(dummy);
this.client.addChatMessage(newMessageArrived); // sdtout to all message-List
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); // sdtout to all message-List
} else {
//message is directed to another chatmember, process as such!
@@ -817,7 +817,7 @@ public class MessageBusManagementThread extends Thread {
if (newMessageArrived.getReceiver().getCallSign()
.equals(this.client.getChatPreferences().getStn_loginCallSign())) {
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived);
if (this.client.getChatPreferences().isNotify_playSimpleSounds()) {
this.client.getPlayAudioUtils().playNoiseLauncher('P');
@@ -960,14 +960,7 @@ public class MessageBusManagementThread extends Thread {
String originalMessage = newMessageArrived.getMessageText();
newMessageArrived
.setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage);
this.client.addChatMessage(newMessageArrived);
// If our message contained a frequency (e.g. "QRG is: 144.375"), record that
// WE sent our QRG to this OM used by SKED frequency resolution.
if (originalMessage != null && newMessageArrived.getReceiver() != null
&& originalMessage.matches(".*\\b\\d{3,5}[.,]\\d{1,3}.*")) {
this.client.recordOutboundQRG(newMessageArrived.getReceiver().getCallSign());
}
this.client.getLst_globalChatMessageList().add(0,newMessageArrived);
// if you sent the message to another station, it will be sorted in to
// the "to me message list" with modified messagetext, added rxers callsign
@@ -1031,7 +1024,7 @@ public class MessageBusManagementThread extends Thread {
newMessageArrived.getSender().setInAngleAndRange(false);
}
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived);
// System.out.println("MSGBS bgfx: tx call = " + newMessageArrived.getSender().getCallSign() + " / rx call = " + newMessageArrived.getReceiver().getCallSign());
}
} catch (NullPointerException referenceDeletedByUserLeftChatDuringMessageprocessing) {
@@ -1371,7 +1364,7 @@ public class MessageBusManagementThread extends Thread {
dummy.setCallSign("ALL");
newMessageArrived.setReceiver(dummy);
this.client.addChatMessage(newMessageArrived); // sdtout to all message-List
this.client.getLst_globalChatMessageList().add(0, newMessageArrived); // sdtout to all message-List
} else {
//message is directed to another chatmember, process as such!
@@ -1415,7 +1408,7 @@ public class MessageBusManagementThread extends Thread {
if (newMessageArrived.getReceiver().getCallSign()
.equals(this.client.getChatPreferences().getStn_loginCallSign())) {
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived);
System.out.println("Historic message directed to me: " + newMessageArrived.getReceiver().getCallSign() + ".");
@@ -1428,7 +1421,7 @@ public class MessageBusManagementThread extends Thread {
String originalMessage = newMessageArrived.getMessageText();
newMessageArrived
.setMessageText("(>" + newMessageArrived.getReceiver().getCallSign() + ")" + originalMessage);
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0,newMessageArrived);
// if you sent the message to another station, it will be sorted in to
// the "to me message list" with modified messagetext, added rxers callsign
@@ -1448,7 +1441,7 @@ public class MessageBusManagementThread extends Thread {
newMessageArrived.getSender().setInAngleAndRange(false);
}
this.client.addChatMessage(newMessageArrived);
this.client.getLst_globalChatMessageList().add(0, newMessageArrived);
// System.out.println("MSGBS bgfx: tx call = " + newMessageArrived.getSender().getCallSign() + " / rx call = " + newMessageArrived.getReceiver().getCallSign());
}
} catch (NullPointerException referenceDeletedByUserLeftChatDuringMessageprocessing) {
@@ -1521,7 +1514,7 @@ public class MessageBusManagementThread extends Thread {
for (int i = 0; i < 10; i++) {
client.addChatMessage(pwErrorMsg);
client.getLst_globalChatMessageList().add(pwErrorMsg);
// client.getLst_toMeMessageList().add(pwErrorMsg);
// client.getLst_toAllMessageList().add(pwErrorMsg);
}
@@ -3,8 +3,6 @@ package kst4contest.controller;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;
import kst4contest.model.ChatMessage;
@@ -16,7 +14,6 @@ import kst4contest.model.ChatMessage;
* @author www.codejava.net
*/
public class ReadThread extends Thread {
private static final Logger LOGGER = Logger.getLogger(ReadThread.class.getName());
private BufferedReader reader;
private Socket socket;
private ChatController client;
@@ -46,7 +43,8 @@ public class ReadThread extends Thread {
reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "Error getting input stream", ex);
System.out.println("Error getting input stream: " + ex.getMessage());
ex.printStackTrace();
}
}
@@ -84,14 +82,15 @@ public class ReadThread extends Thread {
}
catch (Exception sexc) {
LOGGER.log(Level.SEVERE, "[ReadThread] Socket closed unexpectedly", sexc);
System.out.println("[ReadThread, CRITICAL: ] Socket geschlossen: " + sexc.getMessage());
try {
this.client.getSocket().close();
this.interrupt();
break;
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "[ReadThread] Error closing socket", e);
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@@ -1,6 +1,5 @@
package kst4contest.controller;
import javafx.application.Platform;
import kst4contest.ApplicationConstants;
import kst4contest.model.ChatMember;
import kst4contest.model.ThreadStateMessage;
@@ -76,10 +75,9 @@ public class ReadUDPByWintestThread extends Thread {
socket = new DatagramSocket(null); //first init with null, then make ready for reuse
socket.setReuseAddress(true);
// socket = new DatagramSocket(PORT);
int boundPort = client.getChatPreferences().getLogsynch_wintestNetworkPort();
socket.bind(new InetSocketAddress(boundPort));
socket.bind(new InetSocketAddress(client.getChatPreferences().getLogsynch_wintestNetworkPort()));
socket.setSoTimeout(3000);
System.out.println("[WinTest UDP listener] started at port: " + boundPort);
System.out.println("[WinTest UDP listener] started at port: " + PORT);
} catch (SocketException e) {
e.printStackTrace();
return;
@@ -226,43 +224,9 @@ public class ReadUDPByWintestThread extends Thread {
} else {
formattedQRG = String.format(Locale.US, "%.1f", freqFloat); // fallback
}
// Parse pass frequency from parts[11] if available (WT STATUS format)
String formattedPassQRG = null;
if (parts.size() > 11) {
try {
String passFreqRaw = parts.get(11);
double passFreqFloat = Integer.parseInt(passFreqRaw) / 10.0;
if (passFreqFloat > 100) { // Must be a valid radio frequency (> 100 kHz), protects against parsing boolean flag tokens
long passFreqHzTimes100 = Math.round(passFreqFloat * 100.0);
String passHzStr = String.valueOf(passFreqHzTimes100);
if (passHzStr.length() == 8) {
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 3), passHzStr.substring(3, 6), passHzStr.substring(6, 8));
} else if (passHzStr.length() == 9) {
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 4), passHzStr.substring(4, 7), passHzStr.substring(7, 9));
} else if (passHzStr.length() == 7) {
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 2), passHzStr.substring(2, 5), passHzStr.substring(5, 7));
} else if (passHzStr.length() == 6) {
formattedPassQRG = String.format("%s.%s.%s", passHzStr.substring(0, 1), passHzStr.substring(1, 4), passHzStr.substring(4, 6));
} else {
formattedPassQRG = String.format(Locale.US, "%.1f", passFreqFloat);
}
}
} catch (Exception ignored) {
// parts[11] not a valid frequency, leave formattedPassQRG as null
}
}
this.client.getChatPreferences().getMYQRGFirstCat().set(formattedQRG);
if (this.client.getChatPreferences().isLogsynch_wintestQrgSyncEnabled()) {
final String qrgToSet = (this.client.getChatPreferences().isLogsynch_wintestUsePassQrg() && formattedPassQRG != null)
? formattedPassQRG
: formattedQRG;
// JavaFX StringProperty must be updated on the FX Application Thread
Platform.runLater(() -> this.client.getChatPreferences().getMYQRGFirstCat().set(qrgToSet));
}
System.out.println("[WinTest STATUS] stn=" + stn + ", mode=" + mode + ", qrg=" + formattedQRG
+ (formattedPassQRG != null ? ", passQrg=" + formattedPassQRG : "")
+ ", syncActive=" + this.client.getChatPreferences().isLogsynch_wintestQrgSyncEnabled());
System.out.println("[WinTest STATUS] stn=" + stn + ", mode=" + mode + ", qrg=" + formattedQRG);
} catch (Exception e) {
System.out.println("[WinTest] STATUS parsing error: " + e.getMessage());
}
@@ -204,8 +204,6 @@ public class ChatPreferences {
String logsynch_wintestNetworkBroadcastAddress = "255.255.255.255"; // UDP broadcast address for sending to Win-Test
boolean logsynch_wintestNetworkSkedPushEnabled = false; // push SKEDs to Win-Test via UDP
String logsynch_wintestSkedMode = "SSB"; // CW, SSB or AUTO
boolean logsynch_wintestQrgSyncEnabled = true; // sync QRG from Win-Test STATUS packet
boolean logsynch_wintestUsePassQrg = false; // use pass frequency instead of main QRG from STATUS packet
@@ -483,22 +481,6 @@ public class ChatPreferences {
this.logsynch_wintestSkedMode = logsynch_wintestSkedMode;
}
public boolean isLogsynch_wintestQrgSyncEnabled() {
return logsynch_wintestQrgSyncEnabled;
}
public void setLogsynch_wintestQrgSyncEnabled(boolean logsynch_wintestQrgSyncEnabled) {
this.logsynch_wintestQrgSyncEnabled = logsynch_wintestQrgSyncEnabled;
}
public boolean isLogsynch_wintestUsePassQrg() {
return logsynch_wintestUsePassQrg;
}
public void setLogsynch_wintestUsePassQrg(boolean logsynch_wintestUsePassQrg) {
this.logsynch_wintestUsePassQrg = logsynch_wintestUsePassQrg;
}
public String getStn_loginLocatorSecondCat() {
return stn_loginLocatorSecondCat;
}
@@ -1356,14 +1338,6 @@ public class ChatPreferences {
logsynch_wintestSkedMode.setTextContent(this.logsynch_wintestSkedMode);
logsynch.appendChild(logsynch_wintestSkedMode);
Element logsynch_wintestQrgSyncEnabled = doc.createElement("logsynch_wintestQrgSyncEnabled");
logsynch_wintestQrgSyncEnabled.setTextContent(this.logsynch_wintestQrgSyncEnabled + "");
logsynch.appendChild(logsynch_wintestQrgSyncEnabled);
Element logsynch_wintestUsePassQrg = doc.createElement("logsynch_wintestUsePassQrg");
logsynch_wintestUsePassQrg.setTextContent(this.logsynch_wintestUsePassQrg + "");
logsynch.appendChild(logsynch_wintestUsePassQrg);
/**
* trxSynchUCX
@@ -1938,16 +1912,6 @@ public class ChatPreferences {
logsynch_wintestSkedMode,
"logsynch_wintestSkedMode");
logsynch_wintestQrgSyncEnabled = getBoolean(
logsynchEl,
logsynch_wintestQrgSyncEnabled,
"logsynch_wintestQrgSyncEnabled");
logsynch_wintestUsePassQrg = getBoolean(
logsynchEl,
logsynch_wintestUsePassQrg,
"logsynch_wintestUsePassQrg");
System.out.println(
"[ChatPreferences, info]: file based worked-call interpreter: " + logsynch_fileBasedWkdCallInterpreterEnabled);
System.out.println(
@@ -7,16 +7,12 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class has utility methods to handle application files inside the home directory.
*/
public class ApplicationFileUtils {
private static final Logger LOGGER = Logger.getLogger(ApplicationFileUtils.class.getName());
/**
* Gets the path of a file inside the home directory of the user.
* @param applicationName Name off the application which is used for the hidden directory
@@ -65,7 +61,8 @@ public class ApplicationFileUtils {
resourceStream.transferTo(fileOutputStream);
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "Exception when copying Application file: " + ex.getMessage(), ex);
System.err.println("Exception when copying Application file: " + ex.getMessage());
ex.printStackTrace(System.err);
}
}
@@ -1,168 +0,0 @@
package kst4contest.utils;
import javafx.collections.ObservableListBase;
import java.util.Arrays;
/**
* A bounded ObservableList backed by a circular buffer (ring buffer).
* <p>
* Provides O(1) {@link #addFirst} and {@link #addLast} as well as O(1)
* random access via {@link #get}. When the list reaches {@code maxCapacity},
* adding a new element at the front automatically evicts the oldest element
* at the back and vice versa.
* <p>
* This is a drop-in replacement for {@code FXCollections.observableArrayList()}
* wherever elements are prepended frequently, e.g. chat message lists.
*/
public class BoundedDequeObservableList<E> extends ObservableListBase<E> {
private final int maxCapacity;
private final Object[] elements;
private int head = 0;
private int size = 0;
public BoundedDequeObservableList(int maxCapacity) {
if (maxCapacity <= 0) throw new IllegalArgumentException("maxCapacity must be > 0");
this.maxCapacity = maxCapacity;
this.elements = new Object[maxCapacity];
}
// read access
@Override
public int size() {
return size;
}
@Override
@SuppressWarnings("unchecked")
public E get(int index) {
checkIndex(index);
return (E) elements[physicalIndex(index)];
}
// O(1) deque operations
/**
* Inserts {@code element} at index 0 (newest-first order).
* If the list is already at capacity the oldest element (last index) is
* removed first both changes are reported as a single compound change.
*/
public void addFirst(E element) {
beginChange();
if (size == maxCapacity) {
// evict last element
int lastPhysical = physicalIndex(size - 1);
@SuppressWarnings("unchecked")
E evicted = (E) elements[lastPhysical];
elements[lastPhysical] = null;
size--;
nextRemove(size, evicted); // index after decrement == old last index
}
head = (head - 1 + maxCapacity) % maxCapacity;
elements[head] = element;
size++;
nextAdd(0, 1);
endChange();
}
/**
* Appends {@code element} at the last index (oldest-first order).
* If the list is already at capacity the newest element (index 0) is
* removed first.
*/
public void addLast(E element) {
beginChange();
if (size == maxCapacity) {
// evict first element
@SuppressWarnings("unchecked")
E evicted = (E) elements[head];
elements[head] = null;
head = (head + 1) % maxCapacity;
size--;
nextRemove(0, evicted);
}
elements[physicalIndex(size)] = element;
size++;
nextAdd(size - 1, size);
endChange();
}
// standard List mutation (O(n) use addFirst/addLast for hot path)
@Override
public void add(int index, E element) {
if (index == 0) {
addFirst(element);
return;
}
if (index == size) {
addLast(element);
return;
}
checkIndexForAdd(index);
beginChange();
if (size == maxCapacity) {
int lastPhysical = physicalIndex(size - 1);
@SuppressWarnings("unchecked")
E evicted = (E) elements[lastPhysical];
elements[lastPhysical] = null;
size--;
nextRemove(size, evicted);
}
// shift elements [index .. size-1] one position towards the end
for (int i = size; i > index; i--) {
elements[physicalIndex(i)] = elements[physicalIndex(i - 1)];
}
elements[physicalIndex(index)] = element;
size++;
nextAdd(index, index + 1);
endChange();
}
@Override
public E remove(int index) {
checkIndex(index);
beginChange();
@SuppressWarnings("unchecked")
E removed = (E) elements[physicalIndex(index)];
// shift elements [index+1 .. size-1] one position towards the front
for (int i = index; i < size - 1; i++) {
elements[physicalIndex(i)] = elements[physicalIndex(i + 1)];
}
elements[physicalIndex(size - 1)] = null;
size--;
nextRemove(index, removed);
endChange();
return removed;
}
@Override
public E set(int index, E element) {
checkIndex(index);
beginChange();
@SuppressWarnings("unchecked")
E old = (E) elements[physicalIndex(index)];
elements[physicalIndex(index)] = element;
nextSet(index, old);
endChange();
return old;
}
// helpers
private int physicalIndex(int virtualIndex) {
return (head + virtualIndex) % maxCapacity;
}
private void checkIndex(int index) {
if (index < 0 || index >= size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
private void checkIndexForAdd(int index) {
if (index < 0 || index > size)
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
}
}
@@ -3,12 +3,7 @@ package kst4contest.view;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.*;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -3587,57 +3582,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
Menu fileMenu = new Menu("File");
// build "Connect to <configured chat>" label from saved preferences
ChatCategory mainCat = chatcontroller.getChatPreferences().getLoginChatCategoryMain();
String connectLabel = "Connect to " + mainCat.getChatCategoryName(mainCat.getCategoryNumber());
if (chatcontroller.getChatPreferences().isLoginToSecondChatEnabled()) {
ChatCategory secCat = chatcontroller.getChatPreferences().getLoginChatCategorySecond();
if (secCat != null) {
connectLabel += " & " + secCat.getChatCategoryName(secCat.getCategoryNumber());
}
}
menuItemFileConnect = new MenuItem(connectLabel);
menuItemFileConnect.setDisable(false);
if (chatcontroller.isConnectedAndLoggedIn() || chatcontroller.isConnectedAndNOTLoggedIn()) {
menuItemFileConnect.setDisable(true);
}
menuItemFileConnect.setOnAction(event -> {
System.out.println("[Info] File menu: Connect clicked, using saved preferences");
String call = chatcontroller.getChatPreferences().getStn_loginCallSign();
String pass = chatcontroller.getChatPreferences().getStn_loginPassword();
if (call == null || call.isBlank() || pass == null || pass.isBlank()) {
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("Cannot connect");
alert.setHeaderText("Login credentials missing");
alert.setContentText("Please configure your callsign and password in Settings first.");
alert.show();
return;
}
try {
chatcontroller.execute();
menuItemFileConnect.setDisable(true);
menuItemFileDisconnect.setDisable(false);
menuItemOptionsAwayBack.setDisable(false);
menuItemOptionsSetFrequencyAsName.setDisable(false);
chatcontroller.setConnectedAndLoggedIn(true);
chatcontroller.setDisconnected(false);
} catch (InterruptedException | IOException e) {
e.printStackTrace();
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("Connection failed");
alert.setContentText("Could not connect: " + e.getMessage());
alert.show();
}
});
// create menuitems
menuItemFileDisconnect = new MenuItem("Disconnect");
menuItemFileDisconnect.setDisable(true);
@@ -3650,7 +3595,6 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
public void handle(ActionEvent event) {
chatcontroller.disconnect(ApplicationConstants.DISCSTRING_DISCONNECTONLY);
menuItemFileDisconnect.setDisable(true);
menuItemFileConnect.setDisable(false);
}
});
@@ -3663,7 +3607,6 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
});
// add menu items to menu
fileMenu.getItems().add(menuItemFileConnect);
fileMenu.getItems().add(menuItemFileDisconnect);
fileMenu.getItems().add(m10);
@@ -4067,7 +4010,6 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
Scene clusterAndQSOMonScene;
Scene settingsScene;
MenuItem menuItemFileConnect;
MenuItem menuItemFileDisconnect;
MenuItem menuItemOptionsAwayBack;
@@ -4228,15 +4170,10 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
timer_updatePrivatemessageTable.purge();
timer_updatePrivatemessageTable.cancel();
try {
chatcontroller.disconnect("CLOSEALL");
} catch (Exception e) {
System.out.println("[Main.java, Warning:] Exception during disconnect: " + e.getMessage());
}
// Platform.exit();
System.exit(0);
}
private Queue<Media> musicList = new LinkedList<Media>();
@@ -5445,7 +5382,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
FlowPane chatMemberTableFilterQRBHBox = new FlowPane();
chatMemberTableFilterQRBHBox.setAlignment(Pos.CENTER_LEFT);
chatMemberTableFilterQRBHBox.setHgap(2);
chatMemberTableFilterQRBHBox.setPrefWidth(225);
chatMemberTableFilterQRBHBox.setPrefWidth(210);
TextField chatMemberTableFilterMaxQrbTF = new TextField(chatcontroller.getChatPreferences().getStn_maxQRBDefault() + "");
chatMemberTableFilterMaxQrbTF.setFocusTraversable(false);
@@ -5494,7 +5431,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
// HBox chatMemberTableFilterQTFHBox = new HBox();
FlowPane chatMemberTableFilterQTFHBox = new FlowPane();
chatMemberTableFilterQTFHBox.setAlignment(Pos.CENTER_LEFT);
chatMemberTableFilterQTFHBox.setPrefWidth(525);
chatMemberTableFilterQTFHBox.setPrefWidth(490);
chatMemberTableFilterQTFHBox.setHgap(2);
CheckBox chatMemberTableFilterQtfEnableChkbx = new CheckBox("Show only QTF:");
@@ -6275,7 +6212,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
*
****************************************************************************/
settingsStage = new Stage();
settingsStage.setTitle("Change Client Settings");
settingsStage.setTitle("Change Client seetings");
BorderPane optionsPanel = new BorderPane();
@@ -6336,14 +6273,11 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
}
});
boolean isSecondChatEnabled = this.chatcontroller.getChatPreferences().isLoginToSecondChatEnabled();
Label lblNameSecondCat = new Label("Name in Chat 2:");
lblNameSecondCat.setVisible(isSecondChatEnabled);
lblNameSecondCat.setDisable(!isSecondChatEnabled);
lblNameSecondCat.setVisible(false);
TextField txtFldNameInChatSecondCat = new TextField(this.chatcontroller.getChatPreferences().getStn_loginNameSecondCat());
txtFldNameInChatSecondCat.setFocusTraversable(false);
txtFldNameInChatSecondCat.setVisible(isSecondChatEnabled);
txtFldNameInChatSecondCat.setDisable(!isSecondChatEnabled);
txtFldNameInChatSecondCat.setVisible(false);
txtFldNameInChatSecondCat.textProperty().addListener(new ChangeListener<String>() {
@@ -6463,12 +6397,11 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
CheckBox station_chkBxEnableSecondChat = new CheckBox("2nd Chat: ");
boolean isSecondChatEnabledForCheckbox = chatcontroller.getChatPreferences().isLoginToSecondChatEnabled();
station_chkBxEnableSecondChat.setSelected(isSecondChatEnabledForCheckbox);
station_chkBxEnableSecondChat.setSelected(chatcontroller.getChatPreferences().isLoginToSecondChatEnabled());
stn_choiceBxChatChategorySecond.setDisable(!isSecondChatEnabledForCheckbox);
stn_choiceBxChatChategorySecond.setDisable(true);
station_chkBxEnableSecondChat.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
@@ -6498,7 +6431,12 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
}
});
if (chatcontroller.getChatPreferences().isLoginToSecondChatEnabled()) {
stn_choiceBxChatChategorySecond.setVisible(chatcontroller.getChatPreferences().isLoginToSecondChatEnabled());
stn_choiceBxChatChategorySecond.setDisable(!chatcontroller.getChatPreferences().isLoginToSecondChatEnabled());
txtFldNameInChatSecondCat.setVisible(chatcontroller.getChatPreferences().isLoginToSecondChatEnabled());
}
TextField txtFldstn_antennaBeamWidthDeg = new TextField(this.chatcontroller.getChatPreferences().getStn_antennaBeamWidthDeg() + "");
txtFldstn_antennaBeamWidthDeg.setFocusTraversable(false);
@@ -6729,6 +6667,7 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
grdPnlStation_bands.add(settings_chkbx_QRV3400, 1, 2);
grdPnlStation_bands.add(settings_chkbx_QRV5600, 2, 2);
grdPnlStation_bands.add(settings_chkbx_QRV10G, 0, 3);
grdPnlStation_bands.setMaxWidth(555.0);
grdPnlStation_bands.setStyle(" -fx-border-color: lightgray;\n" +
" -fx-vgap: 5;\n" +
@@ -6943,32 +6882,15 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
grdPnlLog.add(lblUDPByWintest, 0, 8);
grdPnlLog.add(txtFldUDPPortforWintest, 1, 8);
// --- QRG sync from Win-Test STATUS ---
Label lblWtQrgSync = new Label("Win-Test STATUS QRG Sync (updates own QRG from Win-Test transceiver frequency)");
CheckBox chkBxWtQrgSync = new CheckBox();
chkBxWtQrgSync.setSelected(
this.chatcontroller.getChatPreferences().isLogsynch_wintestQrgSyncEnabled()
// --- Win-Test SKED push settings ---
Label lblEnableSkedPush = new Label("Push SKEDs to Win-Test via UDP (ADDSKED)");
CheckBox chkBxEnableSkedPush = new CheckBox();
chkBxEnableSkedPush.setSelected(
this.chatcontroller.getChatPreferences().isLogsynch_wintestNetworkSkedPushEnabled()
);
chkBxWtQrgSync.selectedProperty().addListener((obs, oldVal, newVal) -> {
chatcontroller.getChatPreferences().setLogsynch_wintestQrgSyncEnabled(newVal);
System.out.println("[Main.java, Info]: Win-Test QRG sync enabled: " + newVal);
boolean anyActive = chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled() || newVal;
if (!anyActive) {
txt_ownqrgMainCategory.textProperty().unbind();
txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by hand (watch prefs!)"));
} else {
txt_ownqrgMainCategory.textProperty().bind(chatcontroller.getChatPreferences().getMYQRGFirstCat());
txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by the log program (watch prefs!)"));
}
});
Label lblWtUsePassQrg = new Label("Use pass frequency from Win-Test STATUS (instead of own QRG)");
CheckBox chkBxWtUsePassQrg = new CheckBox();
chkBxWtUsePassQrg.setSelected(
this.chatcontroller.getChatPreferences().isLogsynch_wintestUsePassQrg()
);
chkBxWtUsePassQrg.selectedProperty().addListener((obs, oldVal, newVal) -> {
chatcontroller.getChatPreferences().setLogsynch_wintestUsePassQrg(newVal);
System.out.println("[Main.java, Info]: Win-Test use pass QRG: " + newVal);
chkBxEnableSkedPush.selectedProperty().addListener((obs, oldVal, newVal) -> {
chatcontroller.getChatPreferences().setLogsynch_wintestNetworkSkedPushEnabled(newVal);
System.out.println("[Main.java, Info]: Win-Test SKED push enabled: " + newVal);
});
Label lblWtStationName = new Label("KST station name in Win-Test network (src of SKED packets)");
@@ -7013,8 +6935,13 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
}
});
grdPnlLog.add(lblWtStationName, 0, 9);
grdPnlLog.add(txtFldWtStationName, 1, 9);
grdPnlLog.add(lblEnableSkedPush, 0, 9);
grdPnlLog.add(chkBxEnableSkedPush, 1, 9);
grdPnlLog.add(lblWtStationName, 0, 11);
grdPnlLog.add(txtFldWtStationName, 1, 11);
grdPnlLog.add(lblWtStationFilter, 0, 12);
grdPnlLog.add(txtFldWtStationFilter, 1, 12);
// Auto-detect subnet broadcast if preference is still the default
String currentBroadcast = this.chatcontroller.getChatPreferences().getLogsynch_wintestNetworkBroadcastAddress();
@@ -7032,8 +6959,8 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
// Re-read (may have been auto-detected)
txtFldWtBroadcastAddr.setText(this.chatcontroller.getChatPreferences().getLogsynch_wintestNetworkBroadcastAddress());
grdPnlLog.add(lblWtBroadcastAddr, 0, 10);
grdPnlLog.add(txtFldWtBroadcastAddr, 1, 10);
grdPnlLog.add(lblWtBroadcastAddr, 0, 13);
grdPnlLog.add(txtFldWtBroadcastAddr, 1, 13);
VBox vbxLog = new VBox();
vbxLog.setPadding(new Insets(10, 10, 10, 10));
@@ -7068,45 +6995,51 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
chkBxEnableTRXMsgbyUCX.selectedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
chatcontroller.getChatPreferences().setTrxSynch_ucxLogUDPListenerEnabled(newValue);
boolean anyActive = newValue || chatcontroller.getChatPreferences().isLogsynch_wintestQrgSyncEnabled();
if (!anyActive) {
// chk2.setSelected(!newValue);
if (!newValue) {
chatcontroller.getChatPreferences()
.setTrxSynch_ucxLogUDPListenerEnabled(chkBxEnableTRXMsgbyUCX.isSelected());
txt_ownqrgMainCategory.textProperty().unbind();
txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by hand (watch prefs!)"));
System.out.println("[Main.java, Info]: MYQRG will be changed only by User input");
System.out.println("[Main.java, Info]: setted the trx-frequency updated by ucxlog to: "
+ chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled());
} else {
chatcontroller.getChatPreferences()
.setTrxSynch_ucxLogUDPListenerEnabled(chkBxEnableTRXMsgbyUCX.isSelected());
txt_ownqrgMainCategory.textProperty().bind(chatcontroller.getChatPreferences().getMYQRGFirstCat());
txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by the log program (watch prefs!)"));
System.out.println("[Main.java, Info]: setted the trx-frequency updated by ucxlog to: "
+ chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled());
}
}
});
// Unconditionally add listener to manually sync the textfield input to the button
// (this listener also fires correctly when the value is updated by the binding)
// Thats the default behaviour of the myqrg textfield
if (this.chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled()) {
txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by the log program (watch prefs!)"));
txt_ownqrgMainCategory.textProperty().bind(this.chatcontroller.getChatPreferences().getMYQRGFirstCat());// TODO: Bind darf nur
// gemacht werden, wenn
// ucxlog-Frequenznachrichten
// ausgewerttet werden!
// System.out.println("[Main.java, Info]: MYQRG will be changed only by UCXListener");
} else {
txt_ownqrgMainCategory.setTooltip(new Tooltip("enter your cq qrg here"));
// System.out.println("[Main.java, Info]: MYQRG will be changed only by User input");
txt_ownqrgMainCategory.textProperty().addListener((observable, oldValue, newValue) -> {
System.out.println(
"[Main.java, Info]: MYQRG Text changed from " + oldValue + " to " + newValue + " by hand");
MYQRGButton.textProperty().set(newValue);
});
// That's the default behaviour of the myqrg textfield
if (this.chatcontroller.getChatPreferences().isTrxSynch_ucxLogUDPListenerEnabled() || this.chatcontroller.getChatPreferences().isLogsynch_wintestQrgSyncEnabled()) {
txt_ownqrgMainCategory.setTooltip(new Tooltip("Your cq qrg will be updated by the log program (watch prefs!)"));
txt_ownqrgMainCategory.textProperty().bind(this.chatcontroller.getChatPreferences().getMYQRGFirstCat());
} else {
txt_ownqrgMainCategory.setTooltip(new Tooltip("enter your cq qrg here"));
}
grdPnltrx.add(generateLabeledSeparator(100, "Receive UCXLog TRX info"), 0, 0, 2, 1);
grdPnltrx.add(lblEnableTRXMsgbyUCX, 0, 1);
grdPnltrx.add(chkBxEnableTRXMsgbyUCX, 1, 1);
grdPnltrx.add(generateLabeledSeparator(100, "Win-Test TRX sync"), 0, 2, 2, 1);
grdPnltrx.add(lblWtQrgSync, 0, 3);
grdPnltrx.add(chkBxWtQrgSync, 1, 3);
grdPnltrx.add(lblWtUsePassQrg, 0, 4);
grdPnltrx.add(chkBxWtUsePassQrg, 1, 4);
grdPnltrx.add(lblWtStationFilter, 0, 5);
grdPnltrx.add(txtFldWtStationFilter, 1, 5);
VBox vbxTRXSynch = new VBox();
vbxTRXSynch.setPadding(new Insets(10, 10, 10, 10));
vbxTRXSynch.getChildren().addAll(grdPnltrx);
@@ -8191,7 +8124,6 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
else if (chatcontroller.isConnectedAndLoggedIn()) {
btnOptionspnlDisconnectOnly.setDisable(false);
menuItemFileDisconnect.setDisable(false);
menuItemFileConnect.setDisable(true);
menuItemOptionsAwayBack.setDisable(false);
}
@@ -8215,19 +8147,13 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
txtFldstn_maxQRBDefault.setDisable(false);
menuItemOptionsSetFrequencyAsName.setDisable(true);
menuItemOptionsAwayBack.setDisable(true);
menuItemFileConnect.setDisable(false);
station_chkBxEnableSecondChat.setDisable(false);
stn_choiceBxChatChategorySecond.setDisable(false);
}
});
String btnText = "Connect to " + chatcontroller.getChatPreferences().getLoginChatCategoryMain()
.getChatCategoryName(choiceBxChatChategory.getSelectionModel().getSelectedItem().getCategoryNumber());
ChatCategory secCat = chatcontroller.getChatPreferences().getLoginChatCategorySecond();
if (chatcontroller.getChatPreferences().isLoginToSecondChatEnabled() && secCat != null) {
btnText += " & " + secCat.getChatCategoryName(secCat.getCategoryNumber());
}
btnOptionspnlConnect = new Button(btnText);
btnOptionspnlConnect = new Button("Connect to " + chatcontroller.getChatPreferences().getLoginChatCategoryMain()
.getChatCategoryName(choiceBxChatChategory.getSelectionModel().getSelectedItem().getCategoryNumber()));
btnOptionspnlConnect.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
@@ -8259,7 +8185,6 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
btnOptionspnlDisconnectOnly.setDisable(false);
menuItemFileDisconnect.setDisable(false);
menuItemFileConnect.setDisable(true);
menuItemOptionsAwayBack.setDisable(false);
menuItemOptionsSetFrequencyAsName.setDisable(false);
@@ -8509,24 +8434,9 @@ public class Kst4ContestApplication extends Application implements StatusUpdateL
public static void main(String[] args) {
setupFileLogging();
launch(args);
}
private static void setupFileLogging() {
try {
String logDir = Path.of(System.getProperty("user.home"), ".praktiKST").toString();
new File(logDir).mkdirs();
FileHandler fileHandler = new FileHandler(logDir + "/kst4contest-errors.log", true);
fileHandler.setLevel(Level.SEVERE);
fileHandler.setFormatter(new SimpleFormatter());
Logger rootLogger = Logger.getLogger("");
rootLogger.addHandler(fileHandler);
} catch (IOException e) {
System.err.println("Could not set up file logging: " + e.getMessage());
}
}
@Override
public void onThreadStatusChanged(String key, ThreadStateMessage threadStateMessage) {
-3
View File
@@ -1,11 +1,8 @@
module praktiKST {
requires javafx.controls;
requires javafx.fxml;
requires javafx.web;
requires jdk.xml.dom;
requires java.sql;
requires javafx.media;
requires java.logging;
exports kst4contest.controller.interfaces;
exports kst4contest.controller;
exports kst4contest.locatorUtils;
+1783
View File
File diff suppressed because it is too large Load Diff