mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2025-03-22 12:08:43 -04:00
Merge branch 'feat-outgoing-udp-interface' into develop
This commit is contained in:
commit
de0af5a2ed
@ -241,6 +241,7 @@ set (wsjt_qt_CXXSRCS
|
||||
logbook/Multiplier.cpp
|
||||
Network/NetworkAccessManager.cpp
|
||||
widgets/LazyFillComboBox.cpp
|
||||
widgets/CheckableItemComboBox.cpp
|
||||
)
|
||||
|
||||
set (wsjt_qtmm_CXXSRCS
|
||||
@ -1490,7 +1491,7 @@ add_executable (message_aggregator
|
||||
${message_aggregator_RESOURCES_RCC}
|
||||
${message_aggregator_VERSION_RESOURCES}
|
||||
)
|
||||
target_link_libraries (message_aggregator Qt5::Widgets wsjtx_udp-static)
|
||||
target_link_libraries (message_aggregator wsjt_qt Qt5::Widgets wsjtx_udp-static)
|
||||
|
||||
if (WSJT_CREATE_WINMAIN)
|
||||
set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)
|
||||
|
@ -163,6 +163,10 @@
|
||||
#include <QFontDialog>
|
||||
#include <QSerialPortInfo>
|
||||
#include <QScopedPointer>
|
||||
#include <QNetworkInterface>
|
||||
#include <QHostInfo>
|
||||
#include <QHostAddress>
|
||||
#include <QStandardItem>
|
||||
#include <QDebug>
|
||||
|
||||
#include "pimpl_impl.hpp"
|
||||
@ -439,6 +443,12 @@ private:
|
||||
void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *);
|
||||
void update_audio_channels (QComboBox const *, int, QComboBox *, bool);
|
||||
|
||||
void load_network_interfaces (CheckableItemComboBox *, QStringList current);
|
||||
Q_SLOT void validate_network_interfaces (QString const&);
|
||||
QStringList get_selected_network_interfaces (CheckableItemComboBox *);
|
||||
Q_SLOT void host_info_results (QHostInfo);
|
||||
void check_multicast (QHostAddress const&);
|
||||
|
||||
void find_tab (QWidget *);
|
||||
|
||||
void initialize_models ();
|
||||
@ -492,6 +502,8 @@ private:
|
||||
Q_SLOT void on_add_macro_line_edit_editingFinished ();
|
||||
Q_SLOT void delete_macro ();
|
||||
void delete_selected_macros (QModelIndexList);
|
||||
Q_SLOT void on_udp_server_line_edit_textChanged (QString const&);
|
||||
Q_SLOT void on_udp_server_line_edit_editingFinished ();
|
||||
Q_SLOT void on_save_path_select_push_button_clicked (bool);
|
||||
Q_SLOT void on_azel_path_select_push_button_clicked (bool);
|
||||
Q_SLOT void on_calibration_intercept_spin_box_valueChanged (double);
|
||||
@ -641,7 +653,12 @@ private:
|
||||
bool use_dynamic_grid_;
|
||||
QString opCall_;
|
||||
QString udp_server_name_;
|
||||
bool udp_server_name_edited_;
|
||||
int dns_lookup_id_;
|
||||
port_type udp_server_port_;
|
||||
QStringList udp_interface_names_;
|
||||
QString loopback_interface_name_;
|
||||
int udp_TTL_;
|
||||
QString n1mm_server_name_;
|
||||
port_type n1mm_server_port_;
|
||||
bool broadcast_to_n1mm_;
|
||||
@ -741,6 +758,8 @@ QString Configuration::opCall() const {return m_->opCall_;}
|
||||
void Configuration::opCall (QString const& call) {m_->opCall_ = call;}
|
||||
QString Configuration::udp_server_name () const {return m_->udp_server_name_;}
|
||||
auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;}
|
||||
QStringList Configuration::udp_interface_names () const {return m_->udp_interface_names_;}
|
||||
int Configuration::udp_TTL () const {return m_->udp_TTL_;}
|
||||
bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;}
|
||||
QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;}
|
||||
auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;}
|
||||
@ -995,6 +1014,8 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
|
||||
, transceiver_command_number_ {0}
|
||||
, degrade_ {0.} // initialize to zero each run, not
|
||||
// saved in settings
|
||||
, udp_server_name_edited_ {false}
|
||||
, dns_lookup_id_ {-1}
|
||||
{
|
||||
ui_->setupUi (this);
|
||||
|
||||
@ -1044,6 +1065,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
|
||||
// this must be done after the default paths above are set
|
||||
read_settings ();
|
||||
|
||||
// set up dynamic loading of audio devices
|
||||
connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
|
||||
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
|
||||
load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_);
|
||||
@ -1059,6 +1081,14 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
|
||||
QGuiApplication::restoreOverrideCursor ();
|
||||
});
|
||||
|
||||
// set up dynamic loading of network interfaces
|
||||
connect (ui_->udp_interfaces_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
|
||||
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
|
||||
load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_);
|
||||
QGuiApplication::restoreOverrideCursor ();
|
||||
});
|
||||
connect (ui_->udp_interfaces_combo_box, &QComboBox::currentTextChanged, this, &Configuration::impl::validate_network_interfaces);
|
||||
|
||||
// set up LoTW users CSV file fetching
|
||||
connect (&lotw_users_, &LotWUsers::load_finished, [this] () {
|
||||
ui_->LotW_CSV_fetch_push_button->setEnabled (true);
|
||||
@ -1335,7 +1365,14 @@ void Configuration::impl::initialize_models ()
|
||||
ui_->CAT_poll_interval_spin_box->setValue (rig_params_.poll_interval);
|
||||
ui_->opCallEntry->setText (opCall_);
|
||||
ui_->udp_server_line_edit->setText (udp_server_name_);
|
||||
on_udp_server_line_edit_editingFinished ();
|
||||
ui_->udp_server_port_spin_box->setValue (udp_server_port_);
|
||||
load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_);
|
||||
if (!udp_interface_names_.size ())
|
||||
{
|
||||
udp_interface_names_ = get_selected_network_interfaces (ui_->udp_interfaces_combo_box);
|
||||
}
|
||||
ui_->udp_TTL_spin_box->setValue (udp_TTL_);
|
||||
ui_->accept_udp_requests_check_box->setChecked (accept_udp_requests_);
|
||||
ui_->n1mm_server_name_line_edit->setText (n1mm_server_name_);
|
||||
ui_->n1mm_server_port_spin_box->setValue (n1mm_server_port_);
|
||||
@ -1513,6 +1550,8 @@ void Configuration::impl::read_settings ()
|
||||
rig_params_.split_mode = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value<TransceiverFactory::SplitMode> ();
|
||||
opCall_ = settings_->value ("OpCall", "").toString ();
|
||||
udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString ();
|
||||
udp_interface_names_ = settings_->value ("UDPInterface").toStringList ();
|
||||
udp_TTL_ = settings_->value ("UDPTTL", 1).toInt ();
|
||||
udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt ();
|
||||
n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString ();
|
||||
n1mm_server_port_ = settings_->value ("N1MMServerPort", 2333).toUInt ();
|
||||
@ -1641,6 +1680,8 @@ void Configuration::impl::write_settings ()
|
||||
settings_->setValue ("OpCall", opCall_);
|
||||
settings_->setValue ("UDPServer", udp_server_name_);
|
||||
settings_->setValue ("UDPServerPort", udp_server_port_);
|
||||
settings_->setValue ("UDPInterface", QVariant::fromValue (udp_interface_names_));
|
||||
settings_->setValue ("UDPTTL", udp_TTL_);
|
||||
settings_->setValue ("N1MMServer", n1mm_server_name_);
|
||||
settings_->setValue ("N1MMServerPort", n1mm_server_port_);
|
||||
settings_->setValue ("BroadcastToN1MM", broadcast_to_n1mm_);
|
||||
@ -1843,6 +1884,12 @@ bool Configuration::impl::validate ()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dns_lookup_id_ > -1)
|
||||
{
|
||||
MessageBox::information_message (this, tr ("Pending DNS lookup, please try again later"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2061,20 +2108,30 @@ void Configuration::impl::accept ()
|
||||
pwrBandTxMemory_ = ui_->checkBoxPwrBandTxMemory->isChecked ();
|
||||
pwrBandTuneMemory_ = ui_->checkBoxPwrBandTuneMemory->isChecked ();
|
||||
opCall_=ui_->opCallEntry->text();
|
||||
auto new_server = ui_->udp_server_line_edit->text ();
|
||||
if (new_server != udp_server_name_)
|
||||
|
||||
auto new_server = ui_->udp_server_line_edit->text ().trimmed ();
|
||||
auto new_interfaces = get_selected_network_interfaces (ui_->udp_interfaces_combo_box);
|
||||
if (new_server != udp_server_name_ || new_interfaces != udp_interface_names_)
|
||||
{
|
||||
udp_server_name_ = new_server;
|
||||
Q_EMIT self_->udp_server_changed (new_server);
|
||||
udp_interface_names_ = new_interfaces;
|
||||
Q_EMIT self_->udp_server_changed (udp_server_name_, udp_interface_names_);
|
||||
}
|
||||
|
||||
auto new_port = ui_->udp_server_port_spin_box->value ();
|
||||
if (new_port != udp_server_port_)
|
||||
{
|
||||
udp_server_port_ = new_port;
|
||||
Q_EMIT self_->udp_server_port_changed (new_port);
|
||||
Q_EMIT self_->udp_server_port_changed (udp_server_port_);
|
||||
}
|
||||
|
||||
|
||||
auto new_TTL = ui_->udp_TTL_spin_box->value ();
|
||||
if (new_TTL != udp_TTL_)
|
||||
{
|
||||
udp_TTL_ = new_TTL;
|
||||
Q_EMIT self_->udp_TTL_changed (udp_TTL_);
|
||||
}
|
||||
|
||||
if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_)
|
||||
{
|
||||
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
|
||||
@ -2130,6 +2187,12 @@ void Configuration::impl::accept ()
|
||||
|
||||
void Configuration::impl::reject ()
|
||||
{
|
||||
if (dns_lookup_id_ > -1)
|
||||
{
|
||||
QHostInfo::abortHostLookup (dns_lookup_id_);
|
||||
dns_lookup_id_ = -1;
|
||||
}
|
||||
|
||||
initialize_models (); // reverts to settings as at exec ()
|
||||
|
||||
// check if the Transceiver instance changed, in which case we need
|
||||
@ -2344,6 +2407,72 @@ void Configuration::impl::on_add_macro_push_button_clicked (bool /* checked */)
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::impl::on_udp_server_line_edit_textChanged (QString const&)
|
||||
{
|
||||
udp_server_name_edited_ = true;
|
||||
}
|
||||
|
||||
void Configuration::impl::on_udp_server_line_edit_editingFinished ()
|
||||
{
|
||||
if (udp_server_name_edited_)
|
||||
{
|
||||
auto const& server = ui_->udp_server_line_edit->text ().trimmed ();
|
||||
QHostAddress ha {server};
|
||||
if (server.size () && ha.isNull ())
|
||||
{
|
||||
// queue a host address lookup
|
||||
// qDebug () << "server host DNS lookup:" << server;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
dns_lookup_id_ = QHostInfo::lookupHost (server, this, &Configuration::impl::host_info_results);
|
||||
#else
|
||||
dns_lookup_id_ = QHostInfo::lookupHost (server, this, SLOT (host_info_results (QHostInfo)));
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
check_multicast (ha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::impl::host_info_results (QHostInfo host_info)
|
||||
{
|
||||
if (host_info.lookupId () != dns_lookup_id_) return;
|
||||
dns_lookup_id_ = -1;
|
||||
if (QHostInfo::NoError != host_info.error ())
|
||||
{
|
||||
MessageBox::critical_message (this, tr ("UDP server DNS lookup failed"), host_info.errorString ());
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const& server_addresses = host_info.addresses ();
|
||||
// qDebug () << "message server addresses:" << server_addresses;
|
||||
if (server_addresses.size ())
|
||||
{
|
||||
check_multicast (server_addresses[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Configuration::impl::check_multicast (QHostAddress const& ha)
|
||||
{
|
||||
auto is_multicast = is_multicast_address (ha);
|
||||
ui_->udp_interfaces_label->setVisible (is_multicast);
|
||||
ui_->udp_interfaces_combo_box->setVisible (is_multicast);
|
||||
ui_->udp_TTL_label->setVisible (is_multicast);
|
||||
ui_->udp_TTL_spin_box->setVisible (is_multicast);
|
||||
if (isVisible ())
|
||||
{
|
||||
if (is_MAC_ambiguous_multicast_address (ha))
|
||||
{
|
||||
MessageBox::warning_message (this, tr ("MAC-ambiguous multicast groups addresses not supported"));
|
||||
find_tab (ui_->udp_server_line_edit);
|
||||
ui_->udp_server_line_edit->clear ();
|
||||
}
|
||||
}
|
||||
udp_server_name_edited_ = false;
|
||||
}
|
||||
|
||||
void Configuration::impl::delete_frequencies ()
|
||||
{
|
||||
auto selection_model = ui_->frequencies_table_view->selectionModel ();
|
||||
@ -2868,6 +2997,85 @@ void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * com
|
||||
combo_box->setCurrentIndex (current_index);
|
||||
}
|
||||
|
||||
// load the available network interfaces into the selection combo box
|
||||
void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo_box, QStringList current)
|
||||
{
|
||||
combo_box->clear ();
|
||||
for (auto const& net_if : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
auto flags = QNetworkInterface::IsUp | QNetworkInterface::CanMulticast;
|
||||
if ((net_if.flags () & flags) == flags)
|
||||
{
|
||||
if (net_if.flags () & QNetworkInterface::IsLoopBack)
|
||||
{
|
||||
loopback_interface_name_ = net_if.name ();
|
||||
}
|
||||
auto item = combo_box->addCheckItem (net_if.humanReadableName ()
|
||||
, net_if.name ()
|
||||
, current.contains (net_if.name ()) ? Qt::Checked : Qt::Unchecked);
|
||||
auto tip = QString {"name(index): %1(%2) - %3"}.arg (net_if.name ()).arg (net_if.index ())
|
||||
.arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down");
|
||||
auto hw_addr = net_if.hardwareAddress ();
|
||||
if (hw_addr.size ())
|
||||
{
|
||||
tip += QString {"\nhw: %1"}.arg (net_if.hardwareAddress ());
|
||||
}
|
||||
auto aes = net_if.addressEntries ();
|
||||
if (aes.size ())
|
||||
{
|
||||
tip += "\naddresses:";
|
||||
for (auto const& ae : aes)
|
||||
{
|
||||
tip += QString {"\n ip: %1/%2"}.arg (ae.ip ().toString ()).arg (ae.prefixLength ());
|
||||
}
|
||||
}
|
||||
item->setToolTip (tip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get the select network interfaces from the selection combo box
|
||||
void Configuration::impl::validate_network_interfaces (QString const& /*text*/)
|
||||
{
|
||||
auto model = static_cast<QStandardItemModel *> (ui_->udp_interfaces_combo_box->model ());
|
||||
bool has_checked {false};
|
||||
int loopback_row {-1};
|
||||
for (int row = 0; row < model->rowCount (); ++row)
|
||||
{
|
||||
if (model->item (row)->data ().toString () == loopback_interface_name_)
|
||||
{
|
||||
loopback_row = row;
|
||||
}
|
||||
else if (Qt::Checked == model->item (row)->checkState ())
|
||||
{
|
||||
has_checked = true;
|
||||
}
|
||||
}
|
||||
if (loopback_row >= 0)
|
||||
{
|
||||
if (!has_checked)
|
||||
{
|
||||
model->item (loopback_row)->setCheckState (Qt::Checked);
|
||||
}
|
||||
model->item (loopback_row)->setEnabled (has_checked);
|
||||
}
|
||||
}
|
||||
|
||||
// get the select network interfaces from the selection combo box
|
||||
QStringList Configuration::impl::get_selected_network_interfaces (CheckableItemComboBox * combo_box)
|
||||
{
|
||||
QStringList interfaces;
|
||||
auto model = static_cast<QStandardItemModel *> (combo_box->model ());
|
||||
for (int row = 0; row < model->rowCount (); ++row)
|
||||
{
|
||||
if (Qt::Checked == model->item (row)->checkState ())
|
||||
{
|
||||
interfaces << model->item (row)->data ().toString ();
|
||||
}
|
||||
}
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
// enable only the channels that are supported by the selected audio device
|
||||
void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QFont>
|
||||
#include <QString>
|
||||
|
||||
#include "Radio.hpp"
|
||||
#include "models/IARURegions.hpp"
|
||||
@ -14,14 +15,12 @@
|
||||
class QSettings;
|
||||
class QWidget;
|
||||
class QAudioDeviceInfo;
|
||||
class QString;
|
||||
class QDir;
|
||||
class QNetworkAccessManager;
|
||||
class Bands;
|
||||
class FrequencyList_v2;
|
||||
class StationList;
|
||||
class QStringListModel;
|
||||
class QHostAddress;
|
||||
class LotWUsers;
|
||||
class DecodeHighlightingModel;
|
||||
class LogBook;
|
||||
@ -152,6 +151,8 @@ public:
|
||||
void opCall (QString const&);
|
||||
QString udp_server_name () const;
|
||||
port_type udp_server_port () const;
|
||||
QStringList udp_interface_names () const;
|
||||
int udp_TTL () const;
|
||||
QString n1mm_server_name () const;
|
||||
port_type n1mm_server_port () const;
|
||||
bool valid_n1mm_info () const;
|
||||
@ -273,8 +274,9 @@ public:
|
||||
//
|
||||
// This signal is emitted when the UDP server changes
|
||||
//
|
||||
Q_SIGNAL void udp_server_changed (QString const& udp_server) const;
|
||||
Q_SIGNAL void udp_server_changed (QString& udp_server_name, QStringList const& network_interfaces) const;
|
||||
Q_SIGNAL void udp_server_port_changed (port_type server_port) const;
|
||||
Q_SIGNAL void udp_TTL_changed (int TTL) const;
|
||||
Q_SIGNAL void accept_udp_requests_changed (bool checked) const;
|
||||
|
||||
// signal updates to decode highlighting
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>554</width>
|
||||
<height>556</height>
|
||||
<height>560</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -1864,12 +1864,6 @@ and DX Grid fields when a 73 or free text message is sent.</string>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="udp_server_line_edit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Optional hostname of network service to receive decodes.</p><p>Formats:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 multicast group address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 multicast group address</li></ul><p>Clearing this field will disable the broadcasting of UDP status updates.</p></body></html></string>
|
||||
</property>
|
||||
@ -1891,13 +1885,53 @@ and DX Grid fields when a 73 or free text message is sent.</string>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="udp_server_port_spin_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enter the service port number of the UDP server that WSJT-X should send updates to. If this is zero no updates will be broadcast.</p></body></html></string>
|
||||
<string><html><head/><body><p>Enter the service port number of the UDP server that WSJT-X should send updates to. If this is zero no updates will be sent.</p></body></html></string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65534</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="udp_interfaces_label">
|
||||
<property name="text">
|
||||
<string>Outgoing interfaces:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>udp_interfaces_combo_box</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="CheckableItemComboBox" name="udp_interfaces_combo_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>When sending updates to a multicast group address it is necessary to specify which network interface(s) to send them to. If the loop-back interface is multicast capable then at least that one will be selected.</p><p>For most users the loop-back interface is all that is needed, that will allow multiple other applications on the same machine to interoperate with WSJT-X. If applications running on other hosts are to receive status updates then a suitable network interface should be used.</p><p>On some Linux systems it may be necessary to enable multicast on the loop-back network interface.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="udp_TTL_spin_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Sets the number or router hops that multicast datagrams are allowed to make. Almost everyone should set this to 1 to keep outgoing multicast traffic withn the local subnet.</p></body></html></string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="udp_TTL_label">
|
||||
<property name="text">
|
||||
<string>Multicast TTL:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>udp_TTL_spin_box</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
@ -3002,6 +3036,11 @@ Right click for insert and delete options.</string>
|
||||
<extends>QComboBox</extends>
|
||||
<header>widgets/LazyFillComboBox.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>CheckableItemComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>widgets/CheckableItemComboBox.hpp</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>configuration_tabs</tabstop>
|
||||
@ -3084,6 +3123,8 @@ Right click for insert and delete options.</string>
|
||||
<tabstop>psk_reporter_tcpip_check_box</tabstop>
|
||||
<tabstop>udp_server_line_edit</tabstop>
|
||||
<tabstop>udp_server_port_spin_box</tabstop>
|
||||
<tabstop>udp_interfaces_combo_box</tabstop>
|
||||
<tabstop>udp_TTL_spin_box</tabstop>
|
||||
<tabstop>accept_udp_requests_check_box</tabstop>
|
||||
<tabstop>udpWindowToFront</tabstop>
|
||||
<tabstop>udpWindowRestore</tabstop>
|
||||
@ -3101,8 +3142,8 @@ Right click for insert and delete options.</string>
|
||||
<tabstop>include_WAE_check_box</tabstop>
|
||||
<tabstop>rescan_log_push_button</tabstop>
|
||||
<tabstop>LotW_CSV_URL_line_edit</tabstop>
|
||||
<tabstop>LotW_days_since_upload_spin_box</tabstop>
|
||||
<tabstop>LotW_CSV_fetch_push_button</tabstop>
|
||||
<tabstop>LotW_days_since_upload_spin_box</tabstop>
|
||||
<tabstop>sbNtrials</tabstop>
|
||||
<tabstop>sbAggressive</tabstop>
|
||||
<tabstop>cbTwoPass</tabstop>
|
||||
@ -3118,11 +3159,11 @@ Right click for insert and delete options.</string>
|
||||
<tabstop>rbHound</tabstop>
|
||||
<tabstop>rbNA_VHF_Contest</tabstop>
|
||||
<tabstop>rbField_Day</tabstop>
|
||||
<tabstop>Field_Day_Exchange</tabstop>
|
||||
<tabstop>rbEU_VHF_Contest</tabstop>
|
||||
<tabstop>rbRTTY_Roundup</tabstop>
|
||||
<tabstop>RTTY_Exchange</tabstop>
|
||||
<tabstop>rbWW_DIGI</tabstop>
|
||||
<tabstop>Field_Day_Exchange</tabstop>
|
||||
<tabstop>RTTY_Exchange</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
@ -3192,13 +3233,13 @@ Right click for insert and delete options.</string>
|
||||
</connection>
|
||||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="PTT_method_button_group"/>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="special_op_activity_button_group"/>
|
||||
<buttongroup name="CAT_stop_bits_button_group"/>
|
||||
<buttongroup name="TX_audio_source_button_group"/>
|
||||
<buttongroup name="split_mode_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="CAT_stop_bits_button_group"/>
|
||||
<buttongroup name="CAT_handshake_button_group"/>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="special_op_activity_button_group"/>
|
||||
<buttongroup name="PTT_method_button_group"/>
|
||||
<buttongroup name="split_mode_button_group"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
@ -6,16 +6,16 @@
|
||||
#include <limits>
|
||||
|
||||
#include <QUdpSocket>
|
||||
#include <QNetworkInterface>
|
||||
#include <QHostInfo>
|
||||
#include <QTimer>
|
||||
#include <QQueue>
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
#include <QColor>
|
||||
#include <QDebug>
|
||||
|
||||
#include "NetworkMessage.hpp"
|
||||
|
||||
#include "qt_helpers.hpp"
|
||||
#include "pimpl_impl.hpp"
|
||||
|
||||
#include "moc_MessageClient.cpp"
|
||||
@ -34,14 +34,15 @@ class MessageClient::impl
|
||||
|
||||
public:
|
||||
impl (QString const& id, QString const& version, QString const& revision,
|
||||
port_type server_port, MessageClient * self)
|
||||
port_type server_port, int TTL, MessageClient * self)
|
||||
: self_ {self}
|
||||
, dns_lookup_id_ {0}
|
||||
, enabled_ {false}
|
||||
, id_ {id}
|
||||
, version_ {version}
|
||||
, revision_ {revision}
|
||||
, dns_lookup_id_ {-1}
|
||||
, server_port_ {server_port}
|
||||
, TTL_ {TTL}
|
||||
, schema_ {2} // use 2 prior to negotiation not 1 which is broken
|
||||
, heartbeat_timer_ {new QTimer {this}}
|
||||
{
|
||||
@ -49,9 +50,6 @@ public:
|
||||
connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams);
|
||||
|
||||
heartbeat_timer_->start (NetworkMessage::pulse * 1000);
|
||||
|
||||
// bind to an ephemeral port
|
||||
bind ();
|
||||
}
|
||||
|
||||
~impl ()
|
||||
@ -61,35 +59,37 @@ public:
|
||||
|
||||
enum StreamStatus {Fail, Short, OK};
|
||||
|
||||
void parse_message (QByteArray const& msg);
|
||||
void set_server (QString const& server_name, QStringList const& network_interface_names);
|
||||
Q_SLOT void host_info_results (QHostInfo);
|
||||
void start ();
|
||||
void parse_message (QByteArray const&);
|
||||
void pending_datagrams ();
|
||||
void heartbeat ();
|
||||
void closedown ();
|
||||
StreamStatus check_status (QDataStream const&) const;
|
||||
void send_message (QByteArray const&);
|
||||
void send_message (QDataStream const& out, QByteArray const& message)
|
||||
void send_message (QByteArray const&, bool queue_if_pending = true);
|
||||
void send_message (QDataStream const& out, QByteArray const& message, bool queue_if_pending = true)
|
||||
{
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
send_message (message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
send_message (message, queue_if_pending);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
}
|
||||
|
||||
Q_SLOT void host_info_results (QHostInfo);
|
||||
|
||||
MessageClient * self_;
|
||||
int dns_lookup_id_;
|
||||
bool enabled_;
|
||||
QString id_;
|
||||
QString version_;
|
||||
QString revision_;
|
||||
QString server_string_;
|
||||
port_type server_port_;
|
||||
int dns_lookup_id_;
|
||||
QHostAddress server_;
|
||||
port_type server_port_;
|
||||
int TTL_;
|
||||
std::vector<QNetworkInterface> network_interfaces_;
|
||||
quint32 schema_;
|
||||
QTimer * heartbeat_timer_;
|
||||
std::vector<QHostAddress> blocked_addresses_;
|
||||
@ -101,36 +101,99 @@ public:
|
||||
|
||||
#include "MessageClient.moc"
|
||||
|
||||
void MessageClient::impl::set_server (QString const& server_name, QStringList const& network_interface_names)
|
||||
{
|
||||
// qDebug () << "MessageClient server:" << server_name << "port:" << server_port_ << "interfaces:" << network_interface_names;
|
||||
server_.setAddress (server_name);
|
||||
network_interfaces_.clear ();
|
||||
for (auto const& net_if_name : network_interface_names)
|
||||
{
|
||||
network_interfaces_.push_back (QNetworkInterface::interfaceFromName (net_if_name));
|
||||
}
|
||||
|
||||
if (server_.isNull () && server_name.size ()) // DNS lookup required
|
||||
{
|
||||
// queue a host address lookup
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, &MessageClient::impl::host_info_results);
|
||||
#else
|
||||
dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, SLOT (host_info_results (QHostInfo)));
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
start ();
|
||||
}
|
||||
}
|
||||
|
||||
void MessageClient::impl::host_info_results (QHostInfo host_info)
|
||||
{
|
||||
if (host_info.lookupId () != dns_lookup_id_) return;
|
||||
dns_lookup_id_ = -1;
|
||||
if (QHostInfo::NoError != host_info.error ())
|
||||
{
|
||||
Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ());
|
||||
pending_messages_.clear (); // discard
|
||||
Q_EMIT self_->error ("UDP server DNS lookup failed: " + host_info.errorString ());
|
||||
}
|
||||
else if (host_info.addresses ().size ())
|
||||
else
|
||||
{
|
||||
auto server = host_info.addresses ()[0];
|
||||
if (blocked_addresses_.end () == std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server))
|
||||
auto const& server_addresses = host_info.addresses ();
|
||||
if (server_addresses.size ())
|
||||
{
|
||||
server_ = server;
|
||||
TRACE_UDP ("resulting server:" << server);
|
||||
|
||||
// send initial heartbeat which allows schema negotiation
|
||||
heartbeat ();
|
||||
|
||||
// clear any backlog
|
||||
while (pending_messages_.size ())
|
||||
{
|
||||
send_message (pending_messages_.dequeue ());
|
||||
}
|
||||
server_ = server_addresses[0];
|
||||
}
|
||||
else
|
||||
}
|
||||
start ();
|
||||
}
|
||||
|
||||
void MessageClient::impl::start ()
|
||||
{
|
||||
if (server_.isNull ())
|
||||
{
|
||||
Q_EMIT self_->close ();
|
||||
pending_messages_.clear (); // discard
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_broadcast_address (server_))
|
||||
{
|
||||
Q_EMIT self_->error ("IPv4 broadcast not supported, please specify the loop-back address, a server host address, or multicast group address");
|
||||
pending_messages_.clear (); // discard
|
||||
return;
|
||||
}
|
||||
|
||||
if (blocked_addresses_.end () != std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server_))
|
||||
{
|
||||
Q_EMIT self_->error ("UDP server blocked, please try another");
|
||||
pending_messages_.clear (); // discard
|
||||
return;
|
||||
}
|
||||
|
||||
TRACE_UDP ("Trying server:" << server_.toString ());
|
||||
QHostAddress interface_addr {IPv6Protocol == server_.protocol () ? QHostAddress::AnyIPv6 : QHostAddress::AnyIPv4};
|
||||
|
||||
if (localAddress () != interface_addr)
|
||||
{
|
||||
if (UnconnectedState != state () || state ())
|
||||
{
|
||||
Q_EMIT self_->error ("UDP server blocked, please try another");
|
||||
pending_messages_.clear (); // discard
|
||||
close ();
|
||||
}
|
||||
// bind to an ephemeral port on the selected interface and set
|
||||
// up for sending datagrams
|
||||
bind (interface_addr);
|
||||
// qDebug () << "Bound to UDP port:" << localPort () << "on:" << localAddress ();
|
||||
|
||||
// set multicast TTL to limit scope when sending to multicast
|
||||
// group addresses
|
||||
setSocketOption (MulticastTtlOption, TTL_);
|
||||
}
|
||||
|
||||
// send initial heartbeat which allows schema negotiation
|
||||
heartbeat ();
|
||||
|
||||
// clear any backlog
|
||||
while (pending_messages_.size ())
|
||||
{
|
||||
send_message (pending_messages_.dequeue (), false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,14 +421,11 @@ void MessageClient::impl::heartbeat ()
|
||||
if (server_port_ && !server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id_, schema_};
|
||||
hb << NetworkMessage::Builder::schema_number // maximum schema number accepted
|
||||
<< version_.toUtf8 () << revision_.toUtf8 ();
|
||||
if (OK == check_status (hb))
|
||||
{
|
||||
TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_);
|
||||
writeDatagram (message, server_, server_port_);
|
||||
}
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Heartbeat, id_, schema_};
|
||||
out << NetworkMessage::Builder::schema_number // maximum schema number accepted
|
||||
<< version_.toUtf8 () << revision_.toUtf8 ();
|
||||
TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_);
|
||||
send_message (out, message, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -375,15 +435,12 @@ void MessageClient::impl::closedown ()
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Close, id_, schema_};
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
TRACE_UDP ("");
|
||||
writeDatagram (message, server_, server_port_);
|
||||
}
|
||||
TRACE_UDP ("");
|
||||
send_message (out, message, false);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageClient::impl::send_message (QByteArray const& message)
|
||||
void MessageClient::impl::send_message (QByteArray const& message, bool queue_if_pending)
|
||||
{
|
||||
if (server_port_)
|
||||
{
|
||||
@ -391,11 +448,25 @@ void MessageClient::impl::send_message (QByteArray const& message)
|
||||
{
|
||||
if (message != last_message_) // avoid duplicates
|
||||
{
|
||||
writeDatagram (message, server_, server_port_);
|
||||
if (is_multicast_address (server_))
|
||||
{
|
||||
// send datagram on each selected network interface
|
||||
std::for_each (network_interfaces_.begin (), network_interfaces_.end ()
|
||||
, [&] (QNetworkInterface const& net_if) {
|
||||
setMulticastInterface (net_if);
|
||||
// qDebug () << "Multicast UDP datagram sent to:" << server_ << "port:" << server_port_ << "on:" << multicastInterface ().humanReadableName ();
|
||||
writeDatagram (message, server_, server_port_);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// qDebug () << "Unicast UDP datagram sent to:" << server_ << "port:" << server_port_;
|
||||
writeDatagram (message, server_, server_port_);
|
||||
}
|
||||
last_message_ = message;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (queue_if_pending)
|
||||
{
|
||||
pending_messages_.enqueue (message);
|
||||
}
|
||||
@ -428,9 +499,11 @@ auto MessageClient::impl::check_status (QDataStream const& stream) const -> Stre
|
||||
}
|
||||
|
||||
MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision,
|
||||
QString const& server, port_type server_port, QObject * self)
|
||||
QString const& server_name, port_type server_port,
|
||||
QStringList const& network_interface_names,
|
||||
int TTL, QObject * self)
|
||||
: QObject {self}
|
||||
, m_ {id, version, revision, server_port, this}
|
||||
, m_ {id, version, revision, server_port, TTL, this}
|
||||
{
|
||||
connect (&*m_
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
@ -449,8 +522,8 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString
|
||||
#endif
|
||||
Q_EMIT error (m_->errorString ());
|
||||
}
|
||||
});
|
||||
set_server (server);
|
||||
});
|
||||
m_->set_server (server_name, network_interface_names);
|
||||
}
|
||||
|
||||
QHostAddress MessageClient::server_address () const
|
||||
@ -463,20 +536,9 @@ auto MessageClient::server_port () const -> port_type
|
||||
return m_->server_port_;
|
||||
}
|
||||
|
||||
void MessageClient::set_server (QString const& server)
|
||||
void MessageClient::set_server (QString const& server_name, QStringList const& network_interface_names)
|
||||
{
|
||||
m_->server_.clear ();
|
||||
m_->server_string_ = server;
|
||||
if (server.size ())
|
||||
{
|
||||
// queue a host address lookup
|
||||
TRACE_UDP ("server host DNS lookup:" << server);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
|
||||
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, &MessageClient::impl::host_info_results);
|
||||
#else
|
||||
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
|
||||
#endif
|
||||
}
|
||||
m_->set_server (server_name, network_interface_names);
|
||||
}
|
||||
|
||||
void MessageClient::set_server_port (port_type server_port)
|
||||
@ -484,6 +546,12 @@ void MessageClient::set_server_port (port_type server_port)
|
||||
m_->server_port_ = server_port;
|
||||
}
|
||||
|
||||
void MessageClient::set_TTL (int TTL)
|
||||
{
|
||||
m_->TTL_ = TTL;
|
||||
m_->setSocketOption (QAbstractSocket::MulticastTtlOption, m_->TTL_);
|
||||
}
|
||||
|
||||
void MessageClient::enable (bool flag)
|
||||
{
|
||||
m_->enabled_ = flag;
|
||||
@ -499,7 +567,7 @@ void MessageClient::status_update (Frequency f, QString const& mode, QString con
|
||||
, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_};
|
||||
@ -516,7 +584,7 @@ void MessageClient::decode (bool is_new, QTime time, qint32 snr, float delta_tim
|
||||
, QString const& mode, QString const& message_text, bool low_confidence
|
||||
, bool off_air)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_, m_->schema_};
|
||||
@ -531,7 +599,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt
|
||||
, qint32 drift, QString const& callsign, QString const& grid, qint32 power
|
||||
, bool off_air)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::WSPRDecode, m_->id_, m_->schema_};
|
||||
@ -544,7 +612,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt
|
||||
|
||||
void MessageClient::decodes_cleared ()
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Clear, m_->id_, m_->schema_};
|
||||
@ -561,7 +629,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
|
||||
, QString const& my_grid, QString const& exchange_sent
|
||||
, QString const& exchange_rcvd, QString const& propmode)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_, m_->schema_};
|
||||
@ -576,7 +644,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
|
||||
|
||||
void MessageClient::logged_ADIF (QByteArray const& ADIF_record)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_};
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include "Radio.hpp"
|
||||
#include "pimpl_h.hpp"
|
||||
@ -34,19 +35,25 @@ public:
|
||||
//
|
||||
// messages will be silently dropped until a server host lookup is complete
|
||||
MessageClient (QString const& id, QString const& version, QString const& revision,
|
||||
QString const& server, port_type server_port, QObject * parent = nullptr);
|
||||
QString const& server_name, port_type server_port,
|
||||
QStringList const& network_interface_names,
|
||||
int TTL, QObject * parent = nullptr);
|
||||
|
||||
// query server details
|
||||
QHostAddress server_address () const;
|
||||
port_type server_port () const;
|
||||
|
||||
// initiate a new server host lookup or is the server name is empty
|
||||
// the sending of messages is disabled
|
||||
Q_SLOT void set_server (QString const& server = QString {});
|
||||
// initiate a new server host lookup or if the server name is empty
|
||||
// the sending of messages is disabled, if an interface is specified
|
||||
// then that interface is used for outgoing datagrams
|
||||
Q_SLOT void set_server (QString const& server_name, QStringList const& network_interface_names);
|
||||
|
||||
// change the server port messages are sent to
|
||||
Q_SLOT void set_server_port (port_type server_port = 0u);
|
||||
|
||||
// change the server port messages are sent to
|
||||
Q_SLOT void set_TTL (int TTL);
|
||||
|
||||
// enable incoming messages
|
||||
Q_SLOT void enable (bool);
|
||||
|
||||
|
@ -28,13 +28,13 @@ namespace NetworkMessage
|
||||
{
|
||||
setVersion (QDataStream::Qt_5_0); // Qt schema version
|
||||
}
|
||||
#if QT_VERSION >= 0x050200
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
|
||||
else if (schema <= 2)
|
||||
{
|
||||
setVersion (QDataStream::Qt_5_2); // Qt schema version
|
||||
}
|
||||
#endif
|
||||
#if QT_VERSION >= 0x050400
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
|
||||
else if (schema <= 3)
|
||||
{
|
||||
setVersion (QDataStream::Qt_5_4); // Qt schema version
|
||||
@ -73,13 +73,13 @@ namespace NetworkMessage
|
||||
{
|
||||
parent->setVersion (QDataStream::Qt_5_0);
|
||||
}
|
||||
#if QT_VERSION >= 0x050200
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
|
||||
else if (schema_ <= 2)
|
||||
{
|
||||
parent->setVersion (QDataStream::Qt_5_2);
|
||||
}
|
||||
#endif
|
||||
#if QT_VERSION >= 0x050400
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
|
||||
else if (schema_ <= 3)
|
||||
{
|
||||
parent->setVersion (QDataStream::Qt_5_4);
|
||||
|
@ -540,9 +540,9 @@ namespace NetworkMessage
|
||||
|
||||
// increment this if a newer Qt schema is required and add decode
|
||||
// logic to the Builder and Reader class implementations
|
||||
#if QT_VERSION >= 0x050400
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
|
||||
static quint32 constexpr schema_number {3};
|
||||
#elif QT_VERSION >= 0x050200
|
||||
#elif QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
|
||||
static quint32 constexpr schema_number {2};
|
||||
#else
|
||||
// Schema 1 (Qt_5_0) is broken
|
||||
|
@ -25,10 +25,13 @@ namespace
|
||||
|
||||
QFont text_font {"Courier", 10};
|
||||
|
||||
QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time
|
||||
QList<QStandardItem *> make_row (MessageServer::ClientKey const& key, QTime time, qint32 snr, float delta_time
|
||||
, Frequency frequency, qint32 drift, QString const& callsign
|
||||
, QString const& grid, qint32 power, bool off_air)
|
||||
{
|
||||
auto client_item = new QStandardItem {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())};
|
||||
client_item->setData (QVariant::fromValue (key));
|
||||
|
||||
auto time_item = new QStandardItem {time.toString ("hh:mm")};
|
||||
time_item->setData (time);
|
||||
time_item->setTextAlignment (Qt::AlignRight);
|
||||
@ -60,7 +63,7 @@ namespace
|
||||
live->setTextAlignment (Qt::AlignHCenter);
|
||||
|
||||
QList<QStandardItem *> row {
|
||||
new QStandardItem {client_id}, time_item, snr_item, dt, freq, dri, gd, pwr, live, new QStandardItem {callsign}};
|
||||
client_item, time_item, snr_item, dt, freq, dri, gd, pwr, live, new QStandardItem {callsign}};
|
||||
Q_FOREACH (auto& item, row)
|
||||
{
|
||||
item->setEditable (false);
|
||||
@ -81,7 +84,7 @@ BeaconsModel::BeaconsModel (QObject * parent)
|
||||
}
|
||||
}
|
||||
|
||||
void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
|
||||
void BeaconsModel::add_beacon_spot (bool is_new, ClientKey const& key, QTime time, qint32 snr, float delta_time
|
||||
, Frequency frequency, qint32 drift, QString const& callsign
|
||||
, QString const& grid, qint32 power, bool off_air)
|
||||
{
|
||||
@ -90,7 +93,7 @@ void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime
|
||||
int target_row {-1};
|
||||
for (auto row = 0; row < rowCount (); ++row)
|
||||
{
|
||||
if (data (index (row, 0)).toString () == client_id)
|
||||
if (item (row, 0)->data ().value<ClientKey> () == key)
|
||||
{
|
||||
auto row_time = item (row, 1)->data ().toTime ();
|
||||
if (row_time == time
|
||||
@ -113,19 +116,19 @@ void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime
|
||||
}
|
||||
if (target_row >= 0)
|
||||
{
|
||||
insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air));
|
||||
insertRow (target_row + 1, make_row (key, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
appendRow (make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air));
|
||||
appendRow (make_row (key, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air));
|
||||
}
|
||||
|
||||
void BeaconsModel::decodes_cleared (QString const& client_id)
|
||||
void BeaconsModel::decodes_cleared (ClientKey const& key)
|
||||
{
|
||||
for (auto row = rowCount () - 1; row >= 0; --row)
|
||||
{
|
||||
if (data (index (row, 0)).toString () == client_id)
|
||||
if (item (row, 0)->data ().value<ClientKey> () == key)
|
||||
{
|
||||
removeRow (row);
|
||||
}
|
||||
|
@ -26,13 +26,15 @@ class BeaconsModel
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
explicit BeaconsModel (QObject * parent = nullptr);
|
||||
|
||||
Q_SLOT void add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
|
||||
Q_SLOT void add_beacon_spot (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time
|
||||
, Frequency frequency, qint32 drift, QString const& callsign, QString const& grid
|
||||
, qint32 power, bool off_air);
|
||||
Q_SLOT void decodes_cleared (QString const& client_id);
|
||||
Q_SLOT void decodes_cleared (ClientKey const&);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -27,9 +27,9 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id, QObject * parent)
|
||||
ClientWidget::IdFilterModel::IdFilterModel (ClientKey const& key, QObject * parent)
|
||||
: QSortFilterProxyModel {parent}
|
||||
, client_id_ {client_id}
|
||||
, key_ {key}
|
||||
, rx_df_ (quint32_max)
|
||||
{
|
||||
}
|
||||
@ -73,7 +73,7 @@ bool ClientWidget::IdFilterModel::filterAcceptsRow (int source_row
|
||||
, QModelIndex const& source_parent) const
|
||||
{
|
||||
auto source_index_col0 = sourceModel ()->index (source_row, 0, source_parent);
|
||||
return sourceModel ()->data (source_index_col0).toString () == client_id_;
|
||||
return sourceModel ()->data (source_index_col0).value<ClientKey> () == key_;
|
||||
}
|
||||
|
||||
void ClientWidget::IdFilterModel::de_call (QString const& call)
|
||||
@ -106,9 +106,9 @@ void ClientWidget::IdFilterModel::rx_df (quint32 df)
|
||||
|
||||
namespace
|
||||
{
|
||||
QString make_title (QString const& id, QString const& version, QString const& revision)
|
||||
QString make_title (MessageServer::ClientKey const& key, QString const& version, QString const& revision)
|
||||
{
|
||||
QString title {id};
|
||||
QString title {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())};
|
||||
if (version.size ())
|
||||
{
|
||||
title += QString {" v%1"}.arg (version);
|
||||
@ -122,14 +122,14 @@ namespace
|
||||
}
|
||||
|
||||
ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
|
||||
, QString const& id, QString const& version, QString const& revision
|
||||
, ClientKey const& key, QString const& version, QString const& revision
|
||||
, QListWidget const * calls_of_interest, QWidget * parent)
|
||||
: QDockWidget {make_title (id, version, revision), parent}
|
||||
, id_ {id}
|
||||
: QDockWidget {make_title (key, version, revision), parent}
|
||||
, key_ {key}
|
||||
, done_ {false}
|
||||
, calls_of_interest_ {calls_of_interest}
|
||||
, decodes_proxy_model_ {id}
|
||||
, beacons_proxy_model_ {id}
|
||||
, decodes_proxy_model_ {key}
|
||||
, beacons_proxy_model_ {key}
|
||||
, erase_action_ {new QAction {tr ("&Erase Band Activity"), this}}
|
||||
, erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), this}}
|
||||
, erase_both_action_ {new QAction {tr ("Erase &Both"), this}}
|
||||
@ -209,56 +209,56 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
horizontal_layout_->addLayout (subform3_layout_);
|
||||
|
||||
connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) {
|
||||
Q_EMIT do_free_text (id_, text, false);
|
||||
Q_EMIT do_free_text (key_, text, false);
|
||||
});
|
||||
connect (message_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
Q_EMIT do_free_text (id_, message_line_edit_->text (), true);
|
||||
Q_EMIT do_free_text (key_, message_line_edit_->text (), true);
|
||||
});
|
||||
connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
Q_EMIT location (id_, grid_line_edit_->text ());
|
||||
Q_EMIT location (key_, grid_line_edit_->text ());
|
||||
});
|
||||
connect (configuration_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
Q_EMIT switch_configuration (id_, configuration_line_edit_->text ());
|
||||
Q_EMIT switch_configuration (key_, configuration_line_edit_->text ());
|
||||
});
|
||||
connect (mode_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, mode_line_edit_->text (), quint32_max, empty, fast_mode ()
|
||||
Q_EMIT configure (key_, mode_line_edit_->text (), quint32_max, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (frequency_tolerance_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
|
||||
QString empty;
|
||||
auto f = frequency_tolerance_spin_box_->specialValueText ().size () ? quint32_max : i;
|
||||
Q_EMIT configure (id_, empty, f, empty, fast_mode ()
|
||||
Q_EMIT configure (key_, empty, f, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (submode_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, submode_line_edit_->text (), fast_mode ()
|
||||
Q_EMIT configure (key_, empty, quint32_max, submode_line_edit_->text (), fast_mode ()
|
||||
, quint32_max, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (fast_mode_check_box_, &QCheckBox::stateChanged, [this] (int state) {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, Qt::Checked == state
|
||||
Q_EMIT configure (key_, empty, quint32_max, empty, Qt::Checked == state
|
||||
, quint32_max, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (tr_period_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
|
||||
, i, quint32_max, empty, empty, false);
|
||||
});
|
||||
connect (rx_df_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
|
||||
, quint32_max, i, empty, empty, false);
|
||||
});
|
||||
connect (dx_call_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, dx_call_line_edit_->text (), empty, false);
|
||||
});
|
||||
connect (dx_grid_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, empty, dx_grid_line_edit_->text (), false);
|
||||
});
|
||||
|
||||
@ -289,14 +289,14 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
halt_tx_button_ = control_button_box_->addButton (tr ("&Halt Tx"), QDialogButtonBox::ActionRole);
|
||||
connect (generate_messages_push_button_, &QAbstractButton::clicked, [this] (bool /*checked*/) {
|
||||
QString empty;
|
||||
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode ()
|
||||
Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
|
||||
, quint32_max, quint32_max, empty, empty, true);
|
||||
});
|
||||
connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
|
||||
Q_EMIT do_halt_tx (id_, true);
|
||||
Q_EMIT do_halt_tx (key_, true);
|
||||
});
|
||||
connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
|
||||
Q_EMIT do_halt_tx (id_, false);
|
||||
Q_EMIT do_halt_tx (key_, false);
|
||||
});
|
||||
content_layout_->addWidget (control_button_box_);
|
||||
|
||||
@ -318,13 +318,13 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
|
||||
// connect context menu actions
|
||||
connect (erase_action_, &QAction::triggered, [this] (bool /*checked*/) {
|
||||
Q_EMIT do_clear_decodes (id_);
|
||||
Q_EMIT do_clear_decodes (key_);
|
||||
});
|
||||
connect (erase_rx_frequency_action_, &QAction::triggered, [this] (bool /*checked*/) {
|
||||
Q_EMIT do_clear_decodes (id_, 1);
|
||||
Q_EMIT do_clear_decodes (key_, 1);
|
||||
});
|
||||
connect (erase_both_action_, &QAction::triggered, [this] (bool /*checked*/) {
|
||||
Q_EMIT do_clear_decodes (id_, 2);
|
||||
Q_EMIT do_clear_decodes (key_, 2);
|
||||
});
|
||||
|
||||
// connect up table view signals
|
||||
@ -335,7 +335,7 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
// tell new client about calls of interest
|
||||
for (int row = 0; row < calls_of_interest_->count (); ++row)
|
||||
{
|
||||
Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text (), QColor {Qt::blue}, QColor {Qt::yellow});
|
||||
Q_EMIT highlight_callsign (key_, calls_of_interest_->item (row)->text (), QColor {Qt::blue}, QColor {Qt::yellow});
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,7 +349,7 @@ void ClientWidget::closeEvent (QCloseEvent *e)
|
||||
{
|
||||
if (!done_)
|
||||
{
|
||||
Q_EMIT do_close (id_);
|
||||
Q_EMIT do_close (key_);
|
||||
e->ignore (); // defer closure until client actually closes
|
||||
}
|
||||
else
|
||||
@ -363,7 +363,7 @@ ClientWidget::~ClientWidget ()
|
||||
for (int row = 0; row < calls_of_interest_->count (); ++row)
|
||||
{
|
||||
// tell client to forget calls of interest
|
||||
Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text ());
|
||||
Q_EMIT highlight_callsign (key_, calls_of_interest_->item (row)->text ());
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,7 +395,7 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
|
||||
void ClientWidget::update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& dx_call
|
||||
, QString const& report, QString const& tx_mode, bool tx_enabled
|
||||
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
|
||||
, QString const& de_call, QString const& de_grid, QString const& dx_grid
|
||||
@ -403,7 +403,7 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const&
|
||||
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name)
|
||||
{
|
||||
if (id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
fast_mode_check_box_->setChecked (fast_mode);
|
||||
decodes_proxy_model_.de_call (de_call);
|
||||
@ -447,11 +447,11 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const&
|
||||
}
|
||||
}
|
||||
|
||||
void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
|
||||
void ClientWidget::decode_added (bool /*is_new*/, ClientKey const& key, QTime /*time*/, qint32 /*snr*/
|
||||
, float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/
|
||||
, QString const& /*message*/, bool /*low_confidence*/, bool /*off_air*/)
|
||||
{
|
||||
if (client_id == id_ && !columns_resized_)
|
||||
if (key == key_ && !columns_resized_)
|
||||
{
|
||||
decodes_stack_->setCurrentIndex (0);
|
||||
decodes_table_view_->resizeColumnsToContents ();
|
||||
@ -460,12 +460,12 @@ void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTim
|
||||
decodes_table_view_->scrollToBottom ();
|
||||
}
|
||||
|
||||
void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/
|
||||
void ClientWidget::beacon_spot_added (bool /*is_new*/, ClientKey const& key, QTime /*time*/, qint32 /*snr*/
|
||||
, float /*delta_time*/, Frequency /*delta_frequency*/, qint32 /*drift*/
|
||||
, QString const& /*callsign*/, QString const& /*grid*/, qint32 /*power*/
|
||||
, bool /*off_air*/)
|
||||
{
|
||||
if (client_id == id_ && !columns_resized_)
|
||||
if (key == key_ && !columns_resized_)
|
||||
{
|
||||
decodes_stack_->setCurrentIndex (1);
|
||||
beacons_table_view_->resizeColumnsToContents ();
|
||||
@ -474,9 +474,9 @@ void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id,
|
||||
beacons_table_view_->scrollToBottom ();
|
||||
}
|
||||
|
||||
void ClientWidget::decodes_cleared (QString const& client_id)
|
||||
void ClientWidget::decodes_cleared (ClientKey const& key)
|
||||
{
|
||||
if (client_id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
columns_resized_ = false;
|
||||
}
|
||||
|
@ -35,42 +35,44 @@ class ClientWidget
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
|
||||
, QString const& id, QString const& version, QString const& revision
|
||||
, ClientKey const& key, QString const& version, QString const& revision
|
||||
, QListWidget const * calls_of_interest, QWidget * parent = nullptr);
|
||||
void dispose ();
|
||||
~ClientWidget ();
|
||||
|
||||
bool fast_mode () const;
|
||||
|
||||
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call
|
||||
Q_SLOT void update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& dx_call
|
||||
, QString const& report, QString const& tx_mode, bool tx_enabled
|
||||
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
|
||||
, QString const& de_call, QString const& de_grid, QString const& dx_grid
|
||||
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
|
||||
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name);
|
||||
Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime, qint32 snr
|
||||
Q_SLOT void decode_added (bool is_new, ClientKey const& key, QTime, qint32 snr
|
||||
, float delta_time, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message, bool low_confidence, bool off_air);
|
||||
Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime, qint32 snr
|
||||
Q_SLOT void beacon_spot_added (bool is_new, ClientKey const& key, QTime, qint32 snr
|
||||
, float delta_time, Frequency delta_frequency, qint32 drift
|
||||
, QString const& callsign, QString const& grid, qint32 power
|
||||
, bool off_air);
|
||||
Q_SLOT void decodes_cleared (QString const& client_id);
|
||||
Q_SLOT void decodes_cleared (ClientKey const& key);
|
||||
|
||||
Q_SIGNAL void do_clear_decodes (QString const& id, quint8 window = 0);
|
||||
Q_SIGNAL void do_close (QString const& id);
|
||||
Q_SIGNAL void do_clear_decodes (ClientKey const& key, quint8 window = 0);
|
||||
Q_SIGNAL void do_close (ClientKey const& key);
|
||||
Q_SIGNAL void do_reply (QModelIndex const&, quint8 modifier);
|
||||
Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only);
|
||||
Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool);
|
||||
Q_SIGNAL void location (QString const& id, QString const& text);
|
||||
Q_SIGNAL void highlight_callsign (QString const& id, QString const& call
|
||||
Q_SIGNAL void do_halt_tx (ClientKey const& key, bool auto_only);
|
||||
Q_SIGNAL void do_free_text (ClientKey const& key, QString const& text, bool);
|
||||
Q_SIGNAL void location (ClientKey const& key, QString const& text);
|
||||
Q_SIGNAL void highlight_callsign (ClientKey const& key, QString const& call
|
||||
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
|
||||
, bool last_only = false);
|
||||
Q_SIGNAL void switch_configuration (QString const& id, QString const& configuration_name);
|
||||
Q_SIGNAL void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
|
||||
Q_SIGNAL void switch_configuration (ClientKey const& key, QString const& configuration_name);
|
||||
Q_SIGNAL void configure (ClientKey const& key, QString const& mode, quint32 frequency_tolerance
|
||||
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
|
||||
, QString const& dx_call, QString const& dx_grid, bool generate_messages);
|
||||
|
||||
@ -79,7 +81,7 @@ private:
|
||||
: public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
IdFilterModel (QString const& client_id, QObject * = nullptr);
|
||||
IdFilterModel (ClientKey const& key, QObject * = nullptr);
|
||||
|
||||
void de_call (QString const&);
|
||||
void rx_df (quint32);
|
||||
@ -88,7 +90,7 @@ private:
|
||||
private:
|
||||
bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override;
|
||||
|
||||
QString client_id_;
|
||||
ClientKey key_;
|
||||
QString call_;
|
||||
QRegularExpression base_call_re_;
|
||||
quint32 rx_df_;
|
||||
@ -96,7 +98,7 @@ private:
|
||||
|
||||
void closeEvent (QCloseEvent *) override;
|
||||
|
||||
QString id_;
|
||||
ClientKey key_;
|
||||
bool done_;
|
||||
QListWidget const * calls_of_interest_;
|
||||
IdFilterModel decodes_proxy_model_;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <QStandardItem>
|
||||
#include <QModelIndex>
|
||||
#include <QVariant>
|
||||
#include <QTime>
|
||||
#include <QString>
|
||||
#include <QFont>
|
||||
@ -33,10 +34,13 @@ namespace
|
||||
|
||||
QFont text_font {"Courier", 10};
|
||||
|
||||
QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode, QString const& message
|
||||
, bool low_confidence, bool off_air, bool is_fast)
|
||||
QList<QStandardItem *> make_row (MessageServer::ClientKey const& key, QTime time, qint32 snr
|
||||
, float delta_time, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message, bool low_confidence, bool off_air, bool is_fast)
|
||||
{
|
||||
auto client_item = new QStandardItem {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())};
|
||||
client_item->setData (QVariant::fromValue (key));
|
||||
|
||||
auto time_item = new QStandardItem {time.toString (is_fast || "~" == mode ? "hh:mm:ss" : "hh:mm")};
|
||||
time_item->setData (time);
|
||||
time_item->setTextAlignment (Qt::AlignRight);
|
||||
@ -63,7 +67,7 @@ namespace
|
||||
live->setTextAlignment (Qt::AlignHCenter);
|
||||
|
||||
QList<QStandardItem *> row {
|
||||
new QStandardItem {client_id}, time_item, snr_item, dt, df, md, confidence, live, new QStandardItem {message}};
|
||||
client_item, time_item, snr_item, dt, df, md, confidence, live, new QStandardItem {message}};
|
||||
Q_FOREACH (auto& item, row)
|
||||
{
|
||||
item->setEditable (false);
|
||||
@ -84,7 +88,7 @@ DecodesModel::DecodesModel (QObject * parent)
|
||||
}
|
||||
}
|
||||
|
||||
void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
|
||||
void DecodesModel::add_decode (bool is_new, ClientKey const& key, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode, QString const& message
|
||||
, bool low_confidence, bool off_air, bool is_fast)
|
||||
{
|
||||
@ -93,7 +97,7 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time
|
||||
int target_row {-1};
|
||||
for (auto row = 0; row < rowCount (); ++row)
|
||||
{
|
||||
if (data (index (row, 0)).toString () == client_id)
|
||||
if (item (row, 0)->data ().value<ClientKey> () == key)
|
||||
{
|
||||
auto row_time = item (row, 1)->data ().toTime ();
|
||||
if (row_time == time
|
||||
@ -115,21 +119,21 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time
|
||||
}
|
||||
if (target_row >= 0)
|
||||
{
|
||||
insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, delta_frequency, mode
|
||||
insertRow (target_row + 1, make_row (key, time, snr, delta_time, delta_frequency, mode
|
||||
, message, low_confidence, off_air, is_fast));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
appendRow (make_row (client_id, time, snr, delta_time, delta_frequency, mode, message, low_confidence
|
||||
appendRow (make_row (key, time, snr, delta_time, delta_frequency, mode, message, low_confidence
|
||||
, off_air, is_fast));
|
||||
}
|
||||
|
||||
void DecodesModel::decodes_cleared (QString const& client_id)
|
||||
void DecodesModel::decodes_cleared (ClientKey const& key)
|
||||
{
|
||||
for (auto row = rowCount () - 1; row >= 0; --row)
|
||||
{
|
||||
if (data (index (row, 0)).toString () == client_id)
|
||||
if (item (row, 0)->data ().value<ClientKey> () == key)
|
||||
{
|
||||
removeRow (row);
|
||||
}
|
||||
@ -139,7 +143,7 @@ void DecodesModel::decodes_cleared (QString const& client_id)
|
||||
void DecodesModel::do_reply (QModelIndex const& source, quint8 modifiers)
|
||||
{
|
||||
auto row = source.row ();
|
||||
Q_EMIT reply (data (index (row, 0)).toString ()
|
||||
Q_EMIT reply (item (row, 0)->data ().value<ClientKey> ()
|
||||
, item (row, 1)->data ().toTime ()
|
||||
, item (row, 2)->data ().toInt ()
|
||||
, item (row, 3)->data ().toFloat ()
|
||||
|
@ -5,8 +5,6 @@
|
||||
|
||||
#include "MessageServer.hpp"
|
||||
|
||||
using Frequency = MessageServer::Frequency;
|
||||
|
||||
class QTime;
|
||||
class QString;
|
||||
class QModelIndex;
|
||||
@ -28,16 +26,18 @@ class DecodesModel
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
explicit DecodesModel (QObject * parent = nullptr);
|
||||
|
||||
Q_SLOT void add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
|
||||
Q_SLOT void add_decode (bool is_new, ClientKey const&, QTime, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode, QString const& message
|
||||
, bool low_confidence, bool off_air, bool is_fast);
|
||||
Q_SLOT void decodes_cleared (QString const& client_id);
|
||||
Q_SLOT void decodes_cleared (ClientKey const&);
|
||||
Q_SLOT void do_reply (QModelIndex const& source, quint8 modifiers);
|
||||
|
||||
Q_SIGNAL void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
|
||||
Q_SIGNAL void reply (ClientKey const&, QTime, qint32 snr, float delta_time, quint32 delta_frequency
|
||||
, QString const& mode, QString const& message, bool low_confidence, quint8 modifiers);
|
||||
};
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QDateTime>
|
||||
#include <QNetworkInterface>
|
||||
#include <QSet>
|
||||
|
||||
#include "DecodesModel.hpp"
|
||||
#include "BeaconsModel.hpp"
|
||||
@ -37,8 +39,10 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
|
||||
, decodes_model_ {new DecodesModel {this}}
|
||||
, beacons_model_ {new BeaconsModel {this}}
|
||||
, server_ {new MessageServer {this}}
|
||||
, multicast_group_line_edit_ {new QLineEdit}
|
||||
, log_table_view_ {new QTableView}
|
||||
, port_spin_box_ {new QSpinBox {this}}
|
||||
, multicast_group_line_edit_ {new QLineEdit {this}}
|
||||
, network_interfaces_combo_box_ {new CheckableItemComboBox {this}}
|
||||
, log_table_view_ {new QTableView {this}}
|
||||
, add_call_of_interest_action_ {new QAction {tr ("&Add callsign"), this}}
|
||||
, delete_call_of_interest_action_ {new QAction {tr ("&Delete callsign"), this}}
|
||||
, last_call_of_interest_action_ {new QAction {tr ("&Highlight last only"), this}}
|
||||
@ -68,16 +72,71 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
|
||||
auto central_layout = new QVBoxLayout;
|
||||
|
||||
// server details
|
||||
auto port_spin_box = new QSpinBox;
|
||||
port_spin_box->setMinimum (1);
|
||||
port_spin_box->setMaximum (std::numeric_limits<port_type>::max ());
|
||||
port_spin_box_->setMinimum (1);
|
||||
port_spin_box_->setMaximum (std::numeric_limits<port_type>::max ());
|
||||
auto group_box_layout = new QFormLayout;
|
||||
group_box_layout->addRow (tr ("Port number:"), port_spin_box);
|
||||
group_box_layout->addRow (tr ("Port number:"), port_spin_box_);
|
||||
group_box_layout->addRow (tr ("Multicast Group (blank for unicast server):"), multicast_group_line_edit_);
|
||||
group_box_layout->addRow (tr ("Network interfaces:"), network_interfaces_combo_box_);
|
||||
int row;
|
||||
QFormLayout::ItemRole role;
|
||||
group_box_layout->getWidgetPosition (network_interfaces_combo_box_, &row, &role);
|
||||
Q_ASSERT (row >= 0);
|
||||
network_interfaces_form_label_widget_ = static_cast<QLabel *> (group_box_layout->itemAt (row, QFormLayout::LabelRole)->widget ());
|
||||
network_interfaces_form_label_widget_->hide ();
|
||||
network_interfaces_form_label_widget_->buddy ()->hide ();
|
||||
connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this] {
|
||||
if (multicast_group_line_edit_->text ().size ())
|
||||
{
|
||||
network_interfaces_form_label_widget_->show ();
|
||||
network_interfaces_form_label_widget_->buddy ()->show ();
|
||||
}
|
||||
else
|
||||
{
|
||||
network_interfaces_form_label_widget_->hide ();
|
||||
network_interfaces_form_label_widget_->buddy ()->hide ();
|
||||
}
|
||||
});
|
||||
auto group_box = new QGroupBox {tr ("Server Details")};
|
||||
group_box->setLayout (group_box_layout);
|
||||
central_layout->addWidget (group_box);
|
||||
|
||||
// populate network interface list
|
||||
for (auto const& net_if : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
auto flags = QNetworkInterface::IsRunning | QNetworkInterface::CanMulticast;
|
||||
if ((net_if.flags () & flags) == flags)
|
||||
{
|
||||
if (net_if.flags () & QNetworkInterface::IsLoopBack)
|
||||
{
|
||||
loopback_interface_name_ = net_if.name ();
|
||||
}
|
||||
auto item = network_interfaces_combo_box_->addCheckItem (net_if.humanReadableName ()
|
||||
, net_if.name ()
|
||||
, Qt::Unchecked);
|
||||
auto tip = QString {"name(index): %1(%2) - %3"}
|
||||
.arg (net_if.name ()).arg (net_if.index ())
|
||||
.arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down");
|
||||
auto hw_addr = net_if.hardwareAddress ();
|
||||
if (hw_addr.size ())
|
||||
{
|
||||
tip += QString {"\nhw: %1"}.arg (net_if.hardwareAddress ());
|
||||
}
|
||||
auto aes = net_if.addressEntries ();
|
||||
if (aes.size ())
|
||||
{
|
||||
tip += "\naddresses:";
|
||||
for (auto const& ae : aes)
|
||||
{
|
||||
tip += QString {"\n ip: %1/%2"}.arg (ae.ip ().toString ()).arg (ae.prefixLength ());
|
||||
}
|
||||
}
|
||||
item->setToolTip (tip);
|
||||
}
|
||||
}
|
||||
connect (network_interfaces_combo_box_, &QComboBox::currentTextChanged, this, &MessageAggregatorMainWindow::validate_network_interfaces);
|
||||
validate_network_interfaces (QString {});
|
||||
|
||||
log_table_view_->setModel (log_);
|
||||
log_table_view_->verticalHeader ()->hide ();
|
||||
central_layout->addWidget (log_table_view_);
|
||||
@ -184,30 +243,52 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
|
||||
connect (server_, &MessageServer::client_closed, this, &MessageAggregatorMainWindow::remove_client);
|
||||
connect (server_, &MessageServer::client_closed, decodes_model_, &DecodesModel::decodes_cleared);
|
||||
connect (server_, &MessageServer::client_closed, beacons_model_, &BeaconsModel::decodes_cleared);
|
||||
connect (server_, &MessageServer::decode, [this] (bool is_new, QString const& id, QTime time
|
||||
connect (server_, &MessageServer::decode, [this] (bool is_new, ClientKey const& key, QTime time
|
||||
, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message, bool low_confidence
|
||||
, bool off_air) {
|
||||
decodes_model_->add_decode (is_new, id, time, snr, delta_time, delta_frequency, mode, message
|
||||
, low_confidence, off_air, dock_widgets_[id]->fast_mode ());});
|
||||
decodes_model_->add_decode (is_new, key, time, snr, delta_time
|
||||
, delta_frequency, mode, message
|
||||
, low_confidence, off_air
|
||||
, dock_widgets_[key]->fast_mode ());
|
||||
});
|
||||
connect (server_, &MessageServer::WSPR_decode, beacons_model_, &BeaconsModel::add_beacon_spot);
|
||||
connect (server_, &MessageServer::decodes_cleared, decodes_model_, &DecodesModel::decodes_cleared);
|
||||
connect (server_, &MessageServer::decodes_cleared, beacons_model_, &BeaconsModel::decodes_cleared);
|
||||
connect (decodes_model_, &DecodesModel::reply, server_, &MessageServer::reply);
|
||||
|
||||
// UI behaviour
|
||||
connect (port_spin_box, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged)
|
||||
, [this] (port_type port) {server_->start (port);});
|
||||
connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this, port_spin_box] () {
|
||||
server_->start (port_spin_box->value (), QHostAddress {multicast_group_line_edit_->text ()});
|
||||
});
|
||||
connect (port_spin_box_, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged)
|
||||
, [this] (int /*port*/) {restart_server ();});
|
||||
connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this] () {restart_server ();});
|
||||
connect (network_interfaces_combo_box_, &QComboBox::currentTextChanged, [this] () {restart_server ();});
|
||||
|
||||
port_spin_box->setValue (2237); // start up in unicast mode
|
||||
port_spin_box_->setValue (2237); // start up in unicast mode
|
||||
show ();
|
||||
}
|
||||
|
||||
void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call
|
||||
void MessageAggregatorMainWindow::restart_server ()
|
||||
{
|
||||
QSet<QString> net_ifs;
|
||||
if (network_interfaces_combo_box_->isVisible ())
|
||||
{
|
||||
auto model = static_cast<QStandardItemModel *> (network_interfaces_combo_box_->model ());
|
||||
for (int row = 0; row < model->rowCount (); ++row)
|
||||
{
|
||||
if (Qt::Checked == model->item (row)->checkState ())
|
||||
{
|
||||
net_ifs << model->item (row)->data ().toString ();
|
||||
}
|
||||
}
|
||||
}
|
||||
server_->start (port_spin_box_->value ()
|
||||
, QHostAddress {multicast_group_line_edit_->text ()}
|
||||
, net_ifs);
|
||||
}
|
||||
|
||||
void MessageAggregatorMainWindow::log_qso (ClientKey const& /*key*/, QDateTime time_off
|
||||
, QString const& dx_call
|
||||
, QString const& dx_grid, Frequency dial_frequency, QString const& mode
|
||||
, QString const& report_sent, QString const& report_received
|
||||
, QString const& tx_power, QString const& comments
|
||||
@ -240,9 +321,9 @@ void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time
|
||||
log_table_view_->scrollToBottom ();
|
||||
}
|
||||
|
||||
void MessageAggregatorMainWindow::add_client (QString const& id, QString const& version, QString const& revision)
|
||||
void MessageAggregatorMainWindow::add_client (ClientKey const& key, QString const& version, QString const& revision)
|
||||
{
|
||||
auto dock = new ClientWidget {decodes_model_, beacons_model_, id, version, revision, calls_of_interest_, this};
|
||||
auto dock = new ClientWidget {decodes_model_, beacons_model_, key, version, revision, calls_of_interest_, this};
|
||||
dock->setAttribute (Qt::WA_DeleteOnClose);
|
||||
auto view_action = dock->toggleViewAction ();
|
||||
view_action->setEnabled (true);
|
||||
@ -262,13 +343,13 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const&
|
||||
connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign);
|
||||
connect (dock, &ClientWidget::switch_configuration, server_, &MessageServer::switch_configuration);
|
||||
connect (dock, &ClientWidget::configure, server_, &MessageServer::configure);
|
||||
dock_widgets_[id] = dock;
|
||||
server_->replay (id); // request decodes and status
|
||||
dock_widgets_[key] = dock;
|
||||
server_->replay (key); // request decodes and status
|
||||
}
|
||||
|
||||
void MessageAggregatorMainWindow::remove_client (QString const& id)
|
||||
void MessageAggregatorMainWindow::remove_client (ClientKey const& key)
|
||||
{
|
||||
auto iter = dock_widgets_.find (id);
|
||||
auto iter = dock_widgets_.find (key);
|
||||
if (iter != std::end (dock_widgets_))
|
||||
{
|
||||
(*iter)->dispose ();
|
||||
@ -287,9 +368,35 @@ MessageAggregatorMainWindow::~MessageAggregatorMainWindow ()
|
||||
void MessageAggregatorMainWindow::change_highlighting (QString const& call, QColor const& bg, QColor const& fg
|
||||
, bool last_only)
|
||||
{
|
||||
for (auto id : dock_widgets_.keys ())
|
||||
for (auto key : dock_widgets_.keys ())
|
||||
{
|
||||
server_->highlight_callsign (id, call, bg, fg, last_only);
|
||||
server_->highlight_callsign (key, call, bg, fg, last_only);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageAggregatorMainWindow::validate_network_interfaces (QString const& /*text*/)
|
||||
{
|
||||
auto model = static_cast<QStandardItemModel *> (network_interfaces_combo_box_->model ());
|
||||
bool has_checked {false};
|
||||
int loopback_row {-1};
|
||||
for (int row = 0; row < model->rowCount (); ++row)
|
||||
{
|
||||
if (model->item (row)->data ().toString () == loopback_interface_name_)
|
||||
{
|
||||
loopback_row = row;
|
||||
}
|
||||
else if (Qt::Checked == model->item (row)->checkState ())
|
||||
{
|
||||
has_checked = true;
|
||||
}
|
||||
}
|
||||
if (loopback_row >= 0)
|
||||
{
|
||||
if (!has_checked)
|
||||
{
|
||||
model->item (loopback_row)->setCheckState (Qt::Checked);
|
||||
}
|
||||
model->item (loopback_row)->setEnabled (has_checked);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <QString>
|
||||
|
||||
#include "MessageServer.hpp"
|
||||
#include "widgets/CheckableItemComboBox.hpp"
|
||||
|
||||
class QDateTime;
|
||||
class QStandardItemModel;
|
||||
@ -16,6 +17,8 @@ class QLineEdit;
|
||||
class QTableView;
|
||||
class ClientWidget;
|
||||
class QListWidget;
|
||||
class QLabel;
|
||||
class QSpinBox;
|
||||
|
||||
using Frequency = MessageServer::Frequency;
|
||||
|
||||
@ -24,11 +27,13 @@ class MessageAggregatorMainWindow
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
MessageAggregatorMainWindow ();
|
||||
~MessageAggregatorMainWindow ();
|
||||
|
||||
Q_SLOT void log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call, QString const& dx_grid
|
||||
Q_SLOT void log_qso (ClientKey const&, QDateTime time_off, QString const& dx_call, QString const& dx_grid
|
||||
, Frequency dial_frequency, QString const& mode, QString const& report_sent
|
||||
, QString const& report_received, QString const& tx_power, QString const& comments
|
||||
, QString const& name, QDateTime time_on, QString const& operator_call
|
||||
@ -36,13 +41,15 @@ public:
|
||||
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode);
|
||||
|
||||
private:
|
||||
void add_client (QString const& id, QString const& version, QString const& revision);
|
||||
void remove_client (QString const& id);
|
||||
void restart_server ();
|
||||
void add_client (ClientKey const&, QString const& version, QString const& revision);
|
||||
void remove_client (ClientKey const&);
|
||||
void change_highlighting (QString const& call, QColor const& bg = QColor {}, QColor const& fg = QColor {},
|
||||
bool last_only = false);
|
||||
Q_SLOT void validate_network_interfaces (QString const&);
|
||||
|
||||
// maps client id to widgets
|
||||
using ClientsDictionary = QHash<QString, ClientWidget *>;
|
||||
using ClientsDictionary = QHash<ClientKey, ClientWidget *>;
|
||||
ClientsDictionary dock_widgets_;
|
||||
|
||||
QStandardItemModel * log_;
|
||||
@ -50,7 +57,11 @@ private:
|
||||
DecodesModel * decodes_model_;
|
||||
BeaconsModel * beacons_model_;
|
||||
MessageServer * server_;
|
||||
QSpinBox * port_spin_box_;
|
||||
QLineEdit * multicast_group_line_edit_;
|
||||
CheckableItemComboBox * network_interfaces_combo_box_;
|
||||
QString loopback_interface_name_;
|
||||
QLabel * network_interfaces_form_label_widget_;
|
||||
QTableView * log_table_view_;
|
||||
QListWidget * calls_of_interest_;
|
||||
QAction * add_call_of_interest_action_;
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
#include <QNetworkInterface>
|
||||
#include <QUdpSocket>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QHash>
|
||||
|
||||
@ -32,7 +31,6 @@ public:
|
||||
: self_ {self}
|
||||
, version_ {version}
|
||||
, revision_ {revision}
|
||||
, port_ {0u}
|
||||
, clock_ {new QTimer {this}}
|
||||
{
|
||||
// register the required types with Qt
|
||||
@ -78,15 +76,14 @@ public:
|
||||
MessageServer * self_;
|
||||
QString version_;
|
||||
QString revision_;
|
||||
port_type port_;
|
||||
QHostAddress multicast_group_address_;
|
||||
QSet<QString> network_interfaces_;
|
||||
static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint;
|
||||
struct Client
|
||||
{
|
||||
Client () = default;
|
||||
Client (QHostAddress const& sender_address, port_type const& sender_port)
|
||||
: sender_address_ {sender_address}
|
||||
, sender_port_ {sender_port}
|
||||
Client (port_type const& sender_port)
|
||||
: sender_port_ {sender_port}
|
||||
, negotiated_schema_number_ {2} // not 1 because it's broken
|
||||
, last_activity_ {QDateTime::currentDateTime ()}
|
||||
{
|
||||
@ -94,12 +91,11 @@ public:
|
||||
Client (Client const&) = default;
|
||||
Client& operator= (Client const&) = default;
|
||||
|
||||
QHostAddress sender_address_;
|
||||
port_type sender_port_;
|
||||
quint32 negotiated_schema_number_;
|
||||
QDateTime last_activity_;
|
||||
};
|
||||
QHash<QString, Client> clients_; // maps id to Client
|
||||
QHash<ClientKey, Client> clients_; // maps id to Client
|
||||
QTimer * clock_;
|
||||
};
|
||||
|
||||
@ -109,56 +105,39 @@ MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
|
||||
|
||||
void MessageServer::impl::leave_multicast_group ()
|
||||
{
|
||||
if (!multicast_group_address_.isNull () && BoundState == state ()
|
||||
#if QT_VERSION >= 0x050600
|
||||
&& multicast_group_address_.isMulticast ()
|
||||
#endif
|
||||
)
|
||||
if (BoundState == state () && is_multicast_address (multicast_group_address_))
|
||||
{
|
||||
for (auto const& interface : QNetworkInterface::allInterfaces ())
|
||||
for (auto const& if_name : network_interfaces_)
|
||||
{
|
||||
if (QNetworkInterface::CanMulticast & interface.flags ())
|
||||
{
|
||||
leaveMulticastGroup (multicast_group_address_, interface);
|
||||
}
|
||||
leaveMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::join_multicast_group ()
|
||||
{
|
||||
if (BoundState == state ()
|
||||
&& !multicast_group_address_.isNull ()
|
||||
#if QT_VERSION >= 0x050600
|
||||
&& multicast_group_address_.isMulticast ()
|
||||
#endif
|
||||
)
|
||||
if (BoundState == state () && is_multicast_address (multicast_group_address_))
|
||||
{
|
||||
auto mcast_iface = multicastInterface ();
|
||||
if (IPv4Protocol == multicast_group_address_.protocol ()
|
||||
&& IPv4Protocol != localAddress ().protocol ())
|
||||
if (network_interfaces_.size ())
|
||||
{
|
||||
close ();
|
||||
bind (QHostAddress::AnyIPv4, port_, bind_mode_);
|
||||
}
|
||||
bool joined {false};
|
||||
for (auto const& interface : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
if (QNetworkInterface::CanMulticast & interface.flags ())
|
||||
for (auto const& if_name : network_interfaces_)
|
||||
{
|
||||
// Windows requires outgoing interface to match
|
||||
// interface to be joined while joining, at least for
|
||||
// IPv4 it seems to
|
||||
setMulticastInterface (interface);
|
||||
|
||||
joined |= joinMulticastGroup (multicast_group_address_, interface);
|
||||
joinMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
|
||||
}
|
||||
}
|
||||
if (!joined)
|
||||
else
|
||||
{
|
||||
multicast_group_address_.clear ();
|
||||
// find the loop-back interface and join on that
|
||||
for (auto const& net_if : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
auto flags = QNetworkInterface::IsUp | QNetworkInterface::IsLoopBack | QNetworkInterface::CanMulticast;
|
||||
if ((net_if.flags () & flags) == flags)
|
||||
{
|
||||
joinMulticastGroup (multicast_group_address_, net_if);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setMulticastInterface (mcast_iface);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,9 +168,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
auto id = in.id ();
|
||||
if (OK == check_status (in))
|
||||
{
|
||||
if (!clients_.contains (id))
|
||||
auto client_key = ClientKey {sender, id};
|
||||
if (!clients_.contains (client_key))
|
||||
{
|
||||
auto& client = (clients_[id] = {sender, sender_port});
|
||||
auto& client = (clients_[client_key] = {sender_port});
|
||||
QByteArray client_version;
|
||||
QByteArray client_revision;
|
||||
|
||||
@ -212,7 +192,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
<< version_.toUtf8 () << revision_.toUtf8 ();
|
||||
if (impl::OK == check_status (hb))
|
||||
{
|
||||
writeDatagram (message, client.sender_address_, client.sender_port_);
|
||||
writeDatagram (message, client_key.first, sender_port);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -222,10 +202,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
// we don't care if this fails to read
|
||||
in >> client_version >> client_revision;
|
||||
}
|
||||
Q_EMIT self_->client_opened (id, QString::fromUtf8 (client_version),
|
||||
Q_EMIT self_->client_opened (client_key, QString::fromUtf8 (client_version),
|
||||
QString::fromUtf8 (client_revision));
|
||||
}
|
||||
clients_[id].last_activity_ = QDateTime::currentDateTime ();
|
||||
clients_[client_key].last_activity_ = QDateTime::currentDateTime ();
|
||||
|
||||
//
|
||||
// message format is described in NetworkMessage.hpp
|
||||
@ -237,7 +217,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
break;
|
||||
|
||||
case NetworkMessage::Clear:
|
||||
Q_EMIT self_->decodes_cleared (id);
|
||||
Q_EMIT self_->decodes_cleared (client_key);
|
||||
break;
|
||||
|
||||
case NetworkMessage::Status:
|
||||
@ -268,7 +248,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
>> fast_mode >> special_op_mode >> frequency_tolerance >> tr_period >> configuration_name;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call)
|
||||
Q_EMIT self_->status_update (client_key, f, QString::fromUtf8 (mode)
|
||||
, QString::fromUtf8 (dx_call)
|
||||
, QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)
|
||||
, tx_enabled, transmitting, decoding, rx_df, tx_df
|
||||
, QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
|
||||
@ -296,7 +277,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
>> message >> low_confidence >> off_air;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->decode (is_new, id, time, snr, delta_time, delta_frequency
|
||||
Q_EMIT self_->decode (is_new, client_key, time, snr, delta_time, delta_frequency
|
||||
, QString::fromUtf8 (mode), QString::fromUtf8 (message)
|
||||
, low_confidence, off_air);
|
||||
}
|
||||
@ -320,7 +301,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
>> off_air;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->WSPR_decode (is_new, id, time, snr, delta_time, frequency, drift
|
||||
Q_EMIT self_->WSPR_decode (is_new, client_key, time, snr, delta_time, frequency, drift
|
||||
, QString::fromUtf8 (callsign), QString::fromUtf8 (grid)
|
||||
, power, off_air);
|
||||
}
|
||||
@ -351,8 +332,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
>> exchange_sent >> exchange_rcvd >> prop_mode;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
|
||||
, dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent)
|
||||
Q_EMIT self_->qso_logged (client_key, time_off, QString::fromUtf8 (dx_call)
|
||||
, QString::fromUtf8 (dx_grid)
|
||||
, dial_frequency, QString::fromUtf8 (mode)
|
||||
, QString::fromUtf8 (report_sent)
|
||||
, QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power)
|
||||
, QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on
|
||||
, QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call)
|
||||
@ -363,8 +346,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
break;
|
||||
|
||||
case NetworkMessage::Close:
|
||||
Q_EMIT self_->client_closed (id);
|
||||
clients_.remove (id);
|
||||
Q_EMIT self_->client_closed (client_key);
|
||||
clients_.remove (client_key);
|
||||
break;
|
||||
|
||||
case NetworkMessage::LoggedADIF:
|
||||
@ -373,7 +356,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
in >> ADIF;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->logged_ADIF (id, ADIF);
|
||||
Q_EMIT self_->logged_ADIF (client_key, ADIF);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -406,7 +389,7 @@ void MessageServer::impl::tick ()
|
||||
{
|
||||
if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse))
|
||||
{
|
||||
Q_EMIT self_->clear_decodes (iter.key ());
|
||||
Q_EMIT self_->decodes_cleared (iter.key ());
|
||||
Q_EMIT self_->client_closed (iter.key ());
|
||||
iter = clients_.erase (iter); // safe while iterating as doesn't rehash
|
||||
}
|
||||
@ -448,152 +431,162 @@ MessageServer::MessageServer (QObject * parent, QString const& version, QString
|
||||
{
|
||||
}
|
||||
|
||||
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address)
|
||||
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address
|
||||
, QSet<QString> const& network_interface_names)
|
||||
{
|
||||
if (port != m_->port_
|
||||
|| multicast_group_address != m_->multicast_group_address_)
|
||||
// qDebug () << "MessageServer::start port:" << port << "multicast addr:" << multicast_group_address.toString () << "network interfaces:" << network_interface_names;
|
||||
if (port != m_->localPort ()
|
||||
|| multicast_group_address != m_->multicast_group_address_
|
||||
|| network_interface_names != m_->network_interfaces_)
|
||||
{
|
||||
m_->leave_multicast_group ();
|
||||
if (impl::BoundState == m_->state ())
|
||||
if (impl::UnconnectedState != m_->state ())
|
||||
{
|
||||
m_->close ();
|
||||
}
|
||||
m_->multicast_group_address_ = multicast_group_address;
|
||||
auto address = m_->multicast_group_address_.isNull ()
|
||||
|| impl::IPv4Protocol != m_->multicast_group_address_.protocol () ? QHostAddress::Any : QHostAddress::AnyIPv4;
|
||||
if (port && m_->bind (address, port, m_->bind_mode_))
|
||||
if (!(multicast_group_address.isNull () || is_multicast_address (multicast_group_address)))
|
||||
{
|
||||
m_->port_ = port;
|
||||
m_->join_multicast_group ();
|
||||
Q_EMIT error ("Invalid multicast group address");
|
||||
}
|
||||
else if (is_MAC_ambiguous_multicast_address (multicast_group_address))
|
||||
{
|
||||
Q_EMIT error ("MAC-ambiguous IPv4 multicast group address not supported");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_->port_ = 0;
|
||||
m_->multicast_group_address_ = multicast_group_address;
|
||||
m_->network_interfaces_ = network_interface_names;
|
||||
QHostAddress local_addr {is_multicast_address (multicast_group_address)
|
||||
&& impl::IPv4Protocol == multicast_group_address.protocol () ? QHostAddress::AnyIPv4 : QHostAddress::Any};
|
||||
if (port && m_->bind (local_addr, port, m_->bind_mode_))
|
||||
{
|
||||
m_->join_multicast_group ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::clear_decodes (QString const& id, quint8 window)
|
||||
void MessageServer::clear_decodes (ClientKey const& key, quint8 window)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Clear, id, (*iter).negotiated_schema_number_};
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Clear, key.second, (*iter).negotiated_schema_number_};
|
||||
out << window;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time
|
||||
void MessageServer::reply (ClientKey const& key, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message_text, bool low_confidence, quint8 modifiers)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Reply, id, (*iter).negotiated_schema_number_};
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Reply, key.second, (*iter).negotiated_schema_number_};
|
||||
out << time << snr << delta_time << delta_frequency << mode.toUtf8 ()
|
||||
<< message_text.toUtf8 () << low_confidence << modifiers;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::replay (QString const& id)
|
||||
void MessageServer::replay (ClientKey const& key)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Replay, id, (*iter).negotiated_schema_number_};
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Replay, key.second, (*iter).negotiated_schema_number_};
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::close (QString const& id)
|
||||
void MessageServer::close (ClientKey const& key)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Close, id, (*iter).negotiated_schema_number_};
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Close, key.second, (*iter).negotiated_schema_number_};
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::halt_tx (QString const& id, bool auto_only)
|
||||
void MessageServer::halt_tx (ClientKey const& key, bool auto_only)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, id, (*iter).negotiated_schema_number_};
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, key.second, (*iter).negotiated_schema_number_};
|
||||
out << auto_only;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::free_text (QString const& id, QString const& text, bool send)
|
||||
void MessageServer::free_text (ClientKey const& key, QString const& text, bool send)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::FreeText, id, (*iter).negotiated_schema_number_};
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::FreeText, key.second, (*iter).negotiated_schema_number_};
|
||||
out << text.toUtf8 () << send;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::location (QString const& id, QString const& loc)
|
||||
void MessageServer::location (ClientKey const& key, QString const& loc)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Location, id, (*iter).negotiated_schema_number_};
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Location, key.second, (*iter).negotiated_schema_number_};
|
||||
out << loc.toUtf8 ();
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::highlight_callsign (QString const& id, QString const& callsign
|
||||
void MessageServer::highlight_callsign (ClientKey const& key, QString const& callsign
|
||||
, QColor const& bg, QColor const& fg, bool last_only)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, id, (*iter).negotiated_schema_number_};
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, key.second, (*iter).negotiated_schema_number_};
|
||||
out << callsign.toUtf8 () << bg << fg << last_only;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::switch_configuration (QString const& id, QString const& configuration_name)
|
||||
void MessageServer::switch_configuration (ClientKey const& key, QString const& configuration_name)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, id, (*iter).negotiated_schema_number_};
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, key.second, (*iter).negotiated_schema_number_};
|
||||
out << configuration_name.toUtf8 ();
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::configure (QString const& id, QString const& mode, quint32 frequency_tolerance
|
||||
void MessageServer::configure (ClientKey const& key, QString const& mode, quint32 frequency_tolerance
|
||||
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
|
||||
, QString const& dx_call, QString const& dx_grid, bool generate_messages)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Configure, id, (*iter).negotiated_schema_number_};
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Configure, key.second, (*iter).negotiated_schema_number_};
|
||||
out << mode.toUtf8 () << frequency_tolerance << submode.toUtf8 () << fast_mode << tr_period << rx_df
|
||||
<< dx_call.toUtf8 () << dx_grid.toUtf8 () << generate_messages;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
m_->send_message (out, message, key.first, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
#define MESSAGE_SERVER_HPP__
|
||||
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QSet>
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include <QHostAddress>
|
||||
@ -31,6 +34,7 @@ class UDP_EXPORT MessageServer
|
||||
public:
|
||||
using port_type = quint16;
|
||||
using Frequency = Radio::Frequency;
|
||||
using ClientKey = QPair<QHostAddress, QString>;
|
||||
|
||||
MessageServer (QObject * parent = nullptr,
|
||||
QString const& version = QString {}, QString const& revision = QString {});
|
||||
@ -38,77 +42,77 @@ public:
|
||||
// start or restart the server, if the multicast_group_address
|
||||
// argument is given it is assumed to be a multicast group address
|
||||
// which the server will join
|
||||
Q_SLOT void start (port_type port,
|
||||
QHostAddress const& multicast_group_address = QHostAddress {});
|
||||
Q_SLOT void start (port_type port
|
||||
, QHostAddress const& multicast_group_address = QHostAddress {}
|
||||
, QSet<QString> const& network_interface_names = QSet<QString> {});
|
||||
|
||||
// ask the client to clear one or both of the decode windows
|
||||
Q_SLOT void clear_decodes (QString const& id, quint8 window = 0);
|
||||
Q_SLOT void clear_decodes (ClientKey const&, quint8 window = 0);
|
||||
|
||||
// ask the client with identification 'id' to make the same action
|
||||
// as a double click on the decode would
|
||||
//
|
||||
// note that the client is not obliged to take any action and only
|
||||
// takes any action if the decode is present and is a CQ or QRZ message
|
||||
Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
|
||||
Q_SLOT void reply (ClientKey const&, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
|
||||
, QString const& mode, QString const& message, bool low_confidence, quint8 modifiers);
|
||||
|
||||
// ask the client with identification 'id' to close down gracefully
|
||||
Q_SLOT void close (QString const& id);
|
||||
// ask the client to close down gracefully
|
||||
Q_SLOT void close (ClientKey const&);
|
||||
|
||||
// ask the client with identification 'id' to replay all decodes
|
||||
Q_SLOT void replay (QString const& id);
|
||||
// ask the client to replay all decodes
|
||||
Q_SLOT void replay (ClientKey const&);
|
||||
|
||||
// ask the client with identification 'id' to halt transmitting
|
||||
// auto_only just disables auto Tx, otherwise halt is immediate
|
||||
Q_SLOT void halt_tx (QString const& id, bool auto_only);
|
||||
// ask the client to halt transmitting auto_only just disables auto
|
||||
// Tx, otherwise halt is immediate
|
||||
Q_SLOT void halt_tx (ClientKey const&, bool auto_only);
|
||||
|
||||
// ask the client with identification 'id' to set the free text
|
||||
// message and optionally send it ASAP
|
||||
Q_SLOT void free_text (QString const& id, QString const& text, bool send);
|
||||
// ask the client to set the free text message and optionally send
|
||||
// it ASAP
|
||||
Q_SLOT void free_text (ClientKey const&, QString const& text, bool send);
|
||||
|
||||
// ask the client with identification 'id' to set the location provided
|
||||
Q_SLOT void location (QString const& id, QString const& location);
|
||||
// ask the client to set the location provided
|
||||
Q_SLOT void location (ClientKey const&, QString const& location);
|
||||
|
||||
// ask the client with identification 'id' to highlight the callsign
|
||||
// specified with the given colors
|
||||
Q_SLOT void highlight_callsign (QString const& id, QString const& callsign
|
||||
// ask the client to highlight the callsign specified with the given
|
||||
// colors
|
||||
Q_SLOT void highlight_callsign (ClientKey const&, QString const& callsign
|
||||
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
|
||||
, bool last_only = false);
|
||||
|
||||
// ask the client with identification 'id' to switch to
|
||||
// configuration 'configuration_name'
|
||||
Q_SLOT void switch_configuration (QString const& id, QString const& configuration_name);
|
||||
// ask the client to switch to configuration 'configuration_name'
|
||||
Q_SLOT void switch_configuration (ClientKey const&, QString const& configuration_name);
|
||||
|
||||
// ask the client with identification 'id' to change configuration
|
||||
Q_SLOT void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
|
||||
// ask the client to change configuration
|
||||
Q_SLOT void configure (ClientKey const&, QString const& mode, quint32 frequency_tolerance
|
||||
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
|
||||
, QString const& dx_call, QString const& dx_grid, bool generate_messages);
|
||||
|
||||
// the following signals are emitted when a client broadcasts the
|
||||
// matching message
|
||||
Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision);
|
||||
Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call
|
||||
Q_SIGNAL void client_opened (ClientKey const&, QString const& version, QString const& revision);
|
||||
Q_SIGNAL void status_update (ClientKey const&, Frequency, QString const& mode, QString const& dx_call
|
||||
, QString const& report, QString const& tx_mode, bool tx_enabled
|
||||
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
|
||||
, QString const& de_call, QString const& de_grid, QString const& dx_grid
|
||||
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
|
||||
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name);
|
||||
Q_SIGNAL void client_closed (QString const& id);
|
||||
Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time
|
||||
Q_SIGNAL void client_closed (ClientKey const&);
|
||||
Q_SIGNAL void decode (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode, QString const& message
|
||||
, bool low_confidence, bool off_air);
|
||||
Q_SIGNAL void WSPR_decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time, Frequency
|
||||
Q_SIGNAL void WSPR_decode (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time, Frequency
|
||||
, qint32 drift, QString const& callsign, QString const& grid, qint32 power
|
||||
, bool off_air);
|
||||
Q_SIGNAL void qso_logged (QString const& id, QDateTime time_off, QString const& dx_call, QString const& dx_grid
|
||||
Q_SIGNAL void qso_logged (ClientKey const&, QDateTime time_off, QString const& dx_call, QString const& dx_grid
|
||||
, Frequency dial_frequency, QString const& mode, QString const& report_sent
|
||||
, QString const& report_received, QString const& tx_power, QString const& comments
|
||||
, QString const& name, QDateTime time_on, QString const& operator_call
|
||||
, QString const& my_call, QString const& my_grid
|
||||
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode);
|
||||
Q_SIGNAL void decodes_cleared (QString const& id);
|
||||
Q_SIGNAL void logged_ADIF (QString const& id, QByteArray const& ADIF);
|
||||
Q_SIGNAL void decodes_cleared (ClientKey const&);
|
||||
Q_SIGNAL void logged_ADIF (ClientKey const&, QByteArray const& ADIF);
|
||||
|
||||
// this signal is emitted when a network error occurs
|
||||
Q_SIGNAL void error (QString const&) const;
|
||||
@ -118,4 +122,6 @@ private:
|
||||
pimpl<impl> m_;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE (MessageServer::ClientKey);
|
||||
|
||||
#endif
|
||||
|
@ -17,9 +17,14 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QCommandLineOption>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QNetworkInterface>
|
||||
#include <QDateTime>
|
||||
#include <QTime>
|
||||
#include <QHash>
|
||||
@ -38,15 +43,17 @@ class Client
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
explicit Client (QString const& id, QObject * parent = nullptr)
|
||||
explicit Client (ClientKey const& key, QObject * parent = nullptr)
|
||||
: QObject {parent}
|
||||
, id_ {id}
|
||||
, key_ {key}
|
||||
, dial_frequency_ {0u}
|
||||
{
|
||||
}
|
||||
|
||||
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& /*dx_call*/
|
||||
Q_SLOT void update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& /*dx_call*/
|
||||
, QString const& /*report*/, QString const& /*tx_mode*/, bool /*tx_enabled*/
|
||||
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
|
||||
, QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/
|
||||
@ -54,77 +61,85 @@ public:
|
||||
, quint8 /*special_op_mode*/, quint32 /*frequency_tolerance*/, quint32 /*tr_period*/
|
||||
, QString const& /*configuration_name*/)
|
||||
{
|
||||
if (id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
if (f != dial_frequency_)
|
||||
{
|
||||
std::cout << tr ("%1: Dial frequency changed to %2").arg (id_).arg (f).toStdString () << std::endl;
|
||||
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
||||
<< QString {"Dial frequency changed to %1"}.arg (f).toStdString () << std::endl;
|
||||
dial_frequency_ = f;
|
||||
}
|
||||
if (mode + sub_mode != mode_)
|
||||
{
|
||||
std::cout << tr ("%1: Mode changed to %2").arg (id_).arg (mode + sub_mode).toStdString () << std::endl;
|
||||
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
||||
<< QString {"Mode changed to %1"}.arg (mode + sub_mode).toStdString () << std::endl;
|
||||
mode_ = mode + sub_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime time, qint32 snr
|
||||
Q_SLOT void decode_added (bool is_new, ClientKey const& key, QTime time, qint32 snr
|
||||
, float delta_time, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message, bool low_confidence, bool off_air)
|
||||
{
|
||||
if (client_id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
|
||||
<< "Dt:" << delta_time << "Df:" << delta_frequency
|
||||
<< "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high")
|
||||
<< "On air:" << !off_air;
|
||||
std::cout << tr ("%1: Decoded %2").arg (id_).arg (message).toStdString () << std::endl;
|
||||
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
||||
<< QString {"Decoded %1"}.arg (message).toStdString () << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime time, qint32 snr
|
||||
Q_SLOT void beacon_spot_added (bool is_new, ClientKey const& key, QTime time, qint32 snr
|
||||
, float delta_time, Frequency delta_frequency, qint32 drift, QString const& callsign
|
||||
, QString const& grid, qint32 power, bool off_air)
|
||||
{
|
||||
if (client_id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
|
||||
<< "Dt:" << delta_time << "Df:" << delta_frequency
|
||||
<< "drift:" << drift;
|
||||
std::cout << tr ("%1: WSPR decode %2 grid %3 power: %4").arg (id_).arg (callsign).arg (grid).arg (power).toStdString ()
|
||||
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
||||
<< QString {"WSPR decode %1 grid %2 power: %3"}
|
||||
.arg (callsign).arg (grid).arg (power).toStdString ()
|
||||
<< "On air:" << !off_air << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
Q_SLOT void qso_logged (QString const&client_id, QDateTime time_off, QString const& dx_call, QString const& dx_grid
|
||||
Q_SLOT void qso_logged (ClientKey const& key, QDateTime time_off, QString const& dx_call, QString const& dx_grid
|
||||
, Frequency dial_frequency, QString const& mode, QString const& report_sent
|
||||
, QString const& report_received, QString const& tx_power
|
||||
, QString const& comments, QString const& name, QDateTime time_on
|
||||
, QString const& operator_call, QString const& my_call, QString const& my_grid
|
||||
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode)
|
||||
{
|
||||
if (client_id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:" << dx_call << "grid:" << dx_grid
|
||||
qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:"
|
||||
<< dx_call << "grid:" << dx_grid
|
||||
<< "freq:" << dial_frequency << "mode:" << mode << "rpt_sent:" << report_sent
|
||||
<< "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments
|
||||
<< "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call
|
||||
<< "my_grid:" << my_grid << "exchange_sent:" << exchange_sent
|
||||
<< "exchange_rcvd:" << exchange_rcvd << "prop_mode:" << prop_mode;
|
||||
std::cout << QByteArray {80, '-'}.data () << '\n';
|
||||
std::cout << tr ("%1: Logged %2 grid: %3 power: %4 sent: %5 recd: %6 freq: %7 time_off: %8 op: %9 my_call: %10 my_grid: %11 exchange_sent: %12 exchange_rcvd: %13 comments: %14 prop_mode: %15")
|
||||
.arg (id_).arg (dx_call).arg (dx_grid).arg (tx_power).arg (report_sent).arg (report_received)
|
||||
.arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call)
|
||||
.arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd)
|
||||
.arg (comments).arg (prop_mode).toStdString ()
|
||||
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
||||
<< QString {"Logged %1 grid: %2 power: %3 sent: %4 recd: %5 freq: %6 time_off: %7 op: %8 my_call: %9 my_grid: %10 exchange_sent: %11 exchange_rcvd: %12 comments: %13 prop_mode: %14"}
|
||||
.arg (dx_call).arg (dx_grid).arg (tx_power)
|
||||
.arg (report_sent).arg (report_received)
|
||||
.arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call)
|
||||
.arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd)
|
||||
.arg (comments).arg (prop_mode).toStdString ()
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
Q_SLOT void logged_ADIF (QString const&client_id, QByteArray const& ADIF)
|
||||
Q_SLOT void logged_ADIF (ClientKey const& key, QByteArray const& ADIF)
|
||||
{
|
||||
if (client_id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
qDebug () << "ADIF:" << ADIF;
|
||||
std::cout << QByteArray {80, '-'}.data () << '\n';
|
||||
@ -133,7 +148,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
QString id_;
|
||||
ClientKey key_;
|
||||
Frequency dial_frequency_;
|
||||
QString mode_;
|
||||
};
|
||||
@ -143,8 +158,10 @@ class Server
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
Server (port_type port, QHostAddress const& multicast_group)
|
||||
Server (port_type port, QHostAddress const& multicast_group, QStringList const& network_interface_names)
|
||||
: server_ {new MessageServer {this}}
|
||||
{
|
||||
// connect up server
|
||||
@ -154,21 +171,26 @@ public:
|
||||
connect (server_, &MessageServer::client_opened, this, &Server::add_client);
|
||||
connect (server_, &MessageServer::client_closed, this, &Server::remove_client);
|
||||
|
||||
server_->start (port, multicast_group);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 14, 0)
|
||||
server_->start (port, multicast_group, QSet<QString> {network_interface_names.begin (), network_interface_names.end ()});
|
||||
#else
|
||||
server_->start (port, multicast_group, network_interface_names.toSet ());
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
void add_client (QString const& id, QString const& version, QString const& revision)
|
||||
void add_client (ClientKey const& key, QString const& version, QString const& revision)
|
||||
{
|
||||
auto client = new Client {id};
|
||||
auto client = new Client {key};
|
||||
connect (server_, &MessageServer::status_update, client, &Client::update_status);
|
||||
connect (server_, &MessageServer::decode, client, &Client::decode_added);
|
||||
connect (server_, &MessageServer::WSPR_decode, client, &Client::beacon_spot_added);
|
||||
connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged);
|
||||
connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF);
|
||||
clients_[id] = client;
|
||||
server_->replay (id);
|
||||
std::cout << "Discovered WSJT-X instance: " << id.toStdString ();
|
||||
clients_[key] = client;
|
||||
server_->replay (key);
|
||||
std::cout << "Discovered WSJT-X instance: " << key.second.toStdString ()
|
||||
<< '(' << key.first.toString ().toStdString () << ')';
|
||||
if (version.size ())
|
||||
{
|
||||
std::cout << " v" << version.toStdString ();
|
||||
@ -180,23 +202,60 @@ private:
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
void remove_client (QString const& id)
|
||||
void remove_client (ClientKey const& key)
|
||||
{
|
||||
auto iter = clients_.find (id);
|
||||
auto iter = clients_.find (key);
|
||||
if (iter != std::end (clients_))
|
||||
{
|
||||
clients_.erase (iter);
|
||||
(*iter)->deleteLater ();
|
||||
}
|
||||
std::cout << "Removed WSJT-X instance: " << id.toStdString () << std::endl;
|
||||
std::cout << "Removed WSJT-X instance: " << key.second.toStdString ()
|
||||
<< '(' << key.first.toString ().toStdString () << ')' << std::endl;
|
||||
}
|
||||
|
||||
MessageServer * server_;
|
||||
|
||||
// maps client id to clients
|
||||
QHash<QString, Client *> clients_;
|
||||
// maps client key to clients
|
||||
QHash<ClientKey, Client *> clients_;
|
||||
};
|
||||
|
||||
void list_interfaces ()
|
||||
{
|
||||
for (auto const& net_if : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
if (net_if.flags () & QNetworkInterface::IsUp)
|
||||
{
|
||||
std::cout << net_if.humanReadableName ().toStdString () << ":\n"
|
||||
" id: " << net_if.name ().toStdString () << " (" << net_if.index () << ")\n"
|
||||
" addr: " << net_if.hardwareAddress ().toStdString () << "\n"
|
||||
" flags: ";
|
||||
if (net_if.flags () & QNetworkInterface::IsRunning)
|
||||
{
|
||||
std::cout << "Running ";
|
||||
}
|
||||
if (net_if.flags () & QNetworkInterface::CanBroadcast)
|
||||
{
|
||||
std::cout << "Broadcast ";
|
||||
}
|
||||
if (net_if.flags () & QNetworkInterface::CanMulticast)
|
||||
{
|
||||
std::cout << "Multicast ";
|
||||
}
|
||||
if (net_if.flags () & QNetworkInterface::IsLoopBack)
|
||||
{
|
||||
std::cout << "Loop-back ";
|
||||
}
|
||||
std::cout << "\n addresses:\n";
|
||||
for (auto const& ae : net_if.addressEntries ())
|
||||
{
|
||||
std::cout << " " << ae.ip ().toString ().toStdString () << '\n';
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "UDPDaemon.moc"
|
||||
|
||||
int main (int argc, char * argv[])
|
||||
@ -217,6 +276,11 @@ int main (int argc, char * argv[])
|
||||
auto help_option = parser.addHelpOption ();
|
||||
auto version_option = parser.addVersionOption ();
|
||||
|
||||
QCommandLineOption list_option (QStringList {"l", "list-interfaces"},
|
||||
app.translate ("UDPDaemon",
|
||||
"Print the available network interfaces."));
|
||||
parser.addOption (list_option);
|
||||
|
||||
QCommandLineOption port_option (QStringList {"p", "port"},
|
||||
app.translate ("UDPDaemon",
|
||||
"Where <PORT> is the UDP service port number to listen on.\n"
|
||||
@ -232,9 +296,25 @@ int main (int argc, char * argv[])
|
||||
app.translate ("UDPDaemon", "GROUP"));
|
||||
parser.addOption (multicast_addr_option);
|
||||
|
||||
QCommandLineOption network_interface_option (QStringList {"i", "network-interface"},
|
||||
app.translate ("UDPDaemon",
|
||||
"Where <INTERFACE> is the network interface name to join on.\n"
|
||||
"This option can be passed more than once to specify multiple network interfaces\n"
|
||||
"The default is use just the loop back interface."),
|
||||
app.translate ("UDPDaemon", "INTERFACE"));
|
||||
parser.addOption (network_interface_option);
|
||||
|
||||
parser.process (app);
|
||||
|
||||
Server server {static_cast<port_type> (parser.value (port_option).toUInt ()), QHostAddress {parser.value (multicast_addr_option)}};
|
||||
if (parser.isSet (list_option))
|
||||
{
|
||||
list_interfaces ();
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
Server server {static_cast<port_type> (parser.value (port_option).toUInt ())
|
||||
, QHostAddress {parser.value (multicast_addr_option).trimmed ()}
|
||||
, parser.values (network_interface_option)};
|
||||
|
||||
return app.exec ();
|
||||
}
|
||||
|
BIN
lib/fsk4hf/.DS_Store
vendored
BIN
lib/fsk4hf/.DS_Store
vendored
Binary file not shown.
@ -117,6 +117,42 @@ namespace std
|
||||
}
|
||||
#endif
|
||||
|
||||
inline
|
||||
bool is_broadcast_address (QHostAddress const& host_addr)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 11, 0)
|
||||
return host_addr.isBroadcast ();
|
||||
#else
|
||||
bool ok;
|
||||
return host_addr.toIPv4Address (&ok) == 0xffffffffu && ok;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline
|
||||
bool is_multicast_address (QHostAddress const& host_addr)
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 6, 0)
|
||||
return host_addr.isMulticast ();
|
||||
#else
|
||||
bool ok;
|
||||
return (((host_addr.toIPv4Address (&ok) & 0xf0000000u) == 0xe0000000u) && ok)
|
||||
|| host_addr.toIPv6Address ()[0] == 0xff;
|
||||
#endif
|
||||
}
|
||||
|
||||
inline
|
||||
bool is_MAC_ambiguous_multicast_address (QHostAddress const& host_addr)
|
||||
{
|
||||
// sub-ranges 224.128.0.0/24, 225.0.0.0/24, 225.128.0.0/24,
|
||||
// 226.0.0.0/24, 226.128.0.0/24, ..., 239.0.0.0/24, 239.128.0.0/24
|
||||
// are not supported as they are inefficient due to ambiguous
|
||||
// mappings to Ethernet MAC addresses. 224.0.0.0/24 alone is allowed
|
||||
// from these ranges
|
||||
bool ok;
|
||||
auto ipv4 = host_addr.toIPv4Address (&ok);
|
||||
return ok && !((ipv4 & 0xffffff00u) == 0xe0000000) && (ipv4 & 0xf07fff00) == 0xe0000000;
|
||||
}
|
||||
|
||||
// Register some useful Qt types with QMetaType
|
||||
Q_DECLARE_METATYPE (QHostAddress);
|
||||
|
||||
|
@ -131,6 +131,80 @@ private:
|
||||
QDateTime dt {QDate {2020, 8, 6}, QTime {14, 15, 22, 501}};
|
||||
QCOMPARE (qt_truncate_date_time_to (dt, 7500), QDateTime (QDate (2020, 8, 6), QTime (14, 15, 22, 500)));
|
||||
}
|
||||
|
||||
Q_SLOT void is_multicast_address_data ()
|
||||
{
|
||||
QTest::addColumn<QString> ("addr");
|
||||
QTest::addColumn<bool> ("result");
|
||||
|
||||
QTest::newRow ("loopback") << "127.0.0.1" << false;
|
||||
QTest::newRow ("looback IPv6") << "::1" << false;
|
||||
QTest::newRow ("lowest-") << "223.255.255.255" << false;
|
||||
QTest::newRow ("lowest") << "224.0.0.0" << true;
|
||||
QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
|
||||
QTest::newRow ("lowest IPv6") << "ff00::" << true;
|
||||
QTest::newRow ("highest") << "239.255.255.255" << true;
|
||||
QTest::newRow ("highest+") << "240.0.0.0" << false;
|
||||
QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << true;
|
||||
}
|
||||
|
||||
Q_SLOT void is_multicast_address ()
|
||||
{
|
||||
QFETCH (QString, addr);
|
||||
QFETCH (bool, result);
|
||||
|
||||
QCOMPARE (::is_multicast_address (QHostAddress {addr}), result);
|
||||
}
|
||||
|
||||
Q_SLOT void is_MAC_ambiguous_multicast_address_data ()
|
||||
{
|
||||
QTest::addColumn<QString> ("addr");
|
||||
QTest::addColumn<bool> ("result");
|
||||
|
||||
QTest::newRow ("loopback") << "127.0.0.1" << false;
|
||||
QTest::newRow ("looback IPv6") << "::1" << false;
|
||||
|
||||
QTest::newRow ("lowest- R1") << "223.255.255.255" << false;
|
||||
QTest::newRow ("lowest R1") << "224.0.0.0" << false;
|
||||
QTest::newRow ("highest R1") << "224.0.0.255" << false;
|
||||
QTest::newRow ("highest+ R1") << "224.0.1.0" << false;
|
||||
QTest::newRow ("lowest- R1A") << "224.127.255.255" << false;
|
||||
QTest::newRow ("lowest R1A") << "224.128.0.0" << true;
|
||||
QTest::newRow ("highest R1A") << "224.128.0.255" << true;
|
||||
QTest::newRow ("highest+ R1A") << "224.128.1.0" << false;
|
||||
|
||||
QTest::newRow ("lowest- R2") << "224.255.255.255" << false;
|
||||
QTest::newRow ("lowest R2") << "225.0.0.0" << true;
|
||||
QTest::newRow ("highest R2") << "225.0.0.255" << true;
|
||||
QTest::newRow ("highest+ R2") << "225.0.1.0" << false;
|
||||
QTest::newRow ("lowest- R2A") << "225.127.255.255" << false;
|
||||
QTest::newRow ("lowest R2A") << "225.128.0.0" << true;
|
||||
QTest::newRow ("highest R2A") << "225.128.0.255" << true;
|
||||
QTest::newRow ("highest+ R2A") << "225.128.1.0" << false;
|
||||
|
||||
QTest::newRow ("lowest- R3") << "238.255.255.255" << false;
|
||||
QTest::newRow ("lowest R3") << "239.0.0.0" << true;
|
||||
QTest::newRow ("highest R3") << "239.0.0.255" << true;
|
||||
QTest::newRow ("highest+ R3") << "239.0.1.0" << false;
|
||||
QTest::newRow ("lowest- R3A") << "239.127.255.255" << false;
|
||||
QTest::newRow ("lowest R3A") << "239.128.0.0" << true;
|
||||
QTest::newRow ("highest R3A") << "239.128.0.255" << true;
|
||||
QTest::newRow ("highest+ R3A") << "239.128.1.0" << false;
|
||||
|
||||
QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
|
||||
QTest::newRow ("lowest IPv6") << "ff00::" << false;
|
||||
QTest::newRow ("highest") << "239.255.255.255" << false;
|
||||
QTest::newRow ("highest+") << "240.0.0.0" << false;
|
||||
QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
|
||||
}
|
||||
|
||||
Q_SLOT void is_MAC_ambiguous_multicast_address ()
|
||||
{
|
||||
QFETCH (QString, addr);
|
||||
QFETCH (bool, result);
|
||||
|
||||
QCOMPARE (::is_MAC_ambiguous_multicast_address (QHostAddress {addr}), result);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN (TestQtHelpers);
|
||||
|
93
widgets/CheckableItemComboBox.cpp
Normal file
93
widgets/CheckableItemComboBox.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
#include "CheckableItemComboBox.hpp"
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QStandardItemModel>
|
||||
#include <QStandardItem>
|
||||
#include <QLineEdit>
|
||||
#include <QEvent>
|
||||
#include <QListView>
|
||||
|
||||
class CheckableItemComboBoxStyledItemDelegate
|
||||
: public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
explicit CheckableItemComboBoxStyledItemDelegate (QObject * parent = nullptr)
|
||||
: QStyledItemDelegate {parent}
|
||||
{
|
||||
}
|
||||
|
||||
void paint (QPainter * painter, QStyleOptionViewItem const& option, QModelIndex const& index) const override
|
||||
{
|
||||
QStyleOptionViewItem& mutable_option = const_cast<QStyleOptionViewItem&> (option);
|
||||
mutable_option.showDecorationSelected = false;
|
||||
QStyledItemDelegate::paint (painter, mutable_option, index);
|
||||
}
|
||||
};
|
||||
|
||||
CheckableItemComboBox::CheckableItemComboBox (QWidget * parent)
|
||||
: LazyFillComboBox {parent}
|
||||
, model_ {new QStandardItemModel()}
|
||||
{
|
||||
setModel (model_.data ());
|
||||
|
||||
setEditable (true);
|
||||
lineEdit ()->setReadOnly (true);
|
||||
lineEdit ()->installEventFilter (this);
|
||||
setItemDelegate (new CheckableItemComboBoxStyledItemDelegate {this});
|
||||
|
||||
connect (lineEdit(), &QLineEdit::selectionChanged, lineEdit(), &QLineEdit::deselect);
|
||||
connect (static_cast<QListView *> (view ()), &QListView::pressed, this, &CheckableItemComboBox::item_pressed);
|
||||
connect (model_.data (), &QStandardItemModel::dataChanged, this, &CheckableItemComboBox::model_data_changed);
|
||||
}
|
||||
|
||||
QStandardItem * CheckableItemComboBox::addCheckItem (QString const& label, QVariant const& data
|
||||
, Qt::CheckState checkState)
|
||||
{
|
||||
auto * item = new QStandardItem {label};
|
||||
item->setCheckState (checkState);
|
||||
item->setData (data);
|
||||
item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
|
||||
model_->appendRow (item);
|
||||
update_text ();
|
||||
return item;
|
||||
}
|
||||
|
||||
bool CheckableItemComboBox::eventFilter (QObject * object, QEvent * event)
|
||||
{
|
||||
if (object == lineEdit() && event->type () == QEvent::MouseButtonPress)
|
||||
{
|
||||
showPopup();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CheckableItemComboBox::update_text()
|
||||
{
|
||||
QString text;
|
||||
for (int i = 0; i < model_->rowCount (); ++i)
|
||||
{
|
||||
if (model_->item (i)->checkState () == Qt::Checked)
|
||||
{
|
||||
if (text.size ())
|
||||
{
|
||||
text+= ", ";
|
||||
}
|
||||
text += model_->item (i)->data ().toString ();
|
||||
}
|
||||
}
|
||||
lineEdit ()->setText (text);
|
||||
}
|
||||
|
||||
void CheckableItemComboBox::model_data_changed ()
|
||||
{
|
||||
update_text ();
|
||||
}
|
||||
|
||||
void CheckableItemComboBox::item_pressed (QModelIndex const& index)
|
||||
{
|
||||
QStandardItem * item = model_->itemFromIndex (index);
|
||||
item->setCheckState (item->checkState () == Qt::Checked ? Qt::Unchecked : Qt::Checked);
|
||||
}
|
||||
|
||||
#include "widgets/moc_CheckableItemComboBox.cpp"
|
37
widgets/CheckableItemComboBox.hpp
Normal file
37
widgets/CheckableItemComboBox.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#ifndef CHECKABLE_ITEM_COMBO_BOX_HPP__
|
||||
#define CHECKABLE_ITEM_COMBO_BOX_HPP__
|
||||
|
||||
#include <QScopedPointer>
|
||||
|
||||
#include "LazyFillComboBox.hpp"
|
||||
|
||||
class QStandardItemModel;
|
||||
class QStandardItem;
|
||||
|
||||
/**
|
||||
* @brief QComboBox with support of checkboxes
|
||||
* http://stackoverflow.com/questions/8422760/combobox-of-checkboxes
|
||||
*/
|
||||
class CheckableItemComboBox
|
||||
: public LazyFillComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CheckableItemComboBox (QWidget * parent = nullptr);
|
||||
QStandardItem * addCheckItem (QString const& label, QVariant const& data, Qt::CheckState checkState);
|
||||
|
||||
protected:
|
||||
bool eventFilter (QObject *, QEvent *) override;
|
||||
|
||||
private:
|
||||
void update_text();
|
||||
|
||||
Q_SLOT void model_data_changed ();
|
||||
Q_SLOT void item_pressed (QModelIndex const&);
|
||||
|
||||
private:
|
||||
QScopedPointer<QStandardItemModel> model_;
|
||||
};
|
||||
|
||||
#endif
|
@ -10,7 +10,7 @@ class QWidget;
|
||||
//
|
||||
// QComboBox derivative that signals show and hide of the pop up list.
|
||||
//
|
||||
class LazyFillComboBox final
|
||||
class LazyFillComboBox
|
||||
: public QComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -417,6 +417,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
m_messageClient {new MessageClient {QApplication::applicationName (),
|
||||
version (), revision (),
|
||||
m_config.udp_server_name (), m_config.udp_server_port (),
|
||||
m_config.udp_interface_names (), m_config.udp_TTL (),
|
||||
this}},
|
||||
m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()},
|
||||
m_manual {&m_network_manager},
|
||||
@ -785,6 +786,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure);
|
||||
connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server);
|
||||
connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port);
|
||||
connect (&m_config, &Configuration::udp_TTL_changed, m_messageClient, &MessageClient::set_TTL);
|
||||
connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable);
|
||||
connect (&m_config, &Configuration::enumerating_audio_devices, [this] () {
|
||||
showStatusMessage (tr ("Enumerating audio devices"));
|
||||
@ -7835,7 +7837,7 @@ void MainWindow::networkError (QString const& e)
|
||||
, MessageBox::Cancel))
|
||||
{
|
||||
// retry server lookup
|
||||
m_messageClient->set_server (m_config.udp_server_name ());
|
||||
m_messageClient->set_server (m_config.udp_server_name (), m_config.udp_interface_names ());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,9 @@ SOURCES += \
|
||||
widgets/AbstractLogWindow.cpp \
|
||||
widgets/FrequencyLineEdit.cpp widgets/FrequencyDeltaLineEdit.cpp \
|
||||
widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp \
|
||||
widgets/HelpTextWindow.cpp widgets/RestrictedSpinBox.cpp
|
||||
widgets/HelpTextWindow.cpp widgets/RestrictedSpinBox.cpp \
|
||||
widgets/LazyFillComboBox.cpp widgets/CheckableItemComboBox.cpp
|
||||
|
||||
HEADERS += \
|
||||
widgets/mainwindow.h widgets/plotter.h \
|
||||
widgets/about.h widgets/widegraph.h \
|
||||
@ -22,7 +24,8 @@ HEADERS += \
|
||||
widgets/ExportCabrillo.h widgets/AbstractLogWindow.hpp \
|
||||
widgets/FoxLogWindow.hpp widgets/CabrilloLogWindow.hpp \
|
||||
widgets/DateTimeEdit.hpp widgets/HelpTextWindow.hpp \
|
||||
widgets/RestrictedSpinBox.hpp
|
||||
widgets/RestrictedSpinBox.hpp \
|
||||
widgets/LazyFillComboBox.hpp widgets/CheckableItemComboBox.hpp
|
||||
|
||||
FORMS += \
|
||||
widgets/mainwindow.ui widgets/about.ui \
|
||||
|
Loading…
Reference in New Issue
Block a user