Removes leftover debug scaffolding (stray Console.WriteLine/NSLog traces, dead nick_buf_ptr, a no-op --print-config flag now implemented for real), fixes stale/misleading comments (channel passwords are no longer a "future M5+" feature, a wrong cross-reference, a stale TlsContext::close() mention, an incomplete BanRecord::subject_type doc, and a smoke test pointing at a build/m1-dev preset that no longer exists), strips internal M1-M5 milestone jargon from comments now that the roadmap is done, trims comments that just restated the following line, and consolidates a few "why" explanations that were duplicated 2-3 times in the same file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
263 lines
9.0 KiB
C#
263 lines
9.0 KiB
C#
using VoiceCat.App.Models;
|
|
using VoiceCat.Interop;
|
|
|
|
namespace VoiceCat.App.Forms;
|
|
|
|
/// <summary>
|
|
/// Saved-server list + connect flow. On success, ConnectedClient/SelfUserId/Nickname are set
|
|
/// and DialogResult == OK; the caller (Program.cs) takes ownership of ConnectedClient (does
|
|
/// NOT dispose it here — MainForm owns its lifetime from that point on).
|
|
/// </summary>
|
|
public partial class ConnectDialog : Form
|
|
{
|
|
private readonly List<SavedServer> _servers;
|
|
private readonly System.Windows.Forms.Timer _pumpTimer = new() { Interval = 30 };
|
|
|
|
private VoiceCatClient? _client;
|
|
private bool _identityDialogShown;
|
|
|
|
public VoiceCatClient? ConnectedClient { get; private set; }
|
|
public uint SelfUserId { get; private set; }
|
|
public string Nickname { get; private set; } = "";
|
|
public string ServerName { get; private set; } = "";
|
|
|
|
public ConnectDialog()
|
|
{
|
|
InitializeComponent();
|
|
_servers = ServerListStore.Load();
|
|
RefreshServerList();
|
|
|
|
_pumpTimer.Tick += (_, _) =>
|
|
{
|
|
try { _client?.PumpEvents(); }
|
|
catch (Exception ex) { Console.Error.WriteLine($"[ConnectDialog] EXCEPTION in PumpEvents/event handler: {ex}"); }
|
|
};
|
|
|
|
btnAddNew.Click += BtnAddNew_Click;
|
|
btnEdit.Click += BtnEdit_Click;
|
|
btnRemove.Click += BtnRemove_Click;
|
|
btnConnect.Click += BtnConnect_Click;
|
|
lstServers.DoubleClick += BtnConnect_Click;
|
|
}
|
|
|
|
private void RefreshServerList()
|
|
{
|
|
object? previouslySelected = lstServers.SelectedItem;
|
|
lstServers.Items.Clear();
|
|
foreach (var s in _servers) lstServers.Items.Add(s);
|
|
if (previouslySelected is not null && _servers.Contains(previouslySelected))
|
|
lstServers.SelectedItem = previouslySelected;
|
|
else if (lstServers.Items.Count > 0)
|
|
lstServers.SelectedIndex = 0;
|
|
UpdateButtonsEnabled();
|
|
}
|
|
|
|
private void UpdateButtonsEnabled()
|
|
{
|
|
bool hasSelection = lstServers.SelectedItem is not null;
|
|
btnConnect.Enabled = hasSelection;
|
|
btnEdit.Enabled = hasSelection;
|
|
btnRemove.Enabled = hasSelection;
|
|
}
|
|
|
|
private void BtnAddNew_Click(object? sender, EventArgs e)
|
|
{
|
|
using var dlg = new AddServerDialog();
|
|
if (dlg.ShowDialog(this) != DialogResult.OK || dlg.Result is null) return;
|
|
_servers.Add(dlg.Result);
|
|
ServerListStore.Save(_servers);
|
|
RefreshServerList();
|
|
lstServers.SelectedItem = dlg.Result;
|
|
}
|
|
|
|
private void BtnEdit_Click(object? sender, EventArgs e)
|
|
{
|
|
if (lstServers.SelectedItem is not SavedServer existing) return;
|
|
using var dlg = new AddServerDialog(existing);
|
|
if (dlg.ShowDialog(this) != DialogResult.OK) return;
|
|
ServerListStore.Save(_servers);
|
|
RefreshServerList();
|
|
}
|
|
|
|
private void BtnRemove_Click(object? sender, EventArgs e)
|
|
{
|
|
if (lstServers.SelectedItem is not SavedServer existing) return;
|
|
var confirm = MessageBox.Show(this, $"Remove '{existing}' from the saved-server list?",
|
|
"VoiceCat", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
|
if (confirm != DialogResult.Yes) return;
|
|
_servers.Remove(existing);
|
|
ServerListStore.Save(_servers);
|
|
RefreshServerList();
|
|
}
|
|
|
|
private void BtnConnect_Click(object? sender, EventArgs e)
|
|
{
|
|
if (lstServers.SelectedItem is not SavedServer server)
|
|
return;
|
|
try
|
|
{
|
|
StartConnect(server);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.Error.WriteLine($"[ConnectDialog] EXCEPTION in StartConnect: {ex}");
|
|
lblStatus.Text = $"Internal error: {ex.Message}";
|
|
CleanupFailedAttempt();
|
|
}
|
|
}
|
|
|
|
private void StartConnect(SavedServer server)
|
|
{
|
|
SetBusy(true);
|
|
lblStatus.Text = "Connecting...";
|
|
|
|
ServerName = string.IsNullOrWhiteSpace(server.DisplayName)
|
|
? $"{server.Host}:{server.Port}"
|
|
: server.DisplayName;
|
|
|
|
string tofuDir = Path.GetDirectoryName(ServerListStore.TofuStorePath)!;
|
|
Directory.CreateDirectory(tofuDir);
|
|
|
|
_client = new VoiceCatClient("VoiceCat-Windows", VoiceCatClient.VersionString,
|
|
VcLogLevel.Info, ServerListStore.TofuStorePath);
|
|
_client.EventReceived += OnEvent;
|
|
_identityDialogShown = false;
|
|
_pumpTimer.Start();
|
|
|
|
var connectResult = _client.Connect(server.Host, server.Port);
|
|
if (connectResult != VcResult.Ok)
|
|
{
|
|
lblStatus.Text = $"Connect failed: {connectResult}";
|
|
CleanupFailedAttempt();
|
|
return;
|
|
}
|
|
|
|
if (server.AuthMode == AuthMode.Guest)
|
|
{
|
|
Nickname = string.IsNullOrWhiteSpace(server.LastNickname) ? Environment.UserName : server.LastNickname;
|
|
_client.AuthenticateGuest(Nickname);
|
|
}
|
|
else
|
|
{
|
|
string password;
|
|
if (server.ProtectedPasswordBase64 is not null)
|
|
{
|
|
password = PasswordProtector.Unprotect(server.ProtectedPasswordBase64);
|
|
}
|
|
else
|
|
{
|
|
using var pwDlg = new PasswordPromptDialog($"Password for {server.SavedUsername}@{server.Host}:");
|
|
if (pwDlg.ShowDialog(this) != DialogResult.OK)
|
|
{
|
|
lblStatus.Text = "Cancelled.";
|
|
CleanupFailedAttempt();
|
|
return;
|
|
}
|
|
password = pwDlg.Password;
|
|
}
|
|
Nickname = server.SavedUsername ?? "";
|
|
_client.AuthenticateUser(server.SavedUsername ?? "", password);
|
|
}
|
|
}
|
|
|
|
private void OnEvent(VoiceCatEvent ev)
|
|
{
|
|
switch (ev.Type)
|
|
{
|
|
case VcEventType.ConnectionState:
|
|
lblStatus.Text = ev.ConnectionState switch
|
|
{
|
|
VcConnectionState.Connecting => "Connecting...",
|
|
VcConnectionState.TlsHandshake => "TLS handshake...",
|
|
VcConnectionState.VerifyingIdentity => "Verifying server identity...",
|
|
VcConnectionState.Authenticating => "Authenticating...",
|
|
VcConnectionState.Connected => "Connected.",
|
|
_ => lblStatus.Text,
|
|
};
|
|
break;
|
|
|
|
case VcEventType.ServerIdentity:
|
|
HandleServerIdentity((VcTofuStatus)ev.U32a, ev.Text ?? "");
|
|
break;
|
|
|
|
case VcEventType.AuthResult:
|
|
if (ev.Result == VcResult.Ok)
|
|
{
|
|
SelfUserId = ev.UserId;
|
|
ConnectedClient = _client;
|
|
_pumpTimer.Stop();
|
|
_client!.EventReceived -= OnEvent;
|
|
DialogResult = DialogResult.OK;
|
|
Close();
|
|
}
|
|
else
|
|
{
|
|
lblStatus.Text = $"Authentication failed: {ev.Text}";
|
|
CleanupFailedAttempt();
|
|
}
|
|
break;
|
|
|
|
case VcEventType.Disconnected:
|
|
if (ConnectedClient is null)
|
|
{
|
|
lblStatus.Text = string.IsNullOrEmpty(ev.Text) ? "Disconnected." : $"Disconnected: {ev.Text}";
|
|
CleanupFailedAttempt();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void HandleServerIdentity(VcTofuStatus status, string certFingerprintHex)
|
|
{
|
|
if (_identityDialogShown) return; // one decision per connect attempt
|
|
if (status == VcTofuStatus.Matched)
|
|
{
|
|
// Silent success path — no dialog. See ServerIdentityDialog's doc comment.
|
|
_client!.ConfirmServerIdentity(true);
|
|
return;
|
|
}
|
|
|
|
_identityDialogShown = true;
|
|
using var dlg = new ServerIdentityDialog(status, certFingerprintHex, _client!.GetServerIdentityDisplay());
|
|
bool accept = dlg.ShowDialog(this) == DialogResult.OK;
|
|
_client.ConfirmServerIdentity(accept);
|
|
if (!accept) lblStatus.Text = "Server identity rejected.";
|
|
}
|
|
|
|
private void CleanupFailedAttempt()
|
|
{
|
|
_pumpTimer.Stop();
|
|
if (_client is not null)
|
|
{
|
|
_client.EventReceived -= OnEvent;
|
|
_client.Dispose();
|
|
_client = null;
|
|
}
|
|
SetBusy(false);
|
|
}
|
|
|
|
private void SetBusy(bool busy)
|
|
{
|
|
lstServers.Enabled = !busy;
|
|
btnConnect.Enabled = !busy && lstServers.SelectedItem is not null;
|
|
btnAddNew.Enabled = !busy;
|
|
btnEdit.Enabled = !busy && lstServers.SelectedItem is not null;
|
|
btnRemove.Enabled = !busy && lstServers.SelectedItem is not null;
|
|
}
|
|
|
|
protected override void OnFormClosing(FormClosingEventArgs e)
|
|
{
|
|
if (DialogResult != DialogResult.OK)
|
|
{
|
|
_pumpTimer.Stop();
|
|
if (_client is not null)
|
|
{
|
|
_client.EventReceived -= OnEvent;
|
|
_client.Dispose();
|
|
_client = null;
|
|
}
|
|
}
|
|
base.OnFormClosing(e);
|
|
}
|
|
}
|