Commit Graph

30 Commits

Author SHA1 Message Date
7e4b9671fe Global hotkeys, in TeamSpeak's own shape
Bindings are captured system-wide rather than only while the window has
focus: X11's RECORD extension where the X server sees every key, and a
/dev/input reader as the Wayland fallback. Any key can act as a modifier,
mouse buttons included, as TS3 allows.

The action catalogue, its three categories and the "advanced actions"
split are reverse-engineered from the original client; actions this
client cannot perform are listed but greyed out. Push-to-talk becomes one
of these hotkeys, so the old focus-bound pushToTalkKey setting is gone and
the Voice Activation button edits that binding instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-16 17:08:28 +00:00
3e2c06248f Make the tray menu close, and its window come forward
Swing never grabs the pointer for a popup, so a click on the desktop or
another application is not seen and the menu stays up. The little window
the menu hangs off is focusable now and closes it when the focus goes
elsewhere, Escape closes it, and clicking the icon again toggles it.

Raising the window was ignored for the same reason it usually is: a
window manager refuses a raise from an application that does not have the
focus. The icon asks for _NET_ACTIVE_WINDOW instead — the request meant
for pagers and trays acting for the user — and falls back to Swing's
always-on-top shuffle when it cannot find the window. Windows are matched
by _NET_WM_PID and _NET_WM_NAME, since WM_NAME cannot carry the em dash
in our title.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 22:58:24 +00:00
32e5ffb576 Show the local client's state in the system tray
The icon carries the badge the tree draws next to our own nickname on the
active server — idle, talking, away, commander, microphone or speakers
muted — and clicking it brings the window back to the front. Right click
offers "Show TS3J" and "Quit".

AWT's tray icon cannot be transparent on X11: the toolkit embeds a window
of the screen's default, opaque visual and fills it with a background
colour before drawing the image, so every icon sits in a box. Panels that
can show transparent icons advertise an ARGB visual instead, so the icon
is docked by hand over the system tray protocol, in that visual, with the
image put on the window premultiplied. AWT's own icon stays as the
fallback for everything else.

Two details a panel will not forgive: an icon that publishes no size
hints is allocated a one-pixel sliver, and docking is a request, so an
icon that is never adopted has to hand over to the fallback rather than
sit invisible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 22:45:32 +00:00
ef95b6305d Report a chosen client version and operating system
Other clients see the version and platform from clientinit in the info
panel, and until now that was whatever ts3j sends. The new Client Version
tab picks one instead.

Servers refuse a version they do not know with "client is modified", so
free text is not enough: the list holds genuine version/platform/signature
triples from released clients, newest first per platform. The fields stay
editable for a version whose signature is known from elsewhere, and a
refused connection now says where to change it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 20:06:09 +00:00
97148cfe9a Move a client into your own channel from its context menu
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 21:30:12 +02:00
6dee5106c7 Open text chat by double-clicking a client
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 14:48:54 +00:00
9bb1ec7060 Open connection info by middle-clicking a client
The dialog now disposes when its window button is used, too: hiding it
left the one-second refresh timer polling the server for a window
nobody could see again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 14:39:19 +00:00
69003ced9a Close the client's dialogs with Escape
Only the option-pane prompts closed on Escape, because Swing installs
that binding on the option pane itself. Dialogs.closeOnEscape puts the
same window-wide binding on a dialog's root pane, routed to whatever
that dialog does when its Close or Cancel button is pressed — reverting
the live-previewing settings, or flushing the bookmark being edited.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 14:36:17 +00:00
0f8de796ea Kick and ban clients from the context menu
The client menu gains "Kick Client from Channel", "Kick Client from
Server" and "Ban Client", each asking for a reason first. Kicks use a
shared prompt that enforces the protocol's 40-character limit; the ban
dialog adds a duration, whose unit dropdown ends in "Permanent" — the
zero-length ban the server understands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 14:29:21 +00:00
d37c6c9ba9 Name the server node and stop it collapsing
The tree's top row showed the placeholder name, since nothing ever read the
real one; take it from initserver, which ts3j now keeps.

There is only ever one top-level row, so its expand control bought nothing and
cost horizontal space in a narrow view. Drop it, ignore collapse requests on
the root, and tighten the per-level indent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 14:15:32 +00:00
46206e3047 Show and control channel subscriptions
Track the subscription state from the notifychannelsubscribed events, draw
subscribed and unsubscribed channels apart with the pack's icons (and the
built-in glyph as a hollow cone), and offer subscribe/unsubscribe entries —
channel and family — in the channel context menu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:57:25 +00:00
245ae5ecc5 Add an away button with global actions and message presets
The toolbar gains an away toggle with a drop-down: it toggles away on the
current server with no message, while the menu carries the global actions
("Set Globally Away", "Set Globally Away Status"), the saved presets and
their editor. Presets live in ~/.ts3jclient/away.properties and are
managed in a list with add/remove and double-click renaming.

Clients in the channel tree now show their away message in brackets after
the nickname instead of their primary server group.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 13:40:19 +00:00
676e95023e Draw the interface with TeamSpeak icon packs
Icon packs are read in TeamSpeak's own format — a zip (or unpacked folder)
of SVG/PNG art plus a settings.ini mapping icon keys to files — so the packs
of an installed client and the ones from its add-on site work unchanged.
Legacy packs without that mapping (default.zip) are resolved by their file
naming convention instead, picking the resolution closest to the drawn size.

The pack draws the tree, toolbar, menus, context menus, file browser and the
default group icons; a pack's FALLBACK option decides whether what it lacks
comes from default.zip, and anything still missing falls back to the icons
the client draws itself. Options → Design picks the pack and shows a viewer
of everything in it.

Vector art is rasterised with JSVG, and the pack search roots are shared
with the sound packs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 12:46:39 +00:00
2d7b82f9a3 Play sound-pack notifications for client actions
Adds TeamSpeak-format sound packs: a folder of waves plus a settings.ini
mapping actions to play()/say() entries, with ${clientType} and friends
resolved per event. Packs are found in the client's own sound folder, an
installed TS3 client and a folder of the user's choosing, so the official
packs work unchanged.

Each action can be switched off or marked important; important actions are
the only ones still played while the speakers are muted, as in TS3. The new
Notifications options page lists them by category, greys out what the active
pack has no sound for, and previews on double-click.

Sounds are decoded, resampled and mixed onto a single playback line that is
only open while something plays, so overlapping events never fight over the
device.

Fires the events from the protocol layer, following TeamSpeak's own
distinctions: reason ids separate switched/moved/kicked/banned/timed out,
and visibility decides appears/disappears/stays.

Also fixes a ts3j trap in the process: a field an event never carried reads
back as an empty string, so the existing "e.get(x) != null" checks were
always true. That made a partial clientupdate (someone muting) announce a
stopped recording, and it let a nickname-only update reset another client's
mute/away flags and talk power, or a channel edit blank the channel name.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 11:53:11 +00:00
f334153e63 Repaint the whole tree viewport while scrolling
The right-aligned icon strip is positioned against the viewport rather than
the row, so the pixels a blit scroll reuses are stale and the icons leave
ghosts behind. Scroll the viewport in simple mode, and widen every repaint
request to the full visible width so a row whose label changes size cannot
leave its icons behind either.

Also stop blacklisting an icon whose download failed only because the file
transfer channel was not up yet: channel rows are painted during connect,
well before that point, so their icons would never be retried.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 07:28:09 +00:00
610dce1bd9 Show each channel's own icon in the tree
Channels carry a channel_icon_id just like groups do; track it on
ChannelNode from the channel list and the create/edit events, and paint it
in the same right-aligned strip that already carries a client's group
icons. Spacers keep their bare look.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 07:09:21 +00:00
7c86a1f166 Show server and channel group icons in the tree
Download the icons a server advertises for its groups over the file
transfer channel, cache them under the config directory, and draw them as
a badge strip on each client's row, right-aligned against the tree's
visible edge like the TeamSpeak client does. Groups without a custom icon
fall back to the bundled default set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 07:06:23 +00:00
0333b92ce5 Add a PipeWire audio backend and stereo capture/playback
Capture and playback now go through AudioCapture/AudioPlayback interfaces
with two implementations: PipeWire, used for the system default and for
every pw: device when a session is reachable, and Java Sound for the raw
ALSA devices and other platforms. The PipeWire binding is FFM-based
(PipeWireLibrary, PipeWireSession, PipeWireStream, SpaPod) and enumerates
the graph's real devices, so each stream shows up separately in volume
mixers.

Lines are opened with a negotiated channel count instead of a fixed mono
format: OPUS_MUSIC transmits the stereo capture as-is, OPUS_VOICE keeps
transmitting the mono downmix so the pre-processing chain and VAD still
see a single channel. Java Sound enumeration asks only for "a mixer that
can capture/play" so the PipeWire and PulseAudio ALSA plugins are no
longer hidden by their narrow format lists.

The Java Sound classes are renamed to Desktop* to match what they now
are, and JUnit plus a SpaPod round-trip test are wired into the build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 05:30:46 +00:00
d5018729d8 Slim the shaded jar from 7.3 MB down to 2.0 MB
Three things bloated it. Nothing tree-shook the dependencies, so all of
bcprov shipped even though only its lightweight API (AES-EAX, ECDSA over
prime256v1, ASN.1, EC point math) is ever touched. Its multi-release
copies under META-INF/versions accounted for another 2.3 MB that could
never even load, since the shaded jar is not itself multi-release.
And ts3j dragged in commons-lang and dnsjava for one call each.

Enable minimizeJar, keeping our own modules and ts3j whole (they are the
entry points, and ts3j instantiates packet bodies reflectively), and drop
the versioned entries. The shade plugin has to move to 3.6.2 for this:
3.5.1 bundles an ASM that cannot read class file major version 70.

The remaining large entry is the bundled Windows Opus DLL, at 612 KB.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 23:47:03 +00:00
022aef633b Drag and drop clients and channels in the server tree
Clients can be dragged into a channel and channels re-parented or
reordered, with an insertion line (or a row outline, for "into this
channel") showing where the drop lands.

Channel positions come from channel_order, which is the id of the
channel above rather than an index, so siblings are now ordered by
walking that chain and the neighbour links are repaired locally on
create, move and delete -- the server only announces the channel
that changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 17:42:49 +00:00
1249242c89 Switch server tabs with the scroll wheel
Wheel events on the tab strip step through the open connections,
clamped at the ends. The content area is excluded so the tree and chat
keep their own scrolling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 15:28:20 +00:00
41bf9506e3 Use a real tab strip for servers instead of a button bar
ServerTabPane replaces ServerTabBar: the views live in a JTabbedPane
whose strip sits under the toolbar, with the native tab shapes and the
same look as the chat tabs. Each tab carries its server name, the
microphone marker and a close glyph. With a single connection the
server view is mounted directly, so the strip appears only once a
second server is open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 15:23:25 +00:00
03c6ea9a3b Tabbed servers: multiple simultaneous connections
Each connection now lives in its own ServerTab with its own tree, info
and chat views plus its own mic-mute, deafen, away and commander state.
MainFrame keeps only the shell (menus, toolbar, status bar) and shows
one tab at a time; the tab bar below the toolbar appears once a second
server is open.

Incoming voice from every connection mixes into the same output — each
connection already renders one line per speaker. Capture is exclusive:
a connection only opens the microphone while it holds it, and a toolbar
button moves it to the visible tab. The mic follows a disconnect to
another connected tab, and push-to-talk drives whoever holds it.

Startup bookmarks are no longer mutually exclusive: every flagged
bookmark opens its own tab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 15:18:47 +00:00
5db3f4d8c4 Two-column bookmark manager with connect-on-startup
Replace the modal add/edit form with a master/detail dialog: bookmark
list on the left, the selected bookmark's settings on the right. Edits
are flushed back into the bookmark when the selection changes or the
dialog closes.

Bookmarks gain a "connect on startup" flag (only one at a time, since
the client holds a single connection); MainFrame connects to it once
the window is up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 15:02:13 +00:00
707d9ebbba Tabbed chats with BBCode rendering and clickable identities
Replace the chat target dropdown with a tab strip along the bottom of the
chat panel: a Server and a Channel tab plus one closable tab per private
chat, each with its own log and an unread marker.

Render logs and channel/client descriptions as HTML through a new BBCode
renderer in core, supporting b/i/u/s, color, size, url, center and lists,
with auto-linked bare URLs. Input is escaped and tag values validated, so
server-supplied text cannot inject markup or unsafe schemes.

Sender names and TeamSpeak's own client:// links open the same context menu
as the server tree (menus extracted into ClientMenu/ChannelMenu); channel://
links open the channel menu. Links are styled by CSS class: identities bold
in the author colour, other TS protocols plain, and links leaving the client
underlined so they cannot be mistaken for an identity.

Dragging a client or channel out of the tree yields the TS3 link BBCode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:50:04 +00:00
a69b7bde74 Add default channel to bookmarks
Bookmarks can store a channel to join on connect, given either as a
"/"-separated path or a channel id, plus an optional channel password.
"Add bookmark" offers to remember the channel you are currently in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:49:54 +00:00
17ad6cde1c Add identity management
Manage several TeamSpeak identities like the official client instead of
using one auto-generated identity file.

- IdentityStore/IdentityEntry keep each identity as a TeamSpeak-format INI
  in ~/.ts3jclient/identities, so files interchange with the TS3 client;
  supports create, import, export, rename, remove and a cancellable
  security-level search. A legacy identity.ini is migrated in on first load.
- Settings hold the default identity; bookmarks may pin their own, falling
  back to the default when unset.
- TeamspeakConnection now connects with the identity it is handed.
- Swing: Tools -> Identities manager, plus an identity drop-down in the
  connect and bookmark forms.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 07:42:08 +00:00
f912cab07b Implement channel file transfers
Add file up/downloads and directory operations for channel file
repositories, reachable from a channel's right-click "Browse files".

Core (com.ts3client.net.filetransfer):
- FileTransferManager drives the TS3 file protocol on the ts3j socket.
  ftinitupload/ftinitdownload results arrive out-of-band as
  notifystartupload/notifystartdownload (success) or
  notifystatusfiletransfer (failure) events, not as command responses,
  so negotiation correlates them by clientftfid via a CompletableFuture.
  The byte phase opens a raw TCP socket, sends the ftkey, then streams.
- FileTransfer: observable per-transfer handle (state, progress, cancel).
- RemoteFile: UI-facing listing DTO, keeping core free of ts3j API types.

TeamspeakConnection exposes list/mkdir/delete/rename and up/download and
manages the manager's lifecycle. Swing FileBrowserDialog provides the
browser UI with a live per-transfer progress strip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-13 07:22:33 +00:00
80467f5bc0 Merge META-INF/services in shaded uber-JAR
Add ServicesResourceTransformer so overlapping service-provider files
(e.g. java.security.Provider) are concatenated rather than clobbered,
future-proofing the single executable JAR build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 22:24:46 +00:00
0f76258a05 Initial commit: TS3J TeamSpeak 3 Java client
Swing desktop client (core/desktop/swing Maven modules) built on the
ts3j protocol library, included as a submodule. Native Opus voice with
voice-activation detection, push-to-talk, and audio pre-processing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 22:01:17 +00:00