2026-06-17 16:31:29 +02:00
|
|
|
|
using VoiceCat.Interop;
|
|
|
|
|
|
|
|
|
|
|
|
namespace VoiceCat.App.Forms;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Create or edit a channel, including its full per-channel Opus audio config.
|
|
|
|
|
|
/// Returns DialogResult.OK with Result set.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public sealed class ChannelEditDialog : Form
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly bool _isCreate;
|
|
|
|
|
|
private readonly uint _editingId;
|
|
|
|
|
|
private readonly List<ChannelInfo> _channels;
|
|
|
|
|
|
|
|
|
|
|
|
private TextBox _txtName = null!;
|
|
|
|
|
|
private TextBox _txtTopic = null!;
|
|
|
|
|
|
private ComboBox _cboParent = null!;
|
|
|
|
|
|
private NumericUpDown _numMaxUsers = null!;
|
|
|
|
|
|
private NumericUpDown _numSortOrder = null!;
|
|
|
|
|
|
private CheckBox _chkPassword = null!;
|
|
|
|
|
|
private TextBox _txtPassword = null!;
|
|
|
|
|
|
|
|
|
|
|
|
private ComboBox _cboMode = null!;
|
|
|
|
|
|
private NumericUpDown _numSampleRate = null!;
|
|
|
|
|
|
private NumericUpDown _numBitrate = null!;
|
|
|
|
|
|
private NumericUpDown _numFrameMs = null!;
|
|
|
|
|
|
private ComboBox _cboApplication = null!;
|
|
|
|
|
|
private CheckBox _chkFec = null!;
|
|
|
|
|
|
private NumericUpDown _numExpectedLoss = null!;
|
|
|
|
|
|
private CheckBox _chkDtx = null!;
|
feat(clients): expose all channel codec params + guest nickname everywhere
Channel create/edit UIs only surfaced a subset of the core's vc_audio_config,
and DRED was exposed nowhere. While adding it, found a latent ABI mismatch:
both Swift AudioConfig and the C# VcAudioConfigNative blittable struct were one
int short of the native vc_audio_config (missing the trailing `dred`), so native
read past the managed struct in vc_create_channel/vc_edit_channel.
- core marshaling: thread `dred` through Swift (Models/Marshaling/toNative) and
C# (Structs/Models/Marshaling/VoiceCatClient) -- fixes the ABI gap + enables it
- windows: add the one missing DRED checkbox to ChannelEditDialog
- macos: ChannelEditSheet now exposes application, sample rate, packet loss,
complexity, and DRED (was stereo/bitrate/frame/FEC/DTX only)
- ios: rebuild ChannelEditView into a full create+edit form (all params); add
SessionState.editChannel + an admin Edit swipe action (iOS had no edit UI)
- guest nickname: add a dedicated `nickname` to SavedServer on macOS+iOS
(backward-compatible Codable), shown in Guest mode, wired into the guest auth
path -- guests could not set a display name on either before (only Windows)
Verified: macOS + iOS (sim, arm64) xcodebuild BUILD SUCCEEDED; core ctest 22/23
(only external_pcm aborts on a pre-existing shutdown mutex race; no C++ changed).
2026-06-21 04:03:50 +02:00
|
|
|
|
private CheckBox _chkDred = null!;
|
2026-06-17 16:31:29 +02:00
|
|
|
|
private NumericUpDown _numComplexity = null!;
|
|
|
|
|
|
|
|
|
|
|
|
public ChannelEditInfo? Result { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
public ChannelEditDialog(IEnumerable<ChannelInfo> channels, ChannelEditInfo? existing = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
_isCreate = existing is null;
|
|
|
|
|
|
_editingId = existing?.Id ?? 0;
|
|
|
|
|
|
_channels = channels.Where(c => c.Id != _editingId).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
Text = _isCreate ? "Create channel" : "Edit channel";
|
|
|
|
|
|
FormBorderStyle = FormBorderStyle.FixedDialog;
|
|
|
|
|
|
MaximizeBox = false;
|
|
|
|
|
|
MinimizeBox = false;
|
|
|
|
|
|
StartPosition = FormStartPosition.CenterParent;
|
|
|
|
|
|
AutoScaleMode = AutoScaleMode.Font;
|
|
|
|
|
|
ClientSize = new Size(520, 520);
|
|
|
|
|
|
|
|
|
|
|
|
var tabs = new TabControl
|
|
|
|
|
|
{
|
|
|
|
|
|
Dock = DockStyle.Fill,
|
|
|
|
|
|
TabIndex = 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var pageGeneral = new TabPage("General");
|
|
|
|
|
|
BuildGeneralPage(pageGeneral, existing);
|
|
|
|
|
|
var pageAudio = new TabPage("Audio config");
|
|
|
|
|
|
BuildAudioPage(pageAudio, existing?.Audio);
|
|
|
|
|
|
|
|
|
|
|
|
tabs.TabPages.Add(pageGeneral);
|
|
|
|
|
|
tabs.TabPages.Add(pageAudio);
|
|
|
|
|
|
|
|
|
|
|
|
var btnOk = new Button
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = "&OK",
|
|
|
|
|
|
DialogResult = DialogResult.OK,
|
|
|
|
|
|
Location = new Point(350, 486),
|
|
|
|
|
|
Size = new Size(75, 27),
|
|
|
|
|
|
TabIndex = 100,
|
|
|
|
|
|
};
|
|
|
|
|
|
var btnCancel = new Button
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = "&Cancel",
|
|
|
|
|
|
DialogResult = DialogResult.Cancel,
|
|
|
|
|
|
Location = new Point(431, 486),
|
|
|
|
|
|
Size = new Size(75, 27),
|
|
|
|
|
|
TabIndex = 101,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
AcceptButton = btnOk;
|
|
|
|
|
|
CancelButton = btnCancel;
|
|
|
|
|
|
|
|
|
|
|
|
Controls.Add(tabs);
|
|
|
|
|
|
Controls.Add(btnOk);
|
|
|
|
|
|
Controls.Add(btnCancel);
|
|
|
|
|
|
|
|
|
|
|
|
btnOk.Click += BtnOk_Click;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BuildGeneralPage(TabPage page, ChannelEditInfo? existing)
|
|
|
|
|
|
{
|
|
|
|
|
|
int y = 16;
|
|
|
|
|
|
int labelWidth = 110;
|
|
|
|
|
|
int inputX = 128;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "&Name:", 12, y, labelWidth);
|
|
|
|
|
|
_txtName = new TextBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(360, 23),
|
|
|
|
|
|
Text = existing?.Name ?? "",
|
|
|
|
|
|
TabIndex = 1,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_txtName);
|
|
|
|
|
|
y += 36;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "&Topic:", 12, y, labelWidth);
|
|
|
|
|
|
_txtTopic = new TextBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(360, 23),
|
|
|
|
|
|
Text = existing?.Topic ?? "",
|
|
|
|
|
|
TabIndex = 2,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_txtTopic);
|
|
|
|
|
|
y += 36;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "&Parent channel:", 12, y, labelWidth);
|
|
|
|
|
|
_cboParent = new ComboBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(360, 23),
|
|
|
|
|
|
DropDownStyle = ComboBoxStyle.DropDownList,
|
|
|
|
|
|
TabIndex = 3,
|
|
|
|
|
|
};
|
|
|
|
|
|
_cboParent.Items.Add(new ChannelItem("(root)", 0));
|
|
|
|
|
|
foreach (var ch in _channels.OrderBy(c => c.Name))
|
|
|
|
|
|
_cboParent.Items.Add(new ChannelItem(ch.Name, ch.Id));
|
|
|
|
|
|
SelectParent(existing?.ParentId ?? 0);
|
|
|
|
|
|
page.Controls.Add(_cboParent);
|
|
|
|
|
|
y += 36;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "&Max users:", 12, y, labelWidth);
|
|
|
|
|
|
_numMaxUsers = new NumericUpDown
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(120, 23),
|
|
|
|
|
|
Minimum = 0,
|
|
|
|
|
|
Maximum = 10000,
|
|
|
|
|
|
Value = existing?.MaxUsers ?? 0,
|
|
|
|
|
|
TabIndex = 4,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_numMaxUsers);
|
|
|
|
|
|
AddLabel(page, "(0 = unlimited)", inputX + 128, y, 100);
|
|
|
|
|
|
y += 36;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "&Sort order:", 12, y, labelWidth);
|
|
|
|
|
|
_numSortOrder = new NumericUpDown
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(120, 23),
|
|
|
|
|
|
Minimum = 0,
|
|
|
|
|
|
Maximum = uint.MaxValue,
|
|
|
|
|
|
Value = existing?.SortOrder ?? 0,
|
|
|
|
|
|
TabIndex = 5,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_numSortOrder);
|
|
|
|
|
|
y += 36;
|
|
|
|
|
|
|
|
|
|
|
|
_chkPassword = new CheckBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = "&Password protected",
|
|
|
|
|
|
Location = new Point(inputX, y),
|
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Checked = existing?.PasswordProtected ?? false,
|
|
|
|
|
|
TabIndex = 6,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_chkPassword);
|
|
|
|
|
|
y += 28;
|
|
|
|
|
|
|
|
|
|
|
|
_txtPassword = new TextBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y),
|
|
|
|
|
|
Size = new Size(360, 23),
|
|
|
|
|
|
UseSystemPasswordChar = true,
|
|
|
|
|
|
Enabled = _chkPassword.Checked,
|
|
|
|
|
|
Text = existing?.Password ?? "",
|
|
|
|
|
|
TabIndex = 7,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_txtPassword);
|
|
|
|
|
|
_chkPassword.CheckedChanged += (_, _) => _txtPassword.Enabled = _chkPassword.Checked;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BuildAudioPage(TabPage page, AudioConfigInfo? audio)
|
|
|
|
|
|
{
|
feat(clients): expose all channel codec params + guest nickname everywhere
Channel create/edit UIs only surfaced a subset of the core's vc_audio_config,
and DRED was exposed nowhere. While adding it, found a latent ABI mismatch:
both Swift AudioConfig and the C# VcAudioConfigNative blittable struct were one
int short of the native vc_audio_config (missing the trailing `dred`), so native
read past the managed struct in vc_create_channel/vc_edit_channel.
- core marshaling: thread `dred` through Swift (Models/Marshaling/toNative) and
C# (Structs/Models/Marshaling/VoiceCatClient) -- fixes the ABI gap + enables it
- windows: add the one missing DRED checkbox to ChannelEditDialog
- macos: ChannelEditSheet now exposes application, sample rate, packet loss,
complexity, and DRED (was stereo/bitrate/frame/FEC/DTX only)
- ios: rebuild ChannelEditView into a full create+edit form (all params); add
SessionState.editChannel + an admin Edit swipe action (iOS had no edit UI)
- guest nickname: add a dedicated `nickname` to SavedServer on macOS+iOS
(backward-compatible Codable), shown in Guest mode, wired into the guest auth
path -- guests could not set a display name on either before (only Windows)
Verified: macOS + iOS (sim, arm64) xcodebuild BUILD SUCCEEDED; core ctest 22/23
(only external_pcm aborts on a pre-existing shutdown mutex race; no C++ changed).
2026-06-21 04:03:50 +02:00
|
|
|
|
audio ??= new AudioConfigInfo(0, false, 48000, 0, 20, 0, true, 0, false, 10, false);
|
2026-06-17 16:31:29 +02:00
|
|
|
|
|
|
|
|
|
|
int y = 16;
|
|
|
|
|
|
int labelWidth = 150;
|
|
|
|
|
|
int inputX = 168;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "Codec (0 = Opus):", 12, y, labelWidth);
|
|
|
|
|
|
var numCodec = new NumericUpDown
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(120, 23),
|
|
|
|
|
|
Minimum = 0,
|
|
|
|
|
|
Maximum = 0,
|
|
|
|
|
|
Value = audio.Codec,
|
|
|
|
|
|
Enabled = false,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(numCodec);
|
|
|
|
|
|
y += 34;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "&Mode:", 12, y, labelWidth);
|
|
|
|
|
|
_cboMode = new ComboBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(160, 23),
|
|
|
|
|
|
DropDownStyle = ComboBoxStyle.DropDownList,
|
|
|
|
|
|
TabIndex = 10,
|
|
|
|
|
|
};
|
|
|
|
|
|
_cboMode.Items.AddRange(["Mono", "Stereo"]);
|
|
|
|
|
|
_cboMode.SelectedIndex = audio.Stereo ? 1 : 0;
|
|
|
|
|
|
page.Controls.Add(_cboMode);
|
|
|
|
|
|
y += 34;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "Sample &rate (Hz):", 12, y, labelWidth);
|
|
|
|
|
|
_numSampleRate = new NumericUpDown
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(120, 23),
|
|
|
|
|
|
Minimum = 8000,
|
|
|
|
|
|
Maximum = 96000,
|
|
|
|
|
|
Value = audio.SampleRate,
|
|
|
|
|
|
TabIndex = 11,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_numSampleRate);
|
|
|
|
|
|
y += 34;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "&Bitrate (bps, 0 = default):", 12, y, labelWidth);
|
|
|
|
|
|
_numBitrate = new NumericUpDown
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(120, 23),
|
|
|
|
|
|
Minimum = 0,
|
|
|
|
|
|
Maximum = 512000,
|
|
|
|
|
|
Increment = 1000,
|
|
|
|
|
|
Value = audio.BitrateBps,
|
|
|
|
|
|
TabIndex = 12,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_numBitrate);
|
|
|
|
|
|
y += 34;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "Frame &ms:", 12, y, labelWidth);
|
|
|
|
|
|
_numFrameMs = new NumericUpDown
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(120, 23),
|
|
|
|
|
|
Minimum = 5,
|
|
|
|
|
|
Maximum = 120,
|
|
|
|
|
|
Value = audio.FrameMs,
|
|
|
|
|
|
TabIndex = 13,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_numFrameMs);
|
|
|
|
|
|
y += 34;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "&Application:", 12, y, labelWidth);
|
|
|
|
|
|
_cboApplication = new ComboBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(160, 23),
|
|
|
|
|
|
DropDownStyle = ComboBoxStyle.DropDownList,
|
|
|
|
|
|
TabIndex = 14,
|
|
|
|
|
|
};
|
|
|
|
|
|
_cboApplication.Items.AddRange(["VoIP", "Audio", "Low delay"]);
|
|
|
|
|
|
_cboApplication.SelectedIndex = (int)Math.Min(2, audio.Application);
|
|
|
|
|
|
page.Controls.Add(_cboApplication);
|
|
|
|
|
|
y += 34;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "Expected packet loss (%):", 12, y, labelWidth);
|
|
|
|
|
|
_numExpectedLoss = new NumericUpDown
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(120, 23),
|
|
|
|
|
|
Minimum = 0,
|
|
|
|
|
|
Maximum = 100,
|
|
|
|
|
|
Value = audio.ExpectedPacketLoss,
|
|
|
|
|
|
TabIndex = 15,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_numExpectedLoss);
|
|
|
|
|
|
y += 34;
|
|
|
|
|
|
|
|
|
|
|
|
AddLabel(page, "Com&plexity (0–10):", 12, y, labelWidth);
|
|
|
|
|
|
_numComplexity = new NumericUpDown
|
|
|
|
|
|
{
|
|
|
|
|
|
Location = new Point(inputX, y - 2),
|
|
|
|
|
|
Size = new Size(120, 23),
|
|
|
|
|
|
Minimum = 0,
|
|
|
|
|
|
Maximum = 10,
|
|
|
|
|
|
Value = audio.Complexity,
|
|
|
|
|
|
TabIndex = 16,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_numComplexity);
|
|
|
|
|
|
y += 34;
|
|
|
|
|
|
|
|
|
|
|
|
_chkFec = new CheckBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = "&FEC",
|
|
|
|
|
|
Location = new Point(inputX, y),
|
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Checked = audio.Fec,
|
|
|
|
|
|
TabIndex = 17,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_chkFec);
|
|
|
|
|
|
y += 28;
|
|
|
|
|
|
|
|
|
|
|
|
_chkDtx = new CheckBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = "&DTX",
|
|
|
|
|
|
Location = new Point(inputX, y),
|
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Checked = audio.Dtx,
|
|
|
|
|
|
TabIndex = 18,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_chkDtx);
|
feat(clients): expose all channel codec params + guest nickname everywhere
Channel create/edit UIs only surfaced a subset of the core's vc_audio_config,
and DRED was exposed nowhere. While adding it, found a latent ABI mismatch:
both Swift AudioConfig and the C# VcAudioConfigNative blittable struct were one
int short of the native vc_audio_config (missing the trailing `dred`), so native
read past the managed struct in vc_create_channel/vc_edit_channel.
- core marshaling: thread `dred` through Swift (Models/Marshaling/toNative) and
C# (Structs/Models/Marshaling/VoiceCatClient) -- fixes the ABI gap + enables it
- windows: add the one missing DRED checkbox to ChannelEditDialog
- macos: ChannelEditSheet now exposes application, sample rate, packet loss,
complexity, and DRED (was stereo/bitrate/frame/FEC/DTX only)
- ios: rebuild ChannelEditView into a full create+edit form (all params); add
SessionState.editChannel + an admin Edit swipe action (iOS had no edit UI)
- guest nickname: add a dedicated `nickname` to SavedServer on macOS+iOS
(backward-compatible Codable), shown in Guest mode, wired into the guest auth
path -- guests could not set a display name on either before (only Windows)
Verified: macOS + iOS (sim, arm64) xcodebuild BUILD SUCCEEDED; core ctest 22/23
(only external_pcm aborts on a pre-existing shutdown mutex race; no C++ changed).
2026-06-21 04:03:50 +02:00
|
|
|
|
y += 28;
|
|
|
|
|
|
|
|
|
|
|
|
_chkDred = new CheckBox
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = "D&RED (deep redundancy)",
|
|
|
|
|
|
Location = new Point(inputX, y),
|
|
|
|
|
|
AutoSize = true,
|
|
|
|
|
|
Checked = audio.Dred,
|
|
|
|
|
|
TabIndex = 19,
|
|
|
|
|
|
};
|
|
|
|
|
|
page.Controls.Add(_chkDred);
|
2026-06-17 16:31:29 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void AddLabel(Control parent, string text, int x, int y, int width)
|
|
|
|
|
|
{
|
|
|
|
|
|
parent.Controls.Add(new Label
|
|
|
|
|
|
{
|
|
|
|
|
|
Text = text,
|
|
|
|
|
|
Location = new Point(x, y),
|
|
|
|
|
|
Size = new Size(width, 17),
|
|
|
|
|
|
TextAlign = System.Drawing.ContentAlignment.MiddleLeft,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SelectParent(uint parentId)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < _cboParent.Items.Count; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_cboParent.Items[i] is ChannelItem item && item.Id == parentId)
|
|
|
|
|
|
{
|
|
|
|
|
|
_cboParent.SelectedIndex = i;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_cboParent.SelectedIndex = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void BtnOk_Click(object? sender, EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
string name = _txtName.Text.Trim();
|
|
|
|
|
|
if (string.IsNullOrEmpty(name))
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show(this, "Channel name is required.", "VoiceCat",
|
|
|
|
|
|
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
|
|
|
|
DialogResult = DialogResult.None;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uint parentId = (_cboParent.SelectedItem as ChannelItem)?.Id ?? 0;
|
|
|
|
|
|
var audio = new AudioConfigInfo(
|
|
|
|
|
|
Codec: 0,
|
|
|
|
|
|
Stereo: _cboMode.SelectedIndex == 1,
|
|
|
|
|
|
SampleRate: (uint)_numSampleRate.Value,
|
|
|
|
|
|
BitrateBps: (uint)_numBitrate.Value,
|
|
|
|
|
|
FrameMs: (uint)_numFrameMs.Value,
|
|
|
|
|
|
Application: (uint)_cboApplication.SelectedIndex,
|
|
|
|
|
|
Fec: _chkFec.Checked,
|
|
|
|
|
|
ExpectedPacketLoss: (uint)_numExpectedLoss.Value,
|
|
|
|
|
|
Dtx: _chkDtx.Checked,
|
feat(clients): expose all channel codec params + guest nickname everywhere
Channel create/edit UIs only surfaced a subset of the core's vc_audio_config,
and DRED was exposed nowhere. While adding it, found a latent ABI mismatch:
both Swift AudioConfig and the C# VcAudioConfigNative blittable struct were one
int short of the native vc_audio_config (missing the trailing `dred`), so native
read past the managed struct in vc_create_channel/vc_edit_channel.
- core marshaling: thread `dred` through Swift (Models/Marshaling/toNative) and
C# (Structs/Models/Marshaling/VoiceCatClient) -- fixes the ABI gap + enables it
- windows: add the one missing DRED checkbox to ChannelEditDialog
- macos: ChannelEditSheet now exposes application, sample rate, packet loss,
complexity, and DRED (was stereo/bitrate/frame/FEC/DTX only)
- ios: rebuild ChannelEditView into a full create+edit form (all params); add
SessionState.editChannel + an admin Edit swipe action (iOS had no edit UI)
- guest nickname: add a dedicated `nickname` to SavedServer on macOS+iOS
(backward-compatible Codable), shown in Guest mode, wired into the guest auth
path -- guests could not set a display name on either before (only Windows)
Verified: macOS + iOS (sim, arm64) xcodebuild BUILD SUCCEEDED; core ctest 22/23
(only external_pcm aborts on a pre-existing shutdown mutex race; no C++ changed).
2026-06-21 04:03:50 +02:00
|
|
|
|
Complexity: (uint)_numComplexity.Value,
|
|
|
|
|
|
Dred: _chkDred.Checked);
|
2026-06-17 16:31:29 +02:00
|
|
|
|
|
|
|
|
|
|
Result = new ChannelEditInfo(
|
|
|
|
|
|
Id: _editingId,
|
|
|
|
|
|
ParentId: parentId,
|
|
|
|
|
|
Name: name,
|
|
|
|
|
|
Topic: _txtTopic.Text.Trim(),
|
|
|
|
|
|
PasswordProtected: _chkPassword.Checked,
|
|
|
|
|
|
Password: _chkPassword.Checked ? _txtPassword.Text : null,
|
|
|
|
|
|
MaxUsers: (uint)_numMaxUsers.Value,
|
|
|
|
|
|
SortOrder: (uint)_numSortOrder.Value,
|
|
|
|
|
|
Audio: audio);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private sealed class ChannelItem(string name, uint id)
|
|
|
|
|
|
{
|
|
|
|
|
|
public uint Id { get; } = id;
|
|
|
|
|
|
public override string ToString() => name;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|