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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
The look-and-feel is FlatLaf now, chosen light or dark on the new Design
tab and remembered in the settings. Theme keeps only the colours TeamSpeak
gives meaning to (talking, away, muted, channel and server names) and takes
its surfaces and fonts from the look-and-feel, so a switch recolours the
whole window, chat logs included: the HTML panes share one themed stylesheet
and re-parse their content when the theme changes.
Also fixes the channel tree not scrolling: the tree listened for the mouse
wheel to re-check its hover row, and AWT only forwards a wheel event to the
enclosing scroll pane when the component under the pointer has no wheel
listener of its own. The hover row now follows the viewport instead, which
also covers scrollbar drags and keyboard scrolling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The renderer asks the panel which id is ours rather than reading
ClientEntry.self: our own row usually arrives among the enter-view events
of the connect handshake, before the connection knows its own client id,
so that flag was left false. It is now stamped once the id is known,
which also fixes the "(you)" marker in the info panel.
The inline editor matches, and sits 2px further left so its text lines up
with the label it replaces rather than with its own border.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Log lines now name clients and channels with TeamSpeak's own client:// and
channel:// links, the same ones a dragged-in reference produces, so the
chat log's existing handling turns them into the context menus.
The log is rendered with a links-only pass rather than the full BBCode
renderer: its lines are assembled by the client itself, so nothing else in
them should be interpreted — a channel called "[b]VIP" stays literal.
Channels we no longer have (a deleted one) are still named in plain text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Offered for our own client as well as others, with TS3's edit icon. The
description is read back from the server before prompting, since the
model only carries one for clients whose info has been looked at.
Note that client_description is not a clientupdate property even for
ourselves — the server only takes it through clientedit — and that our
own change is not echoed back to us, so the local entry is updated here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The description is fetched on demand and cached, but nothing invalidated
it: notifychanneledited never carries the text, and the separate
notifychanneldescriptionchanged was ignored. Handle that event, and
refresh locally after our own edit, since the server does not notify the
invoker.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ftgetfilelist is answered with a notifyfilelist event per entry and a
closing notifyfilelistfinished, not with rows of the command's own reply
— and ts3j dispatches anything named notify* as an event, so every
listing came back empty. The entries are now collected for the request
in flight, which is what the icon chooser and the file browser read.
The icons themselves live in the repository's "icons" directory on newer
servers rather than loose in its root, so both places are read. They are
still addressed as /icon_<id> for download, upload and delete, which is
how the server resolves them regardless of where it keeps them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The official client creates and edits channels with one dialog, so
ChannelEditDialog becomes ChannelDialog with two entry points. Creating
starts from caller-chosen defaults instead of a channelinfo and sends a
channelcreate carrying only what deviates from a plain new channel —
TeamSpeak checks a create permission per property that is present.
The server row now has a context menu with "Create Channel" and "Create
Spacer" (pre-filled with the lowest free [spacerN], since channel names
must be unique), and a channel's menu offers "Create Channel" and
"Create Sub-Channel", both starting out temporary.
Verified against the test server: a temporary channel is created, and a
permanent one a guest may not create reports the server's refusal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Recovers the official client's CreateChannelDialog layout so the widget
set, labels and tab order match: name/icon/password/topic/description
above Standard, Audio, Permissions and Advanced tabs.
Core gains ChannelSettings (diffed against the original so channeledit
only carries changed properties) and ChannelAdmin (channelinfo/edit,
channel permissions, the server icon store). TeamspeakConnection now
owns the event executor so it can drain queued notify events before
reading a command's result, which channelpermlist needs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lets moderators assign or revoke server and channel groups for a
client directly from its context menu, with per-group checkboxes
gated by the client's actual TS3 permissions (i_group_needed_member_
add/remove_power vs. each group's n_member_addp/removep, including
TS3's -1 "unlimited" sentinel). A "Server Groups Dialog..." entry
shows every server group, graying out ones the client can't touch.
Group membership changes now update ClientEntry locally instead of
only logging them, so the checkboxes and dialog reflect a change
immediately instead of needing a reconnect. The server's default
channel group (e.g. "Guest") is hidden from the channel-group menu
since it's not something you assign directly.
Extract notify-event handling and the native-style server log/sound
announcements into ConnectionEventHandler, and getconnectioninfo
request/response + stats-snapshot building into ConnectionStatsCollector.
TeamspeakConnection remains the public facade (1828 -> 1037 lines); its
external API is unchanged. Pure refactor, no protocol/timing changes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The toolbar now has a right-click menu to hide/show the status bar and
a short Master Volume slider (drag or scroll) that multiplies both
voice and notification volume via new Settings.effectiveOutputVolume()/
effectiveSoundVolume() helpers.
Also splits MainFrame's toolbar, menu bar and status bar construction
into their own MainToolbar/MainMenuBar/StatusBar classes, each talking
back to MainFrame only through a Listener interface.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Native TS3 logs client joins/leaves/moves, group changes and channel edits
to the server tab; minor local-only notices (mic mute, tray) no longer
spam the log. Chat tabs scroll by mouse wheel like server tabs. The info
panel can now be moved into a persistent, generically-labelled chat tab
that tracks tree selection and collapses the panel's space entirely; a
tree rebuild no longer briefly fires a null selection that reset this.
Added "Find Client in Channel Tree" to the client context menu, and tree
selection now survives model rebuilds.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Mute/deafen status now goes out through clientupdate instead of
clientedit, which silently rejected client_input_muted/output_muted
since they are runtime status, not editable client properties.
- Publish client_input_hardware so other clients see "Microphone
Disabled" instead of silence while another tab holds the capture
device; track input/output hardware flags for other clients too,
and show a distinct grey "disabled" icon instead of reusing the red
"muted" one for both the tree and the info panel.
- Implement TS3's Enable/Disable/Toggle Local Mic Mute hotkeys: they
silence capture like a real mute, but never touch the published
mute status or play a sound.
- A speaker's "talking" indicator only ever cleared when its
zero-length end-of-burst voice packet arrived; if that one UDP
packet was lost, the indicator stuck until their next burst. Add a
watchdog that clears it after 200ms of silence from that speaker
regardless.
Ports WebRTC's rnn_vad (as TS3 embeds it) to Java: LPC, pitch
estimation, spectral features and the RNN itself, feeding a
speech-probability detector that replaces the old SpeechDetector.
Also switches the volume-gate threshold from raw dBFS to
InputLevel's scale, matching TS3's own slider and range, with a
migration for settings saved under the old key.
A message-only window registers the keyboard and mouse with
RIDEV_INPUTSINK, so every key and button arrives as WM_INPUT whether or
not the client is in front. Raw input only observes, where a
WH_KEYBOARD_LL hook sits in the path of the system input queue and can
swallow a keystroke; it also reports the side buttons and both edges of
every key, which push-to-talk needs.
Keyboard codes stay in each platform's own numbering — X keycodes on
X11, set-1 scan codes with the E0/E1 escape folded into the high byte on
Windows — since that is what the input API reports and the key-naming
call expects. Bindings are per-machine either way, as TeamSpeak's own
per-OS keydefs are. Mouse buttons are unified on the X numbering, so
"Mouse 4" means the same thing on both.
The RAWINPUT decoding lives in its own class so it can be tested off
Windows; the window and its pump have not been run on Windows yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>