From cb05b097a6f312d5f412ff48ebce1537748f7e5f Mon Sep 17 00:00:00 2001 From: ericek111 Date: Wed, 19 Aug 2026 18:04:35 +0000 Subject: [PATCH] Add "Change Description" to the client menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../ts3client/net/ConnectionEventHandler.java | 4 ++ .../ts3client/net/TeamspeakConnection.java | 39 +++++++++++++++++++ .../java/com/ts3client/ui/ClientMenu.java | 3 ++ .../ts3client/ui/ServerTabTreeActions.java | 27 +++++++++++++ .../com/ts3client/ui/ServerTreePanel.java | 3 ++ 5 files changed, 76 insertions(+) diff --git a/ts3-client/core/src/main/java/com/ts3client/net/ConnectionEventHandler.java b/ts3-client/core/src/main/java/com/ts3client/net/ConnectionEventHandler.java index 5cf5787..cb3a023 100644 --- a/ts3-client/core/src/main/java/com/ts3client/net/ConnectionEventHandler.java +++ b/ts3-client/core/src/main/java/com/ts3client/net/ConnectionEventHandler.java @@ -292,6 +292,10 @@ final class ConnectionEventHandler implements TS3Listener { c.awayMessage = e.get("client_away_message"); } if (has(e, "client_talk_power")) c.talkPower = e.getInt("client_talk_power"); + if (has(e, "client_description")) { + c.description = e.get("client_description"); + conn.ui.onInfoUpdated(); + } if (has(e, "client_is_channel_commander")) c.channelCommander = e.getBoolean("client_is_channel_commander"); announceClientUpdate(e, c, renamed, oldName); diff --git a/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java b/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java index 32f5702..edfb775 100644 --- a/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java +++ b/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java @@ -887,6 +887,45 @@ public final class TeamspeakConnection implements TS3Listener { }, "ts3j-clientinfo").start(); } + /** + * Reads a client's description straight from the server, so an editor opens on the + * live text instead of whatever an earlier info request cached. + */ + public void requestClientDescription(int clientId, BiConsumer callback) { + run("ts3j-client-description", callback, () -> { + Client c = client.getClientInfo(clientId); + String description = c == null ? "" : orEmpty(c.get("client_description")); + ClientEntry e = model.getClient(clientId); + if (e != null) e.description = description; + return description; + }); + } + + /** + * Changes a client's description. Unlike the nickname or the away state, this is not + * a {@code clientupdate} property even for ourselves: the server only takes it + * through {@code clientedit}, gated by {@code b_client_modify_own_description}. + * + * @param callback given {@code null} on success, or the failure message + */ + public void setClientDescription(int clientId, String description, Consumer callback) { + boolean self = clientId == selfClientId; + run("ts3j-client-description-set", (ignored, error) -> callback.accept(error), () -> { + SingleCommand cmd = new SingleCommand("clientedit", ProtocolRole.CLIENT); + cmd.add(new CommandSingleParameter("clid", Integer.toString(clientId))); + cmd.add(new CommandSingleParameter("client_description", description)); + client.executeCommand(cmd).complete(); + // Only the other clients are told about our own update, so the local entry + // (and the info panel showing it) has to be caught up here. + if (self) { + ClientEntry e = model.getClient(clientId); + if (e != null) e.description = description; + ui.onInfoUpdated(); + } + return null; + }); + } + // ---- channel administration ---- /** diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ClientMenu.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ClientMenu.java index db660aa..0763f7e 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/ClientMenu.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ClientMenu.java @@ -65,6 +65,9 @@ final class ClientMenu { findInTree.addActionListener(a -> actions.findClientInTree(client)); menu.add(findInTree); } + JMenuItem description = new JMenuItem("Change Description", Icons.of("EDIT")); + description.addActionListener(a -> actions.changeClientDescription(client)); + menu.add(description); JMenuItem info = new JMenuItem("Connection Info", Icons.of("INFO")); info.addActionListener(a -> actions.showConnectionInfo(client)); menu.add(info); diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabTreeActions.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabTreeActions.java index d510abc..bb6464a 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabTreeActions.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabTreeActions.java @@ -8,6 +8,7 @@ import com.ts3client.net.TeamspeakConnection; import com.ts3client.text.TsLink; import javax.swing.JOptionPane; +import javax.swing.SwingUtilities; import java.awt.Component; /** @@ -201,6 +202,32 @@ final class ServerTabTreeActions implements ServerTreePanel.Actions { groupsDialog = null; } + /** + * Reads the description from the server before prompting, since the model only + * carries one for clients whose info has been looked at. + */ + @Override + public void changeClientDescription(ClientEntry client) { + if (!conn.isConnected()) return; + conn.requestClientDescription(client.id, (description, error) -> SwingUtilities.invokeLater(() -> { + if (error != null) { + JOptionPane.showMessageDialog(host, "Could not read the description: " + error, + "Error", JOptionPane.ERROR_MESSAGE); + return; + } + String edited = (String) JOptionPane.showInputDialog(host, + "Description for " + client.nickname + ":", "Change Description", + JOptionPane.PLAIN_MESSAGE, Icons.of("EDIT"), null, description); + if (edited == null || edited.equals(description)) return; + conn.setClientDescription(client.id, edited, failure -> SwingUtilities.invokeLater(() -> { + if (failure != null) { + JOptionPane.showMessageDialog(host, "Could not change the description: " + failure, + "Error", JOptionPane.ERROR_MESSAGE); + } + })); + })); + } + @Override public void setChannelSubscribed(ChannelNode channel, boolean family, boolean subscribed) { if (conn.isConnected()) conn.setChannelsSubscribed(channel.familyIds(family), subscribed); diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java index ceaad23..0f87dad 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java @@ -99,6 +99,9 @@ public final class ServerTreePanel extends JScrollPane { /** Opens the full server-groups list dialog for a client. */ void showServerGroupsDialog(ClientEntry client); + + /** Edits a client's description — our own included. */ + void changeClientDescription(ClientEntry client); } private final DropIndicatorTree tree;