Commit Graph

175 Commits

Author SHA1 Message Date
12ca585d90 Open a new line when a speaker's playback line dies
Both desktop backends dropped writes to a line whose device had gone
away, and the per-speaker playout ignored any failure, so that speaker
stayed silent for the rest of the session. A write to a dead line now
throws; the playout drops that line and the speaker's next packet
opens a fresh one. Sound effects and the microphone test's loopback
reopen their line the same way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:25 +00:00
6234ec6d97 Give back a speaker's line after 30 s of silence
Every client that ever spoke kept an open playback line and a thread
until it left the server. A speaker silent for 30 s now has both
released, and its next talk burst opens them again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:25 +00:00
0083dc8ad6 Play incoming voice through TeamSpeak's adaptive jitter buffer
Each speaker's stream held a fixed 60 ms and gave up after 200 ms of
concealment, so a jittery connection concealed constantly and a stall
ended the burst. It now runs through a port of the libspeex jitter
buffer the official client links, configured and driven as it does:

- the delay adapts to the measured jitter, aiming for at most 1% of
  packets arriving late
- a speaker keeps one timeline, never re-anchored between talk bursts,
  so what the buffer learned carries over; only 5 s without packets
  drops it
- the talker's stop marker never enters the buffer; playback ends when
  it reaches it, and its slot is played as silence, not concealed

A pull now always hands out one 20 ms frame: what a longer packet or a
delay increase produces beyond that is played on the following pulls,
which Android's mixer used to cut off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:25 +00:00
7cb4f47a81 Show an avatar, description or away message being cleared
Client and channel updates were only applied when the field had a
value, since ts3j reads a missing field back as an empty string. A
removed avatar, an emptied description or a cleared away message
arrives as the field without a value, so they stayed as they were. Such
fields are now applied whenever the update carries them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:25 +00:00
8c735a05b8 Look permissions up by name directly
selfPermissionValue scanned every permission the server lists, several
times per context menu. The model now keeps a name index next to the
id one. Also drops primaryServerGroupName, which nothing calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:25 +00:00
67f8f46cab Publish the channel tree's lists whole instead of refilling them
buildTree() cleared and refilled the children and client lists inside
the shared channel nodes, so a UI still walking the previous tree could
see them change under it. It now builds new lists and publishes them
unmodifiable. The node fields, written by the event and audio threads
and read by the UI, are volatile.

Whether a channel is full is now counted from the model, rather than
from a tree that may not have been rebuilt since the last join.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:25 +00:00
665316d515 Hand the microphone between tabs in the order it was asked
Each hand-over ran on a fresh thread, so two quick tab switches could
start the new tab's capture before the old one stopped, or stop it after
it started. They now run one after another on a single thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:25 +00:00
5b1809cc2d Hand out hotkey bindings as a snapshot
Hotkeys.all() returned its synchronised list, and every reader had to
lock it while iterating: a lock owned by one class and taken by four
others. It now returns a copy, and the lock stays inside Hotkeys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:25 +00:00
eeccb495eb Name chat logs by the server's unique id for guests too
The id came from initserver, which never carries it, then from
serverinfo and servergetvariables, which a guest may not use; chat logs
only worked with admin rights. ts3j now derives it from the server's
handshake key, as the official client does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:35:41 +00:00
d074ffaa7a Tell permission errors apart by the server's error id
The sound for a refused action was picked by looking for "permission"
or "insufficient" in the message, which also matched errors such as an
invalid permission id. The server's error ids for insufficient client,
group and permission power say it exactly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:31:28 +00:00
d28bb57598 Release a previous connection's resources on connect
A connect on an instance that was still connected, or still
connecting, replaced the socket and audio pipelines without closing
them. The fields shared between the connect, event, action and UI
threads are now volatile, and read once where they are used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:30:34 +00:00
f350b19d31 Run a connection's requests on its own pool
Each action started a thread that read the socket field once it ran; a
disconnect in between cleared it, and the user saw "Could not join
channel: NullPointerException". Actions now capture the socket when
they are issued and run on a pool that lives as long as the connection.
What fails because the connection went away is no longer reported as an
error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:29:23 +00:00
ba5de5a8a8 Give the importer test a fresh profile every run
The test home under target/ outlives a run, so the identity the first
run imported made every later run fail. The test now points AppDirs at
a temporary directory of its own, which also works when another test
resolved the profile first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:27:11 +00:00
11a2a14c70 Let a session reconnect to where it was
ServerSession.reconnect() connects again to the last server with the
same identity and returns to the channel the client was in: what a
dropped connection or a changed network (Android moving from Wi-Fi to
mobile data) needs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:26:03 +00:00
07aa7bd7f9 Don't report a channel join that leaving the server cut short
Connecting with a channel moves there once the channel list is in; if
the connection was left again before that, the move ran against the
closed socket and reported an error nobody could act on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:26:03 +00:00
e211ffa7b6 Share libopus' encoder controls between bindings
libopus' constants and the encoder settings built on opus_encoder_ctl
(clamping, voice/music signal) move into core, so the Android binding
reuses them rather than copying them from the desktop's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:24:12 +00:00
75d9606231 Conceal a lost voice packet with 20 ms, not 120 ms
A lost packet was concealed by decoding "nothing" into a buffer sized
for the longest Opus frame, and Opus fills whatever it is given: every
lost packet played 120 ms of made-up audio in place of 20 ms, piling up
delay on that speaker's line.

Decoders now conceal through their own call that takes the length to
make up, and the stream asks for the length of the last packet it got.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:18:06 +00:00
fecb2ba8f4 Answer what the local client may do from the model
The Swing client menu decided on its own whether a group can be
(un)assigned and whether a client's channel can be joined, and the
server groups dialog borrowed the menu's check. These are rules about
the server, not about menus, so ServerModel answers them now and any
frontend asks it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:10:54 +00:00
8f385b9343 Keep each server's session state in core
What a server tab knows beyond its views - the connection lifecycle,
title and status, the identity used, the microphone/speaker/away and
commander flags, chat sending and the channel lookups - moves from the
Swing ServerTab and ServerTabSelfState into core's ServerSession, so
another frontend gets the same behaviour without copying it.

ServerSession also takes over what the Swing event bridge decided on its
own: clearing the model on disconnect, naming the tab after the server,
showing senders under their contact names, and the chat's system lines.
The bridge now only updates views. SelfState moves along, while its
icons stay with the tray.

A server switch no longer needs to queue behind the UI thread: the
disconnect is reported synchronously, so the session is free again by
the time it reconnects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:09:23 +00:00
24f4b3813d Build core for Java 17
Android will run core as well, and its runtime stops at Java 17. Pinning
the release makes the compiler reject newer language features and APIs
there, while the desktop modules keep the newest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:03:42 +00:00
94748e27ee Play notification sounds from core
The sound player was a desktop class only because it decoded wave files
through Java Sound. A small RIFF reader replaces that, so the player
moves to core and every platform gets it from AudioBackend by default.

The reader takes integer PCM up to 32 bits and 32-bit float, in plain
or extensible headers. It decodes all 151 files of the TS3 sound packs
to the same samples Java Sound does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:03:08 +00:00
7396c4ca40 Move the voice pipelines into core
Capture processing, the jitter buffer and decoding were desktop classes,
so any other platform would have had to copy them. They now live in core
and reach the platform only through two interfaces:

- AudioIo lists devices and opens capture/playback lines; the desktop's
  AudioDevices implements it over PipeWire and Java Sound.
- OpusCodec creates encoders and decoders; the desktop binds libopus
  through the FFM API as before.

DesktopVoiceInput becomes CaptureVoiceInput unchanged in behaviour.
DesktopVoiceOutput splits into VoiceStream, one speaker's jitter buffer
and decoder, paced by whoever pulls it, and StreamingVoiceOutput around
it. Speakers reach the device either on a line each (the desktop, so
each shows up in the PipeWire mixer) or mixed onto one shared line, as
mobile audio APIs want.

The settings dialog's device lists and microphone test now go through
the AudioBackend instead of desktop classes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:01:28 +00:00
3c389de36b Resolve the client's data directories in one place
Stores read their paths from AppDirs instead of user.home, so a platform
without a home directory can point them elsewhere at startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 07:52:32 +00:00
1d61f458ae Process the microphone like TS3, and tell it about key presses
The capture path now runs the ported WebRTC chain in place of the
home-made noise, typing and gain stages, which are removed.

- As in TS3, the speech detector judges the raw microphone signal, while
  the level meter and volume gate see the processed one.
- Noise removal takes TS3's four levels (6, 12, 18 or 21 dB); a stored
  0..1 level falls back to TS3's default of 12 dB.
- Every key press from the global input hook reaches the connected
  microphones and the microphone test, so typing attenuation engages
  while the user types.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 20:47:04 +00:00
de5f7da236 Port TS3's WebRTC capture processing to Java
TS3 pre-processes the microphone with WebRTC's audio processing module.
AudioProcessor ports the stages it enables, in APM's order, per 10 ms at
48 kHz:

- The high-pass filter APM forces on with noise suppression, then the
  three-band split and WebRTC's noise suppressor at TS3's four levels.
- The transient suppressor behind "Typing attenuation", which only acts
  while it is told about key presses.
- AGC2 adaptive digital, WebRTC's successor to the AGC1 that TS3 runs.

The tests compare against 16-bit outputs of upstream APM builds
(webrtc-audio-processing 2.1, and 1.3 for the transient suppressor).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 20:46:46 +00:00
ee0f15fb3c Stop tripping the server's flood protection with query commands
channelinfo, clientinfo, channellist and clientlist are query commands
that count heavily against anti-flood; clicking through the tree got
every command refused within seconds. Use what the official client does:

- Channel descriptions via channelgetdescription, whose answer arrives as
  an invoker-less channel edit.
- Client version and platform via clientgetvariables, answered as a
  client update.
- Connection info takes version and platform from the model and idle
  time from notifyconnectioninfo instead of polling clientinfo.
- Drop the channellist/clientlist re-read after connecting: the events
  during connect already carry everything, and more (groups, unique ids,
  away state).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 18:32:05 +00:00
b01ed43d60 Find clients and channels in the server tree
Ctrl+F in the tree opens a find bar below it that washes matching rows,
steps the selection through them and can prune the tree down to the
matches and the channels leading to them. ServerTreePanel becomes a
panel holding the scroll pane and the bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:09:45 +00:00
d05c04afb4 Find in chat with Ctrl+F
A find bar above the conversations highlights matches and steps through
them, or filters the selected tab down to matching messages. New lines
no longer pull the view away while it is open. Ctrl+F opens it from
anywhere the tree doesn't claim the shortcut.

Note tabs are now found by their scroll pane like other tabs, so sending
checks for a target instead of assuming every tab has one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:09:45 +00:00
a4057cd499 Add text queries and whole-log chat search
TextQuery holds a find bar's text and options (match case, whole word,
regex) and matches plain text; HtmlText strips a log entry down to it.
ChatLogStore can now search a whole log rather than only the history
loaded into a chat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:09:45 +00:00
249430efad Hide query clients from the log and name server groups correctly
- Query clients were already left out of the tree, but their joins, moves
  and ping-timeout drops were still logged and could play sounds; TS3 keeps
  them out of sight entirely.
- The name field of notifyservergroupclientadded/deleted is the client's
  nickname, not the group's, so "added to server group" named the wrong
  thing. Resolve the group by id instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:08:18 +00:00
3f65e95afd Fix channel reordering and keep the channel tree in sync
- Sort a channel among its current siblings with channeledit channel_order,
  as the official client does; the server refuses channelmove within the
  same parent ("already member of channel").
- Relink siblings when a channel_order edit arrives: the server never
  re-announces the neighbours, so the tree drifted out of order after any
  reorder.
- Apply password, permanence and max-clients flags from channel list,
  create and edit events, and let an emptied topic clear the old one.
- Log moves like TS3: "..., new parent channel is X".
- Play identical sounds that start within 250 ms only once, so bursts
  (a channel deleted with its subchannels) don't stack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:06:53 +00:00
4de3ad4cab Make the talking-while-muted beep optional
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 14:25:58 +00:00
03ea2c86c3 Give the window its own identity in Linux task bars
AWT names the X11 window class after the thread that first touched the
toolkit, so the client shared java-awt-EventDispatchThread with every other
Swing app. Set it to ts3j-client instead (the jar's manifest opens the AWT
internals this needs), and install a matching .desktop launcher and icon in
the user's data directory: without a launcher claiming the class, Plasma falls
back to the process's command line and files the window under Java.

The window icon is now offered at several sizes instead of 16 px only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 14:21:10 +00:00
f27cc40755 Build ts3j from source as part of the client build
The client resolved ts3j 1.0.3 from ~/.m2, so a machine that pulled new
ts3j sources without re-running mvn install silently shipped the old
library - here one without the packet generation fix, which fails
connects to busy servers with "mac check in EAX failed".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 12:43:56 +00:00
5fc2405d4a Update ts3j: resend like the official client; clean up failed connects
- ts3j: native resend timing, INIT1 resends, one clientinitiv per handshake,
  non-blocking disconnect.
- Ignore errors from a connect attempt that has been superseded or cancelled.
- Show the whole cause chain in network error messages.
- -Dts3j.debug=true enables ts3j's protocol logging.
- Tests run with a throwaway user.home instead of the real profile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 04:58:21 +00:00
c743efc3b6 Keep the window responsive while the channel tree loads
Connecting to a server with ~560 channels froze the UI for over two
seconds: every model event queued its own full tree rebuild, and each
rebuild expanded row by row with Swing's variable-height layout cache,
which measures every row on each expansion.

Coalesce model-change bursts into one rebuild and switch the tree to the
large-model layout (it already has a fixed row height), which only
measures rows on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 23:21:52 +00:00
3187608579 Update ts3j: survive packet loss during the handshake; allow 30 s to connect
Picks up the ts3j fixes for "mac check in EAX failed" disconnects on lossy
links (retransmitted handshake packets now decrypt) and for the handshake
stalling when clientek's ACK is lost, plus tolerating an empty
client_servergroups.

The connect timeout also covers downloading the channel list, which on a
big server over mobile data can take longer than the previous 10 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 23:21:52 +00:00
9d996f48a2 Scale the interface fractionally on Linux
Java2D on X11 (and so under XWayland) truncates its scale to a whole
number, drawing 150 % at 100 %. On Linux, pin Java2D to 1 and hand the
full factor to FlatLaf, taken from XSETTINGS Xft/DPI, KWin's Xwayland
scale or GDK_SCALE, or chosen in Options → Design.

The client's own sizes now go through UIScale: borders, gaps, dialog and
row sizes, icons (rendered at the scaled size, so they stay crisp),
custom-painted components and font sizes in the HTML views.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 22:33:27 +00:00
1fddae3f0b Always rebuild the swing jar before shading it
Without forceCreation, maven-jar-plugin kept the previous shaded
ts3-client.jar when the module's classes were unchanged, and the shade then
used it as input: the stale ts3j classes inside it won over the freshly
installed ts3j, so ts3j fixes never reached the built client without a clean.

Also updates ts3j for the empty server-group parsing fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 19:30:48 +00:00
2a73900dcc Show client avatars and let the user set their own
Avatars are fetched from the server's file repository as /avatar_<id>,
verified against client_flag_avatar and cached on disk; animated GIFs play in
the info panel. Self > Set avatar... uploads or removes our own picture within
the server's size limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 19:03:00 +00:00
dbb425403a Import bookmarks and identities from the TeamSpeak 3 client
The official client keeps what a myTeamSpeak account synchronises as protobuf
Item_Data blobs in the ProtobufItems table of settings.db. Decode them, adopt
them on first run when we have no identity yet, and offer a re-import from
Manage Bookmarks and Identities. The settings.db reader moves from contacts to
a new teamspeak package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 19:02:52 +00:00
bab89f9b50 Add the chat log store missing from earlier commits
TeamspeakConnection and ChatPanel already use com.ts3client.chatlog, but the
package itself was never committed, so the tree did not build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 19:02:40 +00:00
f409f500fd Add the contact list (friends, blocked, neutral)
Mirrors the TeamSpeak 3 client's Contacts: clients can be filed as friend,
blocked or neutral from their context menu or by dragging them into the
Contacts window, given a custom and phonetic nickname, shown under either
name or both, and per contact muted automatically, ignored in server/channel
or private chat and pokes, have their away message hidden and whispers
allowed or denied. Friends are green and blocked clients red in the tree
(optional), new contacts start from per-type defaults ("Set Defaults"), and
the global whisper policy lives in Options -> Contacts. "Change Volume..."
adds a per-client dB gain that a contact entry remembers.

Contacts are stored in ~/.ts3jclient/contacts.txt using the exact record
format the official client keeps in the Contacts table of its settings.db
(field names and codes recovered from the binary), so entries move between
the two verbatim. A small read-only pure-Java SQLite reader imports the
official client's list on first run and on demand, without a driver.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 07:50:58 +00:00
22e0304f25 Add the Tools > Ban List window
Reimplements the official client's ban list: a searchable table of the
server's bans with Add / Remove / Edit, an own-ban filter and highlight,
choosable columns, and the Add/Edit dialog whose IP and name patterns
can be read as IPv4/IPv6 wildcards, fixed strings or regular expressions
(translated to the regexes banadd takes exactly as the official client
does). Editing is add-then-delete, since the protocol has no banedit.

The ban list is read from the notifybanlist burst that answers banlist,
like channelpermlist. The menu item is only shown where the server grants
b_client_ban_list.

Also fixes ServerModel numbering permissions from 0 instead of 1, which
had every selfPermissionValue() lookup reading the neighbouring
permission — including the group-assignment menus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 23:00:18 +00:00
636090d075 Start overlapping notification sounds without delay
The sound mixer rendered as fast as the playback line accepted, running a
full device buffer ahead of real time, so a sound fired while another
played could only join the mix behind all of that queued audio, and each
further one landed later still. Pace the mixer against the clock so it
never commits more than a few frames ahead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:54:02 +00:00
ad62f5fc92 Update connection and chat handling 2026-09-06 19:14:42 +00:00
62d5af7cf9 Smooth incoming voice under jitter 2026-09-06 13:43:11 +00:00
a01c598fe6 Remember how tall the user made the info panel
Kept as a share of the tree/info column, so it survives new tabs, a resized
window and the next session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 08:11:29 +00:00
02533b543d Sit the info panel's link in the viewport's corner
It overlapped the scroll bar; the viewport is what the scroll bars have
already been taken out of. The layered pane also has to report a size now,
or the split pane above it has nothing to size the panel from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 08:11:29 +00:00
5a00015824 Float the info panel's "Show in chat tab" link in its corner
As a real label over the text, it stays reachable however long a description
runs, instead of trailing off the end of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 07:46:42 +00:00