Join one FM-Funknetz talk group from a headset window.

The station reads ~/.funknetz.json, speaks protocol 2.0, and builds an unsigned funknetz.app.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Konrad Neitzel
2026-10-03 16:21:41 +02:00
co-authored by Cursor
parent 8e75515460
commit 9c4f6621fc
23 changed files with 2933 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
body {
margin: 0;
background: #101418;
color: #f2f4f6;
font: 16px/1.4 system-ui, sans-serif;
}
main {
padding: 24px;
}
#connection {
margin: 0 0 16px;
font-size: 14px;
}
#detail {
min-height: 1.4em;
font-size: 20px;
}
button {
margin-top: 24px;
width: 100%;
height: 64px;
border: 0;
border-radius: 8px;
background: #2d6a4f;
color: white;
font: inherit;
font-size: 18px;
}
button.down {
background: #9b2226;
}
+98
View File
@@ -0,0 +1,98 @@
const button = document.getElementById("ptt");
function api() {
if (!window.go || !window.go.main || !window.go.main.App) {
return null;
}
return window.go.main.App;
}
function line(state) {
if (state.detail && state.detail !== "Idle") {
return state.detail;
}
if (state.talker) {
return state.talker;
}
return "Idle";
}
function render(state) {
document.getElementById("connection").textContent = state.connection;
document.getElementById("callsign").textContent = state.callsign;
document.getElementById("talkgroup").textContent = String(state.talkGroup);
document.getElementById("detail").textContent = line(state);
button.classList.toggle("down", !!state.transmitting);
}
function refresh() {
const app = api();
if (!app) {
return;
}
app.State().then(render).catch(function () {});
}
button.addEventListener("pointerdown", function (event) {
event.preventDefault();
button.setPointerCapture(event.pointerId);
const app = api();
if (app) {
app.HoldButton();
}
});
function releaseButton() {
const app = api();
if (app) {
app.ReleaseButton();
}
}
button.addEventListener("pointerup", releaseButton);
button.addEventListener("pointercancel", releaseButton);
let spaceHeld = false;
window.addEventListener("keydown", function (event) {
if (event.code !== "Space") {
return;
}
event.preventDefault();
if (event.repeat || spaceHeld) {
return;
}
spaceHeld = true;
const app = api();
if (app) {
app.HoldSpace();
}
});
window.addEventListener("keyup", function (event) {
if (event.code !== "Space") {
return;
}
event.preventDefault();
if (!spaceHeld) {
return;
}
spaceHeld = false;
const app = api();
if (app) {
app.ReleaseSpace();
}
});
window.addEventListener("blur", function () {
spaceHeld = false;
const app = api();
if (!app) {
return;
}
app.ReleaseButton();
app.ReleaseSpace();
});
setInterval(refresh, 100);
refresh();
+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>funknetz</title>
<link rel="stylesheet" href="app.css">
</head>
<body>
<main>
<p id="connection">Disconnected</p>
<p>Callsign <span id="callsign"></span></p>
<p>Talk group <span id="talkgroup"></span></p>
<p id="detail">Idle</p>
<button id="ptt" type="button">Push to talk</button>
</main>
<script src="app.js"></script>
</body>
</html>