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>
This commit is contained in:
2026-09-25 15:12:50 +00:00
parent f2c9143997
commit 70c6eb769c
3 changed files with 30 additions and 8 deletions

View File

@@ -81,6 +81,7 @@ final class ConnectionEventHandler implements TS3Listener {
int from = c.channelId; int from = c.channelId;
c.channelId = e.getTargetChannelId(); c.channelId = e.getTargetChannelId();
if (e.getClientId() == conn.getSelfClientId()) { if (e.getClientId() == conn.getSelfClientId()) {
conn.getModel().enteredChannel(e.getTargetChannelId());
announceOwnMove(safeInt(e, "reasonid"), e); announceOwnMove(safeInt(e, "reasonid"), e);
logOwnMove(e, from, e.getTargetChannelId()); logOwnMove(e, from, e.getTargetChannelId());
} else if (!c.isQuery()) { } else if (!c.isQuery()) {
@@ -95,7 +96,10 @@ final class ConnectionEventHandler implements TS3Listener {
private void logClientEntered(ClientJoinEvent e) { private void logClientEntered(ClientJoinEvent e) {
String name = conn.clientLink(e.getClientId(), String name = conn.clientLink(e.getClientId(),
TeamspeakConnection.orEmpty(e.getUniqueClientIdentifier()), e.getClientNickname()); TeamspeakConnection.orEmpty(e.getUniqueClientIdentifier()), e.getClientNickname());
switch (safeInt(e, "reasonid")) { // A client connecting also comes with "switched", only from no channel at all.
int reason = safeInt(e, "reasonid");
if (reason == REASON_SWITCHED && e.getClientFromId() == 0) reason = -1;
switch (reason) {
case REASON_MOVED: case REASON_MOVED:
conn.log(name + " appears, coming from channel \"" + conn.channelLink(e.getClientFromId()) + "\""); conn.log(name + " appears, coming from channel \"" + conn.channelLink(e.getClientFromId()) + "\"");
break; break;
@@ -116,7 +120,10 @@ final class ConnectionEventHandler implements TS3Listener {
private void logClientLeft(ClientLeaveEvent e, String name) { private void logClientLeft(ClientLeaveEvent e, String name) {
String reasonMsg = TeamspeakConnection.orEmpty(e.get("reasonmsg")); String reasonMsg = TeamspeakConnection.orEmpty(e.get("reasonmsg"));
String suffix = reasonMsg.isEmpty() ? "" : " (" + reasonMsg + ")"; String suffix = reasonMsg.isEmpty() ? "" : " (" + reasonMsg + ")";
switch (safeInt(e, "reasonid")) { int reason = safeInt(e, "reasonid");
// A client leaving the server may also come with "switched", only to no channel at all.
if (reason == REASON_SWITCHED && e.getClientTargetId() == 0) reason = -1;
switch (reason) {
case REASON_TIMEOUT: case REASON_TIMEOUT:
conn.log(name + " dropped (ping timeout)"); conn.log(name + " dropped (ping timeout)");
break; break;

View File

@@ -229,6 +229,16 @@ public final class ServerModel {
return channels.get(id); return channels.get(id);
} }
/**
* Marks the channel we are in as subscribed. The server tells us about it along with
* everything else there, but never announces the subscription for the channel we
* start out in, although it stays subscribed after we leave it.
*/
public synchronized void enteredChannel(int id) {
ChannelNode channel = channels.get(id);
if (channel != null) channel.subscribed = true;
}
public synchronized void removeChannel(int id) { public synchronized void removeChannel(int id) {
ChannelNode c = channels.remove(id); ChannelNode c = channels.remove(id);
if (c == null) return; if (c == null) return;

View File

@@ -332,7 +332,10 @@ public final class TeamspeakConnection implements TS3Listener {
// handshake carries, i.e. before our id was known here, so it has to be // handshake carries, i.e. before our id was known here, so it has to be
// marked once it is. // marked once it is.
ClientEntry ourselves = model.getClient(selfClientId); ClientEntry ourselves = model.getClient(selfClientId);
if (ourselves != null) ourselves.self = true; if (ourselves != null) {
ourselves.self = true;
model.enteredChannel(ourselves.channelId);
}
fileTransfers = new FileTransferManager(client, () -> serverHost); fileTransfers = new FileTransferManager(client, () -> serverHost);
client.setMicrophone(microphone); client.setMicrophone(microphone);
icons.retryFailed(); icons.retryFailed();
@@ -360,12 +363,13 @@ public final class TeamspeakConnection implements TS3Listener {
// connect has set up in the meantime. // connect has set up in the meantime.
if (connectAttempt != attempt) return; if (connectAttempt != attempt) return;
connected = false; connected = false;
ui.onError("Connection failed: " + rootMessage(e) + versionHint(e)); String failure = "Connection failed: " + rootMessage(e) + versionHint(e);
ui.onError(failure);
safeCleanup(); safeCleanup();
// Report the failed attempt as a disconnect too, so the UI leaves the // Report the failed attempt as a disconnect too, so the UI leaves the
// "connecting" state and the tab becomes reusable. // "connecting" state and the tab becomes reusable. The status keeps saying why.
ui.onDisconnected("connection failed"); ui.onDisconnected("connection failed");
ui.onStatus("Disconnected"); ui.onStatus(failure);
} }
} }
@@ -1370,8 +1374,9 @@ public final class TeamspeakConnection implements TS3Listener {
connected = false; connected = false;
safeCleanup(); safeCleanup();
announceDisconnect(false); announceDisconnect(false);
ui.onDisconnected(orEmpty(e.getReasonMessage())); String reason = orEmpty(e.getReasonMessage());
ui.onStatus("Disconnected"); ui.onDisconnected(reason);
ui.onStatus(reason.isEmpty() ? "Disconnected" : "Disconnected: " + reason);
} }
// ---- sound notifications ---- // ---- sound notifications ----