Commit Graph

196 Commits

Author SHA1 Message Date
0ff0919db2 Keep the server tab strip in step with theme switches
The strip is out of the component tree while one server is open, so a
look-and-feel switch missed it and it came back in the old theme's colours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:31:43 +00:00
bda114d2c0 Let admins delete other clients' avatars
With b_client_avatar_delete_other, the client's context menu and the
menu on their avatar offer "Delete Avatar", as in TeamSpeak 3. The
server takes the deletion as ftdeletefile on /avatar_<unique id> (not
the stored file's name, which it refuses with "convert error") and
clears the client's avatar flag for everyone. Our own avatar's file is
now deleted the same way, which only such clients are allowed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:27:41 +00:00
b1890cbf79 Set and delete our avatar from the Self menu and the avatar's menu
As in TeamSpeak 3, Self offers "Set avatar…", which picks an image and
uploads it, and "Delete avatar" while we have one; the old avatar
dialog is gone. Setting (and stealing) is offered only while the server
grants i_client_max_avatar_filesize. Right-clicking our own avatar in
the info panel offers the same two beside saving it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 16:01:16 +00:00
e7d338ce28 Save or steal other people's avatars from the info panel
Right-clicking a client's avatar offers to save it to a file or to take
it as our own avatar on that server, as the Android picture viewer does.
Our own avatar only offers saving.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:56:11 +00:00
8fc7be826c Let the noise removal slider move freely like the AGC boost slider
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 15:51:34 +00:00
662e031b6b Find in the channel tree and the chat on Android
A find button in the top bar opens a find bar over the tree (clients or
channels) or the conversation on screen, with match stepping, highlights,
only-matches filtering and the desktop's matching options. The tree's
match rule moves to core TreeMatch, which the desktop now uses too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:54:02 +00:00
66ef62913a Find a client in the channel tree from their sheet on Android
Offered, as on desktop, when the sheet was opened from outside the tree
(a name in the chat): expands the client's channels, scrolls to them and
flashes their row amber.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:13:49 +00:00
f80f619a65 Put friend and block on one line in the Android client sheet; remove contacts from their page
The client sheet keeps only "Edit contact" for contacts. The contact page's
remove button takes the remove-friend/foe icon instead of the trash can.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:52:41 +00:00
7f30a27d73 Open avatars and description pictures full size on Android; fold minor client facts
A client's sheet now shows only their server and channel groups, with
the rest (contact, talk power, version, microphone, unique ID, ...)
behind "more…". Core marks those facts minor, for frontends short of
room; groups without a name or an icon are left out on both clients.

Tapping an avatar or a picture in a description opens it on black, to
pinch or double-tap zoom. Its menu saves it to Pictures/TeamSpeak, takes
it as our avatar (not on our own), copies its address, and opens the
link it sat in, which a tap used to do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:27:50 +00:00
e799218444 Show clients' country flags beside their names, and optionally in the tree
The server places each client in a country (client_country, from its GeoIP
database); a client's details now show that country's flag left of their
name, as TeamSpeak's info frame does, on both clients. "Show country flags
in the channel tree" (off by default, as TeamSpeak's EnableCountryFlags)
also puts it last on client rows.

The flags are TeamSpeak's countries.zip, which now ships with the icon
packs; core's CountryFlags reads it, else a TeamSpeak 3 install's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:40:44 +00:00
4ca5534ef4 Ship TeamSpeak's icon packs with both clients
Default, Default Colored 2014 and Default Mono 2014 now come inside the
desktop JAR and the APK, so the default pack is there without a TeamSpeak 3
install; before, a desktop without one had no icons at all. As with the
sound packs, the builds take them from the TeamSpeak 3 client next to the
repository (ts3.client.dir to point elsewhere) and ship none without it.

Core reads them from the classpath in memory; a pack of the same name found
on disk, in an install or imported, still comes first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:16:48 +00:00
af0564702e Show pictures in descriptions, or links to them when turned off
[img] in a channel, client or server description is now a picture on both
clients, as TeamSpeak shows it; chat keeps it a link. Only web addresses
become pictures. While one loads it is TeamSpeak's loading icon; one that
cannot be had is its address behind the broken-picture icon.

"Show pictures in descriptions" (desktop: Chat settings; Android: Appearance)
turns them into links behind a picture icon instead, fetching nothing. A
picture that was itself a link keeps that link beside it.

Core's PictureDownloads fetches them for both clients: kept on disk for a
month after last shown, at most 8 MB, redirects followed across http and
https, and on Android plain http asked for over https. The desktop draws
them narrowed to the info panel, Android at a pixel to a dp, animated GIFs
playing on both; long addresses are shortened to host and file name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:06:13 +00:00
fd86d9e638 Show server, channel and group icons and client avatars on Android
The tree lines icons up at the right of each row as TeamSpeak does: the
server's own icon, a channel's icon, and a client's server group icons then
their channel group's. The details list each group with its icon, and the
client sheet shows the avatar; an animated GIF plays while it is on screen.

TeamSpeak's default group icons come from the icon pack, as on the desktop;
a server's own uploads are downloaded by core's repositories and decoded
once per server, redrawn as they arrive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:34:52 +00:00
b678805c37 Offer the server groups we have the power for, not only those needing none
canToggleGroup compared a group's needed member add/remove power with our own
i_group_needed_member_*_power, which the server never reports for us: our
power is i_group_member_add_power and i_group_member_remove_power. An admin
was only ever offered groups that need no power at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:21:43 +00:00
b1ad403fdf Move clients to another channel or our own from the Android client sheet
"Move to channel…" closes the sheet and marks the client, and the next
channel tapped is where they go; a bar above the tree names them and cancels.
"Move to own channel" shows while they are elsewhere.

Both show only with some i_client_move_power. The server's needed
permissions never carry it, even for an admin, so core asks for it with
permget on connect and whenever our own groups change; a refusal (guests may
not ask) counts as none. Whether it beats the client's needed move power only
the server knows, and it refuses when not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 22:16:21 +00:00
cd02e4b2d8 Show server, channel and client details on Android, and the server's on the desktop
Core's Details says what the info box tells, as TeamSpeak's info frame does:
labelled facts, then a BBCode description. The desktop info panel now lays it
out instead of building its own, and selecting the server row shows the
server: address, version, clients, channels, creation date and its welcome
and host messages, from the virtualserver_* properties kept since initserver.

On Android the channel and client sheets show the same details, asking the
server for a missing channel description or a client's variables; tapping
the server row opens a server sheet. Descriptions render as TeamSpeak's do:
BBCode now knows [hr], [left]/[right]/[justify], numbered and lettered lists
and tables, which Android's HTML parser approximates — rules as dividers,
table rows as lines, list items numbered, font sizes as big/small. Links are
blue whatever the wallpaper.

Push-to-talk in the bottom bar follows whichever server holds the microphone,
and a server without it keeps its button to take it in a fixed slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 21:52:28 +00:00
9b1aa45611 Connect to several servers at once on Android, in a denser TeamSpeak-like layout
The server name in the top bar opens the servers the app is on, "Connect…" for
another one and "Disconnect from current". Only one server captures with the
microphone at a time, handed over by core's Sessions as on the desktop; a
server without it shows a button to bring it there. Push-to-talk, the floating
button and the level meter follow the server holding it.

Each server has its own controller (session, tree, chats, network recovery);
the app-wide one keeps what they share. Private message notifications carry
their server, as client ids repeat across servers.

The layout follows TeamSpeak's own: 26 dp tree rows with smaller icons and
text, a lower top bar, and no tabs — the chat is a button in the bottom bar,
whose buttons keep fixed places around the push-to-talk button's space.
Holding the mute button switches between push-to-talk and voice activation.

A server that drops the connection now lets go of the microphone too, so a
reconnect cannot capture alongside the server that took it over meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:08:36 +00:00
9f2e5087ce Stop two servers taking turns claiming a contact's last sighting
A contact on two servers at once was marked seen by each in turn: a sighting
announced a contact change, every connection re-applied its contacts, and
each re-application recorded the contact on its own server — another change,
round and round, rewriting contacts.txt every time. Re-applying now only marks
a contact that was never seen; the last sighting moves when the client appears.

Sightings no longer write the file on their own either: they reach it in one
save half a minute later, or with the next edit's save, and are flushed when
the client closes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:08:36 +00:00
1dda20d1b2 Manage identities on Android, pick one per bookmark, and give identities their own nicknames
Settings -> Identities lists them and creates, imports (an identity file or a
TeamSpeak 3 settings.db), exports, renames and removes them, picks the
default and improves an identity's security level; the search runs on until
reached or stopped, whichever page is on screen. The Connect screen and each
bookmark choose the identity to connect with.

An identity keeps a nickname and phonetic nickname, in TeamSpeak's own INI
keys, on both clients. A connect goes by the bookmark's nickname, else the
identity's, else the global one; the phonetic one is sent in clientinit.
Imported bookmarks no longer copy their identity's nickname.

IdentityStore is now safe to share with the thread a connect resolves its
identity on, and imports and exports through streams for Android's document
picker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:17:14 +00:00
6aa4be38ee Fold the Android noise suppression toggle into its slider, and let the muted-talk beep be turned off
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 23:42:43 +00:00
f1e7c93594 Float a push-to-talk button over other apps on Android
Held, it talks; a double tap whose second touch is held drags it. It shows
while the app is off screen in push-to-talk mode, half see-through at rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 23:38:59 +00:00
4b50f37735 Ship the Android sound packs as Opus
The build encodes TeamSpeak's packs to Ogg Opus at 64 kbps before bundling
them, which takes the sounds from 21.3 MB to 2.0 MB, in the APK and again
once unpacked on the phone. The encoding runs the desktop code's libopus
on the build machine, through a build-only :desktop module compiled from
the same sources as the Maven one.

Both clients play Opus sounds: the sound player (now PackSoundPlayer, as it
no longer plays only wave files) decodes Ogg Opus with the platform's
libopus, and a pack's play("x.wav") finds x.opus, so converted packs keep
their scripts. SoundPackConverter turns any pack folder into Opus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 17:12:50 +00:00
02ff63341e Notify of pokes and private messages on the desktop
While none of the client's windows has the focus, a poke or a private
message shows a desktop notification, which the desktop's notification
centre keeps. A private conversation keeps one notification, updated as
messages come; clicking it brings the window forward with that chat open,
and they all go once a window of the client is in front again.

On Linux they go to the freedesktop.org notification service, spoken to
over the session bus by a small D-Bus client of our own on Java's Unix
sockets, so no library or native code is involved; Plasma also takes a
reply typed into the notification. Elsewhere they are AWT tray messages,
which Windows and macOS put in their notification centres. The options
can turn them off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:20:51 +00:00
a6efc0b8da Notify of pokes and private messages on Android, with TeamSpeak's sounds
While the app is out of sight, a poke or a private message posts a
notification; a message's one gathers the conversation, takes a reply
inline and opens its chat. The notifications are silent, as the sound
pack already plays TeamSpeak's own sound for either.

The build bundles the sound packs of the TeamSpeak 3 client found next
to the checkout (or at ts3.client.dir / TS3_CLIENT_DIR), unpacked into
the profile once per app version. Sound Settings picks the pack and its
volume, and speech packs talk through the phone's text-to-speech.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:12:16 +00:00
002b1ac7ce Dock the tray icon once the window is on screen
Docking waits for the panel to adopt the icon, which held up the main
window; wait at most half a second for it before falling back to AWT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 12:25:44 +00:00
75df46a03a Narrow the desktop contact list
Each contact shows just the date it was last seen, without the client
dot and at half the usual nesting; the full time and server move under
the phonetic nickname as "Last seen … on …".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 11:39:15 +00:00
fedb7b4385 Show all of a contact's options at once on desktop
The contacts window drops its tabs for one column of short groups beside
the list: contact type, display and ignore, where whisper becomes a
"Deny whisper" box, since allowing is the default. The names get their
labels on a line of their own, and the window is never shorter than the
options, leaving the list the only thing that scrolls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:52:56 +00:00
570feffc25 Keep contacts on Android
A contacts page lists friends, blocked and neutral clients with when and
where each was last seen, and opens each one's settings: custom and
phonetic nickname, type, how the name is shown, what to ignore, whether
it may whisper, a volume change, and its unique ID, copied on a tap.
Changes are stored at once. Another page holds the defaults new contacts
start from, and a TeamSpeak 3 settings.db can be imported.

A client's sheet on a server adds them as a friend or blocks them, or
edits and removes the contact they already are. Everything else runs on
core's contact store as on desktop, so a change reaches the server's
playback and tree straight away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:52:56 +00:00
5c0a5e4bcf Sort the Android settings into categories
Settings opens on a list of categories, for now Sound Settings (devices,
transmission, voice activation, processing, playback) and myTeamSpeak,
each a page of its own, with the icon pack beneath them. The nickname
field goes, as the connect screen already keeps it. The rows the pages
share move into their own file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 10:52:56 +00:00
e7741e8b34 Give the Material pack every icon TeamSpeak's packs have
The rest of default_colored_2014's keys get Material counterparts:
talk power, whisper, recording, text formatting, identities, add-ons,
permissions and the like. Ping levels are drawn as signal bars, since
Material's own tell their levels apart by a hairline, and the default
group icons as lettered badges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:43:07 +00:00
cdf81f3258 Share icon packs between desktop and Android
The Android app's Material icons become a TeamSpeak-format pack shipped in
core, which the desktop offers next to the TeamSpeak packs. Android now
draws its icons from packs too and imports TeamSpeak's own zips, falling
back to the Material pack for what they lack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:39:42 +00:00
a7aa122728 Sign in to myTeamSpeak and import what the account synchronises
The myTeamSpeak client protocol and its end-to-end encryption are
reverse-engineered (docs/myteamspeak): an HTTP POST of a framed protobuf,
a PBKDF2-HMAC-SHA512 login token and account key, an AES-GCM wrapped
item key and AES-CTR items with a SHA-512 trailer.

Options on desktop and Settings on Android gain a myTeamSpeak page: sign
in and out, and the account's bookmarks and identities, each picked for
import and marked when already here. The client stays signed in the way
the official one does, keeping the derived token and key rather than the
password; each read signs in afresh, and a sign-in the server no longer
takes signs out. Nothing is written to the account.

Items are the same Item_Data as the local TeamSpeak store, so they go
through the existing decoder and importer; a bookmark chosen alone brings
the identity it connects with, found by UUID or, as some name it, by
the identity's name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 01:07:17 +00:00
73a15a8cfc Let AGC raise the noise floor further to bring up a distant voice
AGC2 won't amplify the background noise above -50 dBFS, which also caps
the gain for a voice barely above that noise: a distant talker ended up
5 dB quieter than a near one with noise suppression on, 12 dB with it
off. A "Boost quiet speech" setting (0-20 dB, default 0) raises the cap,
live, on desktop and Android.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 22:08:50 +00:00
491cbcb259 Leave the server when the app is swiped away
The process lived on without a screen, or was killed without a word, and
the server held our identity as a ghost until it timed out, refusing a
reconnect in the meantime as a clone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:31:50 +00:00
5fa7024bcb Follow the phone from one network to another
A server knows its clients by address, so moving between Wi-Fi and mobile
data leaves the connection with nothing to do but time out. The process's
sockets are now bound to the network a connection was made over: on a
change, the goodbye still leaves through the old network while it lasts,
and the connection is made again over the new one, back into the channel
we were in. A drop because the network vanished counts as a change too.

A server still holding the old connection refuses the new one as a clone
until it times out, so attempts are retried with a growing pause; a ban
ends it, and so do five minutes without getting back on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:31:41 +00:00
c8e99c8946 Show the microphone level for tuning voice activation
The settings show the level on TeamSpeak's scale, lit while it would be
sent, with the volume gate's threshold marked as the slider moves. While
connected it listens to the connection's own microphone, so it shows
exactly what goes out; otherwise it runs a test microphone of its own for
as long as the settings are on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:31:31 +00:00
62ced89701 Pick the Android audio devices, microphone preset and playback usage
Microphone and speaker are chosen separately, so a Bluetooth headset can
play in media quality while the phone's own microphone listens, instead of
the headset dropping into its call profile. Devices are kept by type and
address: Android hands out new ids whenever one reconnects, and a line that
lost its device comes back to it when it returns.

The microphone preset (voice recognition by default, or the platform's
call processing, unprocessed, ...) and whether voices play as media or as
a call are Android's own settings. Changing either reopens the open
streams in place. Communication mode, and the headset's call link that a
Bluetooth microphone needs, are held only while a stream needs them, and
are set up before that stream opens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:31:22 +00:00
2281c125fa Keep the error id a server refused the last connect with
The message alone does not tell a ban from a refusal worth retrying.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:30:23 +00:00
25ca586a50 Let a disconnect give up a connect still under way
Disconnecting only acted once connected, so a connect that was stuck or
retrying could not be called off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:30:23 +00:00
ed526897dc Switch audio devices without reopening the connection
A new input device used to wait for the next connect, and the shared mixer
kept its line on the old output until it went idle for good. The microphone
now reopens on the new device as the settings are applied, and the mixer
moves its line over when the output device changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:30:23 +00:00
967fcadf77 Keep chat drafts per conversation and open each at its newest line
Switching conversations dropped the message being written, and showed
the next one at the scroll position of the last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 15:12:50 +00:00
70c6eb769c Say why a connection ended, and mark the channel we are in subscribed
- A failed connect keeps its reason in the status instead of a bare
  "Disconnected", as does a disconnect the server gave a reason for.
- The server never announces the subscription to the channel we start
  out in, although it keeps us subscribed after we leave it, so the
  channel we enter counts as subscribed.
- A client connecting or leaving the server is logged as that, not as
  switching from or to "channel #0": the server gives both the same
  reason, only with no channel on one side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 15:12:50 +00:00
f2c9143997 Pick up ts3j's refused-connect reason
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 15:12:50 +00:00
941365cab0 Give the Android app its real UI
- Connect screen with an address or a bookmark, which can be added and
  removed there.
- Server screen: the channel tree with each client's status, folding
  channels, joining (asking for a password where needed) and
  subscribing from a channel's sheet; private message, poke and volume
  from a client's sheet.
- Chat: the server log, the channel and private conversations with
  their history, unread marks, BBCode and clickable client, channel and
  web links. Chat lines are the HTML the desktop shows, turned into
  styled text.
- Voice bar: mute, deafen, away, and a hold-to-talk button in
  push-to-talk mode. The microphone permission is asked for on connect.
- Settings: nickname, transmission mode, voice activation, noise
  suppression, gain control and playback volume, in effect at once.

The session's state reaches Compose as immutable snapshots: the tree
at most every 30 ms, rebuilt only when channels or clients changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:21:51 +00:00
d83ba1145b Make a parsed spacer a value
So a frontend comparing snapshots of the tree sees an unchanged spacer
as unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:21:51 +00:00
35d6427456 Apply voice settings to a live microphone in one place
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:10:35 +00:00
d206b12a30 Move spacers, client status and chat entries into core
The Android client needs them too: which state a client's icon shows,
how spacer channels are named, and the HTML a chat line is written as.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:09:43 +00:00
c5a49fc7b3 Avoid JDK APIs newer than Android's minSdk 33 in core
Files.readString/writeString (API 36.1), Path.of, ByteBuffer.slice(int, int)
and Matcher.appendReplacement(StringBuilder) (API 34) threw NoSuchMethodError
on Android; connecting crashed in ChatLogStore. Each replacement keeps the
original desktop code in a comment above it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:05:39 +00:00
5d67b0c1e3 Record what came of the codebase audit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:39 +00:00
51f19c2d03 Bring the README's architecture and features up to date
The architecture tree predated the session, chatlog, contacts, text,
file transfer and voice pipeline packages, and named a backend that no
longer exists. The feature list still described the first speech
detector and processing chain rather than the WebRTC ports, and knew
nothing of the jitter buffer; the limitations listed file transfers,
avatars and administration as missing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:25:26 +00:00