FEAT(client, ui): Revamped server information dialog

The previous dialog was simply a message box with a bunch of HTML in
order to give a little structure to the contents. This approach is very
limiting in terms of UI flexibility though and therefore this commit
replaces the old HTML approach with a dedicated dialog class that uses
proper UI elements instead.

While doing so, the ordering and grouping of information was also
changed in order to make it more suitable for the every-day-user.

Fixes #4733
This commit is contained in:
Robert Adam 2021-03-28 18:58:34 +02:00
parent 604427ccdd
commit 5fabceee9d
5 changed files with 885 additions and 131 deletions

View File

@ -176,6 +176,9 @@ set(MUMBLE_SOURCES
"Screen.h"
"ServerHandler.cpp"
"ServerHandler.h"
"ServerInformation.cpp"
"ServerInformation.h"
"ServerInformation.ui"
"Settings.cpp"
"Settings.h"
"SharedMemory.cpp"

View File

@ -39,6 +39,7 @@
#include "SSLCipherInfo.h"
#include "Screen.h"
#include "ServerHandler.h"
#include "ServerInformation.h"
#include "Settings.h"
#include "SvgIcon.h"
#include "TalkingUI.h"
@ -1403,138 +1404,9 @@ void MainWindow::on_qaServerUserList_triggered() {
}
}
static const QString currentCodec() {
if (Global::get().bOpus)
return QLatin1String("Opus");
int v = Global::get().bPreferAlpha ? Global::get().iCodecAlpha : Global::get().iCodecBeta;
CELTCodec *cc = Global::get().qmCodecs.value(v);
if (cc)
return QString::fromLatin1("CELT %1").arg(cc->version());
else
return QString::fromLatin1("CELT %1").arg(QString::number(v, 16));
}
void MainWindow::on_qaServerInformation_triggered() {
ConnectionPtr c = Global::get().sh->cConnection;
if (!c)
return;
QSslCipher qsc = Global::get().sh->qscCipher;
QString qsVersion = tr("<h2>Version</h2><p>Protocol %1</p>").arg(MumbleVersion::toString(Global::get().sh->uiVersion));
if (Global::get().sh->qsRelease.isEmpty() || Global::get().sh->qsOS.isEmpty() || Global::get().sh->qsOSVersion.isEmpty()) {
qsVersion.append(tr("<p>No build information or OS version available</p>"));
} else {
qsVersion.append(
tr("<p>%1 (%2)<br />%3</p>")
.arg(Global::get().sh->qsRelease.toHtmlEscaped(), Global::get().sh->qsOS.toHtmlEscaped(), Global::get().sh->qsOSVersion.toHtmlEscaped()));
}
QString host, uname, pw;
unsigned short port;
Global::get().sh->getConnectionInfo(host, port, uname, pw);
const SSLCipherInfo *ci = SSLCipherInfoLookupByOpenSSLName(qsc.name().toLatin1().constData());
QString cipherDescription;
if (ci && ci->message_authentication && ci->encryption && ci->key_exchange_verbose && ci->rfc_name) {
if (QString::fromLatin1(ci->message_authentication) == QLatin1String("AEAD")) {
// Authenticated Encryption with Associated Data
// See https://en.wikipedia.org/wiki/Authenticated_encryption
cipherDescription =
tr("The connection is encrypted and authenticated "
"using %1 and uses %2 as the key exchange mechanism (%3)")
.arg(QString::fromLatin1(ci->encryption), QString::fromLatin1(ci->key_exchange_verbose),
QString::fromLatin1(ci->rfc_name));
} else {
cipherDescription =
tr("The connection is encrypted using %1, with %2 "
"for message authentication and %3 as the key "
"exchange mechanism (%4)")
.arg(QString::fromLatin1(ci->encryption), QString::fromLatin1(ci->message_authentication),
QString::fromLatin1(ci->key_exchange_verbose), QString::fromLatin1(ci->rfc_name));
}
}
if (cipherDescription.isEmpty()) {
cipherDescription =
tr("The connection is secured by the cipher suite that OpenSSL identifies as %1").arg(qsc.name());
}
QString cipherPFSInfo;
if (ci) {
if (ci->forward_secret) {
cipherPFSInfo = tr("<p>The connection provides perfect forward secrecy</p>");
} else {
cipherPFSInfo = tr("<p>The connection does not provide perfect forward secrecy</p>");
}
}
QString qsControl =
tr("<h2>Control channel</h2>"
"<p>The connection uses %1</p>"
"%2"
"%3"
"<p>%4 ms average latency (%5 deviation)</p>"
"<p>Remote host %6 (port %7)</p>")
.arg(c->sessionProtocolString().toHtmlEscaped(), cipherDescription, cipherPFSInfo,
QString::fromLatin1("%1").arg(boost::accumulators::mean(Global::get().sh->accTCP), 0, 'f', 2),
QString::fromLatin1("%1").arg(sqrt(boost::accumulators::variance(Global::get().sh->accTCP)), 0, 'f', 2),
host.toHtmlEscaped(), QString::number(port));
if (Global::get().uiMaxUsers) {
qsControl += tr("<p>Connected users: %1/%2</p>").arg(ModelItem::c_qhUsers.count()).arg(Global::get().uiMaxUsers);
}
QString qsVoice, qsCrypt, qsAudio;
if (NetworkConfig::TcpModeEnabled()) {
qsVoice = tr("Voice channel is sent over control channel");
} else {
qsVoice = tr("<h2>Voice channel</h2><p>Encrypted with 128 bit OCB-AES128<br />%1 ms average latency (%4 "
"deviation)</p>")
.arg(boost::accumulators::mean(Global::get().sh->accUDP), 0, 'f', 2)
.arg(sqrt(boost::accumulators::variance(Global::get().sh->accUDP)), 0, 'f', 2);
qsCrypt = QString::fromLatin1("<h2>%1</h2><table><tr><th></th><th>%2</th><th>%3</th></tr>"
"<tr><th>%4</th><td>%8</td><td>%12</td></tr>"
"<tr><th>%5</th><td>%9</td><td>%13</td></tr>"
"<tr><th>%6</th><td>%10</td><td>%14</td></tr>"
"<tr><th>%7</th><td>%11</td><td>%15</td></tr>"
"</table>")
.arg(tr("UDP Statistics"))
.arg(tr("To Server"))
.arg(tr("From Server"))
.arg(tr("Good"))
.arg(tr("Late"))
.arg(tr("Lost"))
.arg(tr("Resync"))
.arg(c->csCrypt->uiRemoteGood)
.arg(c->csCrypt->uiRemoteLate)
.arg(c->csCrypt->uiRemoteLost)
.arg(c->csCrypt->uiRemoteResync)
.arg(c->csCrypt->uiGood)
.arg(c->csCrypt->uiLate)
.arg(c->csCrypt->uiLost)
.arg(c->csCrypt->uiResync);
}
qsAudio = tr("<h2>Audio bandwidth</h2><p>Maximum %1 kbit/s<br />Current %2 kbit/s<br />Codec: %3</p>")
.arg(Global::get().iMaxBandwidth / 1000.0, 0, 'f', 1)
.arg(Global::get().iAudioBandwidth / 1000.0, 0, 'f', 1)
.arg(currentCodec());
QMessageBox qmb(QMessageBox::Information, tr("Mumble Server Information"),
qsVersion + qsControl + qsVoice + qsCrypt + qsAudio, QMessageBox::Ok, this);
qmb.setDefaultButton(QMessageBox::Ok);
qmb.setEscapeButton(QMessageBox::Ok);
QPushButton *qp = qmb.addButton(tr("&View Certificate"), QMessageBox::ActionRole);
int res = qmb.exec();
if ((res == 0) && (qmb.clickedButton() == qp)) {
ViewCert vc(Global::get().sh->qscCert, this);
vc.exec();
}
ServerInformation *infoDialog = new ServerInformation(this);
infoDialog->show();
}
void MainWindow::on_qaServerTexture_triggered() {

View File

@ -0,0 +1,199 @@
// Copyright 2021 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#include "ServerInformation.h"
#include "CELTCodec.h"
#include "Connection.h"
#include "MainWindow.h"
#include "NetworkConfig.h"
#include "SSL.h"
#include "SSLCipherInfo.h"
#include "ServerHandler.h"
#include "UserModel.h"
#include "Version.h"
#include "ViewCert.h"
#include "Global.h"
#include <QTableWidgetItem>
#include <boost/accumulators/accumulators.hpp>
#include <cmath>
QString ServerInformation::m_unknownStr = tr("Unknown");
ServerInformation::ServerInformation(QWidget *parent) : QDialog(parent) {
setupUi(this);
updateFields();
}
void ServerInformation::updateFields() {
updateServerInformation();
updateAudioBandwidth();
updateConnectionDetails();
}
void ServerInformation::on_okButton_clicked() {
accept();
}
void ServerInformation::on_viewCertButton_clicked() {
hide();
ViewCert certViewer(Global::get().sh->qscCert, this);
certViewer.exec();
accept();
}
void ServerInformation::updateServerInformation() {
QString host, userName, password;
unsigned short port;
Global::get().sh->getConnectionInfo(host, port, userName, password);
const int userCount = ModelItem::c_qhUsers.count();
const unsigned int maxUserCount = Global::get().uiMaxUsers;
QString release = Global::get().sh->qsRelease;
if (release.isEmpty()) {
release = m_unknownStr;
}
QString os = Global::get().sh->qsOS;
if (os.isEmpty()) {
os = m_unknownStr;
} else if (!Global::get().sh->qsOSVersion.isEmpty()) {
os += QString::fromLatin1(" (%1)").arg(Global::get().sh->qsOSVersion);
}
serverInfo_host->setText(host);
serverInfo_port->setText(QString::number(port));
serverInfo_users->setText(QString::fromLatin1("%1 / %2").arg(userCount).arg(maxUserCount));
serverInfo_protocol->setText(MumbleVersion::toString(Global::get().sh->uiVersion));
serverInfo_release->setText(release);
serverInfo_os->setText(os);
}
static const QString currentCodec() {
if (Global::get().bOpus)
return QLatin1String("Opus");
int v = Global::get().bPreferAlpha ? Global::get().iCodecAlpha : Global::get().iCodecBeta;
CELTCodec *cc = Global::get().qmCodecs.value(v);
if (cc)
return QString::fromLatin1("CELT %1").arg(cc->version());
else
return QString::fromLatin1("CELT %1").arg(QString::number(v, 16));
}
void ServerInformation::updateAudioBandwidth() {
// The bandwidths are in bit/s, so we divide by 1000 to get kBit/s
const float maxBandwidthAllowed = Global::get().iMaxBandwidth / 1000.0f;
const float currentBandwidth = Global::get().iAudioBandwidth / 1000.0f;
audio_current->setText(QString::fromLatin1("%1 kBit/s").arg(currentBandwidth, 0, 'f', 1));
audio_allowed->setText(QString::fromLatin1("%1 kBit/s").arg(maxBandwidthAllowed, 0, 'f', 1));
audio_codec->setText(currentCodec());
}
QString getCipherID(const QSslCipher &cipher, const SSLCipherInfo *cipherInfo) {
if (cipherInfo && cipherInfo->rfc_name) {
return QString::fromUtf8(cipherInfo->rfc_name);
}
return cipher.name();
}
void ServerInformation::updateConnectionDetails() {
QString latencyString = QString::fromUtf8("%1 ms (σ = %2 ms)");
const ConnectionPtr connection = Global::get().sh->cConnection;
if (!connection) {
return;
}
// UDP
if (NetworkConfig::TcpModeEnabled()) {
connection_udp_infoMessage->show();
connection_udp_encryption->hide();
connection_udp_encryptionLabel->hide();
connection_udp_latency->hide();
connection_udp_latencyLabel->hide();
connection_udp_statisticsGroup->hide();
} else {
connection_udp_infoMessage->hide();
connection_udp_encryption->show();
connection_udp_encryptionLabel->show();
connection_udp_latency->show();
connection_udp_latencyLabel->show();
connection_udp_statisticsGroup->show();
// Actually fill in data
const float latency = boost::accumulators::mean(Global::get().sh->accUDP);
const float deviation = std::sqrt(boost::accumulators::variance(Global::get().sh->accUDP));
connection_udp_encryption->setText("128 bit OCB-AES128");
connection_udp_latency->setText(latencyString.arg(latency, 0, 'f', 1).arg(deviation, 0, 'f', 1));
populateUDPStatistics(*connection);
}
// TCP
const float latency = boost::accumulators::mean(Global::get().sh->accTCP);
const float deviation = std::sqrt(boost::accumulators::variance(Global::get().sh->accTCP));
QSslCipher cipher = Global::get().sh->qscCipher;
const SSLCipherInfo *cipherInfo = SSLCipherInfoLookupByOpenSSLName(cipher.name().toLatin1().constData());
const QString cipherID = getCipherID(cipher, cipherInfo);
connection_tcp_tls->setText(MumbleSSL::protocolToString(connection->sessionProtocol()).toHtmlEscaped());
connection_tcp_latency->setText(latencyString.arg(latency, 0, 'f', 1).arg(deviation, 0, 'f', 1));
connection_tcp_cipher->setText(cipherID.isEmpty() ? m_unknownStr : cipherID);
if (cipherInfo) {
if (cipherInfo->forward_secret) {
connection_tcp_forwardSecrecy->setText(tr("The connection provides perfect forward secrecy."));
} else {
connection_tcp_forwardSecrecy->setText(tr("The connection does NOT provide perfect forward secrecy."));
}
} else {
connection_tcp_forwardSecrecy->setText(tr("No information about forward secrecy available."));
}
}
void ServerInformation::populateUDPStatistics(const Connection &connection) {
// statistics
constexpr int toServerCol = 0;
constexpr int fromServerCol = 1;
constexpr int goodRow = 0;
constexpr int lateRow = 1;
constexpr int lostRow = 2;
constexpr int resyncRow = 3;
QTableWidgetItem *toGoodItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiRemoteGood));
QTableWidgetItem *fromGoodItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiGood));
QTableWidgetItem *toLateItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiRemoteLate));
QTableWidgetItem *fromLateItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiLate));
QTableWidgetItem *toLostItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiRemoteLost));
QTableWidgetItem *fromLostItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiLost));
QTableWidgetItem *toResyncItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiRemoteResync));
QTableWidgetItem *fromResyncItem = new QTableWidgetItem(QString::number(connection.csCrypt->uiResync));
connection_udp_statisticsTable->setItem(goodRow, toServerCol, toGoodItem);
connection_udp_statisticsTable->setItem(goodRow, fromServerCol, fromGoodItem);
connection_udp_statisticsTable->setItem(lateRow, toServerCol, toLateItem);
connection_udp_statisticsTable->setItem(lateRow, fromServerCol, fromLateItem);
connection_udp_statisticsTable->setItem(lostRow, toServerCol, toLostItem);
connection_udp_statisticsTable->setItem(lostRow, fromServerCol, fromLostItem);
connection_udp_statisticsTable->setItem(resyncRow, toServerCol, toResyncItem);
connection_udp_statisticsTable->setItem(resyncRow, fromServerCol, fromResyncItem);
connection_udp_statisticsTable->adjustSize();
}

View File

@ -0,0 +1,37 @@
// Copyright 2021 The Mumble Developers. All rights reserved.
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
#ifndef MUMBLE_MUMBLE_SERVERINFORMATION_H_
#define MUMBLE_MUMBLE_SERVERINFORMATION_H_
#include <QDialog>
#include "ui_ServerInformation.h"
class Connection;
class ServerInformation : public QDialog, private Ui::ServerInformation {
Q_OBJECT;
Q_DISABLE_COPY(ServerInformation);
public:
ServerInformation(QWidget *parent = nullptr);
void updateFields();
public slots:
void on_okButton_clicked();
void on_viewCertButton_clicked();
private:
static QString m_unknownStr;
void updateServerInformation();
void updateAudioBandwidth();
void updateConnectionDetails();
void populateUDPStatistics(const Connection &connection);
};
#endif // MUMBLE_MUMBLE_SERVERINFORMATION_H_

View File

@ -0,0 +1,643 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ServerInformation</class>
<widget class="QDialog" name="ServerInformation">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>578</width>
<height>585</height>
</rect>
</property>
<property name="windowTitle">
<string>Server information</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>-203</y>
<width>541</width>
<height>731</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="qgbServerInformation">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Server Information</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;b&gt;Host:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="serverInfo_host">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;host&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Port:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="serverInfo_port">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;port&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt;b&gt;Users&lt;/b&gt;:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="serverInfo_users">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;users&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Protocol:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="serverInfo_protocol">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;protocol&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Release:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="serverInfo_release">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;release&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;OS:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="serverInfo_os">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;os&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="qgbAudioBandwidth">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Audio</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="1" column="0">
<widget class="QLabel" name="label_31">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Allowed:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="audio_allowed">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;allowed&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_32">
<property name="text">
<string>&lt;b&gt;Current:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="audio_current">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;current&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_33">
<property name="text">
<string>&lt;b&gt;Codec:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="audio_codec">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;codec&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="qgbConnectionDetails">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Connection details</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="tabPosition">
<enum>QTabWidget::North</enum>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>UDP (Voice)</string>
</attribute>
<layout class="QFormLayout" name="formLayout_3">
<item row="1" column="0">
<widget class="QLabel" name="connection_udp_encryptionLabel">
<property name="text">
<string>&lt;b&gt;Encryption:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="connection_udp_encryption">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;encryption&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="connection_udp_latencyLabel">
<property name="text">
<string>&lt;b&gt;Avg. latency:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="connection_udp_latency">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;latency&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QGroupBox" name="connection_udp_statisticsGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Statistics</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<item>
<widget class="QTableWidget" name="connection_udp_statisticsTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="showGrid">
<bool>true</bool>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<row>
<property name="text">
<string>Good</string>
</property>
</row>
<row>
<property name="text">
<string>Late</string>
</property>
</row>
<row>
<property name="text">
<string>Lost</string>
</property>
</row>
<row>
<property name="text">
<string>Resync</string>
</property>
</row>
<column>
<property name="text">
<string>Outgoing</string>
</property>
</column>
<column>
<property name="text">
<string>Incoming</string>
</property>
</column>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="connection_udp_infoMessage">
<property name="text">
<string>Voice packets is currently sent over TCP.</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="1">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>TCP (Control)</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>&lt;b&gt;TLS version:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="connection_tcp_tls">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;TLS&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>&lt;b&gt;Cipher suite:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="connection_tcp_cipher">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;Cipher&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>&lt;b&gt;Avg. latency:&lt;/b&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="connection_tcp_latency">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">&lt;latency&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="connection_tcp_forwardSecrecy">
<property name="text">
<string>&lt;forward secrecy&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="viewCertButton">
<property name="text">
<string>&amp;View certificate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>&amp;Ok</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>