mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-26 10:30:22 -04:00 
			
		
		
		
	Thanks to Morgan (sri no other attribution given) for the initial contribution this change is based on.
		
			
				
	
	
		
			596 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			596 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "MessageServer.hpp"
 | |
| 
 | |
| #include <stdexcept>
 | |
| #include <limits>
 | |
| 
 | |
| #include <QNetworkInterface>
 | |
| #include <QUdpSocket>
 | |
| #include <QTimer>
 | |
| #include <QHash>
 | |
| 
 | |
| #include "Radio.hpp"
 | |
| #include "Network/NetworkMessage.hpp"
 | |
| #include "qt_helpers.hpp"
 | |
| 
 | |
| #include "pimpl_impl.hpp"
 | |
| 
 | |
| #include "moc_MessageServer.cpp"
 | |
| 
 | |
| namespace
 | |
| {
 | |
|   auto quint32_max = std::numeric_limits<quint32>::max ();
 | |
| }
 | |
| 
 | |
| class MessageServer::impl
 | |
|   : public QUdpSocket
 | |
| {
 | |
|   Q_OBJECT;
 | |
| 
 | |
| public:
 | |
|   impl (MessageServer * self, QString const& version, QString const& revision)
 | |
|     : self_ {self}
 | |
|     , version_ {version}
 | |
|     , revision_ {revision}
 | |
|     , clock_ {new QTimer {this}}
 | |
|   {
 | |
|     // register the required types with Qt
 | |
|     Radio::register_types ();
 | |
| 
 | |
|     connect (this, &QIODevice::readyRead, this, &MessageServer::impl::pending_datagrams);
 | |
| #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
 | |
|     connect (this, static_cast<void (impl::*) (SocketError)> (&impl::error)
 | |
|              , [this] (SocketError /* e */)
 | |
|              {
 | |
|                Q_EMIT self_->error (errorString ());
 | |
|              });
 | |
| #else
 | |
|     connect (this, &impl::errorOccurred, [this] (SocketError /* e */)
 | |
|                                  {
 | |
|                                    Q_EMIT self_->error (errorString ());
 | |
|                                  });
 | |
| #endif
 | |
|     connect (clock_, &QTimer::timeout, this, &impl::tick);
 | |
|     clock_->start (NetworkMessage::pulse * 1000);
 | |
|   }
 | |
| 
 | |
|   enum StreamStatus {Fail, Short, OK};
 | |
| 
 | |
|   void leave_multicast_group ();
 | |
|   void join_multicast_group ();
 | |
|   void parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg);
 | |
|   void tick ();
 | |
|   void pending_datagrams ();
 | |
|   StreamStatus check_status (QDataStream const&) const;
 | |
|   void send_message (QDataStream const& out, QByteArray const& message, QHostAddress const& address, port_type port)
 | |
|   {
 | |
|       if (OK == check_status (out))
 | |
|         {
 | |
|           writeDatagram (message, address, port);
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           Q_EMIT self_->error ("Error creating UDP message");
 | |
|         }
 | |
|   }
 | |
| 
 | |
|   MessageServer * self_;
 | |
|   QString version_;
 | |
|   QString revision_;
 | |
|   QHostAddress multicast_group_address_;
 | |
|   QSet<QString> network_interfaces_;
 | |
|   static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint;
 | |
|   struct Client
 | |
|   {
 | |
|     Client () = default;
 | |
|     Client (port_type const& sender_port)
 | |
|       : sender_port_ {sender_port}
 | |
|       , negotiated_schema_number_ {2} // not 1 because it's broken
 | |
|       , last_activity_ {QDateTime::currentDateTime ()}
 | |
|     {
 | |
|     }
 | |
|     Client (Client const&) = default;
 | |
|     Client& operator= (Client const&) = default;
 | |
| 
 | |
|     port_type sender_port_;
 | |
|     quint32 negotiated_schema_number_;
 | |
|     QDateTime last_activity_;
 | |
|   };
 | |
|   QHash<ClientKey, Client> clients_; // maps id to Client
 | |
|   QTimer * clock_;
 | |
| };
 | |
| 
 | |
| MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
 | |
| 
 | |
| #include "MessageServer.moc"
 | |
| 
 | |
| void MessageServer::impl::leave_multicast_group ()
 | |
| {
 | |
|   if (BoundState == state () && is_multicast_address (multicast_group_address_))
 | |
|     {
 | |
|       for (auto const& if_name : network_interfaces_)
 | |
|         {
 | |
|           leaveMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void MessageServer::impl::join_multicast_group ()
 | |
| {
 | |
|   if (BoundState == state () && is_multicast_address (multicast_group_address_))
 | |
|     {
 | |
|       if (network_interfaces_.size ())
 | |
|         {
 | |
|           for (auto const& if_name : network_interfaces_)
 | |
|             {
 | |
|               joinMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
 | |
|             }
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           // 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;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void MessageServer::impl::pending_datagrams ()
 | |
| {
 | |
|   while (hasPendingDatagrams ())
 | |
|     {
 | |
|       QByteArray datagram;
 | |
|       datagram.resize (pendingDatagramSize ());
 | |
|       QHostAddress sender_address;
 | |
|       port_type sender_port;
 | |
|       if (0 <= readDatagram (datagram.data (), datagram.size (), &sender_address, &sender_port))
 | |
|         {
 | |
|           parse_message (sender_address, sender_port, datagram);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void MessageServer::impl::parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg)
 | |
| {
 | |
|   try
 | |
|     {
 | |
|       //
 | |
|       // message format is described in NetworkMessage.hpp
 | |
|       //
 | |
|       NetworkMessage::Reader in {msg};
 | |
| 
 | |
|       auto id = in.id ();
 | |
|       if (OK == check_status (in))
 | |
|         {
 | |
|           auto client_key = ClientKey {sender, id};
 | |
|           if (!clients_.contains (client_key))
 | |
|             {
 | |
|               auto& client = (clients_[client_key] = {sender_port});
 | |
|               QByteArray client_version;
 | |
|               QByteArray client_revision;
 | |
| 
 | |
|               if (NetworkMessage::Heartbeat == in.type ())
 | |
|                 {
 | |
|                   // negotiate a working schema number
 | |
|                   in >> client.negotiated_schema_number_;
 | |
|                   if (OK == check_status (in))
 | |
|                     {
 | |
|                       auto sn = NetworkMessage::Builder::schema_number;
 | |
|                       client.negotiated_schema_number_ = std::min (sn, client.negotiated_schema_number_);
 | |
| 
 | |
|                       // reply to the new client informing it of the
 | |
|                       // negotiated schema number
 | |
|                       QByteArray message;
 | |
|                       NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id, client.negotiated_schema_number_};
 | |
|                       hb << NetworkMessage::Builder::schema_number // maximum schema number accepted
 | |
|                          << version_.toUtf8 () << revision_.toUtf8 ();
 | |
|                       if (impl::OK == check_status (hb))
 | |
|                         {
 | |
|                           writeDatagram (message, client_key.first, sender_port);
 | |
|                         }
 | |
|                       else
 | |
|                         {
 | |
|                           Q_EMIT self_->error ("Error creating UDP message");
 | |
|                         }
 | |
|                     }
 | |
|                   // we don't care if this fails to read
 | |
|                   in >> client_version >> client_revision;
 | |
|                 }
 | |
|               Q_EMIT self_->client_opened (client_key, QString::fromUtf8 (client_version),
 | |
|                                            QString::fromUtf8 (client_revision));
 | |
|             }
 | |
|           clients_[client_key].last_activity_ = QDateTime::currentDateTime ();
 | |
|   
 | |
|           //
 | |
|           // message format is described in NetworkMessage.hpp
 | |
|           //
 | |
|           switch (in.type ())
 | |
|             {
 | |
|             case NetworkMessage::Heartbeat:
 | |
|               //nothing to do here as time out handling deals with lifetime
 | |
|               break;
 | |
| 
 | |
|             case NetworkMessage::Clear:
 | |
|               Q_EMIT self_->decodes_cleared (client_key);
 | |
|               break;
 | |
| 
 | |
|             case NetworkMessage::Status:
 | |
|               {
 | |
|                 // unpack message
 | |
|                 Frequency f;
 | |
|                 QByteArray mode;
 | |
|                 QByteArray dx_call;
 | |
|                 QByteArray report;
 | |
|                 QByteArray tx_mode;
 | |
|                 bool tx_enabled {false};
 | |
|                 bool transmitting {false};
 | |
|                 bool decoding {false};
 | |
|                 quint32 rx_df {quint32_max};
 | |
|                 quint32 tx_df {quint32_max};
 | |
|                 QByteArray de_call;
 | |
|                 QByteArray de_grid;
 | |
|                 QByteArray dx_grid;
 | |
|                 bool watchdog_timeout {false};
 | |
|                 QByteArray sub_mode;
 | |
|                 bool fast_mode {false};
 | |
|                 quint8 special_op_mode {0};
 | |
|                 quint32 frequency_tolerance {quint32_max};
 | |
|                 quint32 tr_period {quint32_max};
 | |
|                 QByteArray configuration_name;
 | |
|                 QByteArray tx_message;
 | |
|                 in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding
 | |
|                    >> rx_df >> tx_df >> de_call >> de_grid >> dx_grid >> watchdog_timeout >> sub_mode
 | |
|                    >> fast_mode >> special_op_mode >> frequency_tolerance >> tr_period >> configuration_name
 | |
|                    >> tx_message;
 | |
|                 if (check_status (in) != Fail)
 | |
|                   {
 | |
|                     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)
 | |
|                                                  , QString::fromUtf8 (dx_grid), watchdog_timeout
 | |
|                                                  , QString::fromUtf8 (sub_mode), fast_mode
 | |
|                                                  , special_op_mode, frequency_tolerance, tr_period
 | |
|                                                  , QString::fromUtf8 (configuration_name)
 | |
|                                                  , QString::fromUtf8 (tx_message));
 | |
|                   }
 | |
|               }
 | |
|               break;
 | |
| 
 | |
|             case NetworkMessage::Decode:
 | |
|               {
 | |
|                 // unpack message
 | |
|                 bool is_new {true};
 | |
|                 QTime time;
 | |
|                 qint32 snr;
 | |
|                 float delta_time;
 | |
|                 quint32 delta_frequency;
 | |
|                 QByteArray mode;
 | |
|                 QByteArray message;
 | |
|                 bool low_confidence {false};
 | |
|                 bool off_air {false};
 | |
|                 in >> is_new >> time >> snr >> delta_time >> delta_frequency >> mode
 | |
|                    >> message >> low_confidence >> off_air;
 | |
|                 if (check_status (in) != Fail)
 | |
|                   {
 | |
|                     Q_EMIT self_->decode (is_new, client_key, time, snr, delta_time, delta_frequency
 | |
|                                           , QString::fromUtf8 (mode), QString::fromUtf8 (message)
 | |
|                                           , low_confidence, off_air);
 | |
|                   }
 | |
|               }
 | |
|               break;
 | |
| 
 | |
|             case NetworkMessage::WSPRDecode:
 | |
|               {
 | |
|                 // unpack message
 | |
|                 bool is_new {true};
 | |
|                 QTime time;
 | |
|                 qint32 snr;
 | |
|                 float delta_time;
 | |
|                 Frequency frequency;
 | |
|                 qint32 drift;
 | |
|                 QByteArray callsign;
 | |
|                 QByteArray grid;
 | |
|                 qint32 power;
 | |
|                 bool off_air {false};
 | |
|                 in >> is_new >> time >> snr >> delta_time >> frequency >> drift >> callsign >> grid >> power
 | |
|                    >> off_air;
 | |
|                 if (check_status (in) != Fail)
 | |
|                   {
 | |
|                     Q_EMIT self_->WSPR_decode (is_new, client_key, time, snr, delta_time, frequency, drift
 | |
|                                                , QString::fromUtf8 (callsign), QString::fromUtf8 (grid)
 | |
|                                                , power, off_air);
 | |
|                   }
 | |
|               }
 | |
|               break;
 | |
| 
 | |
|             case NetworkMessage::QSOLogged:
 | |
|               {
 | |
|                 QDateTime time_off;
 | |
|                 QByteArray dx_call;
 | |
|                 QByteArray dx_grid;
 | |
|                 Frequency dial_frequency;
 | |
|                 QByteArray mode;
 | |
|                 QByteArray report_sent;
 | |
|                 QByteArray report_received;
 | |
|                 QByteArray tx_power;
 | |
|                 QByteArray comments;
 | |
|                 QByteArray name;
 | |
|                 QDateTime time_on; // Note: LOTW uses TIME_ON for their +/- 30-minute time window
 | |
|                 QByteArray operator_call;
 | |
|                 QByteArray my_call;
 | |
|                 QByteArray my_grid;
 | |
|                 QByteArray exchange_sent;
 | |
|                 QByteArray exchange_rcvd;
 | |
|                 QByteArray prop_mode;
 | |
|                 in >> time_off >> dx_call >> dx_grid >> dial_frequency >> mode >> report_sent >> report_received
 | |
|                    >> tx_power >> comments >> name >> time_on >> operator_call >> my_call >> my_grid
 | |
|                    >> exchange_sent >> exchange_rcvd >> prop_mode;
 | |
|                 if (check_status (in) != Fail)
 | |
|                   {
 | |
|                     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)
 | |
|                                               , QString::fromUtf8 (my_grid), QString::fromUtf8 (exchange_sent)
 | |
|                                               , QString::fromUtf8 (exchange_rcvd), QString::fromUtf8 (prop_mode));
 | |
|                   }
 | |
|               }
 | |
|               break;
 | |
| 
 | |
|             case NetworkMessage::Close:
 | |
|               Q_EMIT self_->client_closed (client_key);
 | |
|               clients_.remove (client_key);
 | |
|               break;
 | |
| 
 | |
|             case NetworkMessage::LoggedADIF:
 | |
|               {
 | |
|                 QByteArray ADIF;
 | |
|                 in >> ADIF;
 | |
|                 if (check_status (in) != Fail)
 | |
|                   {
 | |
|                     Q_EMIT self_->logged_ADIF (client_key, ADIF);
 | |
|                   }
 | |
|               }
 | |
|               break;
 | |
| 
 | |
|             default:
 | |
|               // Ignore
 | |
|               break;
 | |
|             }
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           Q_EMIT self_->error ("MessageServer warning: invalid UDP message received");
 | |
|         }
 | |
|     }
 | |
|   catch (std::exception const& e)
 | |
|     {
 | |
|       Q_EMIT self_->error (QString {"MessageServer exception: %1"}.arg (e.what ()));
 | |
|     }
 | |
|   catch (...)
 | |
|     {
 | |
|       Q_EMIT self_->error ("Unexpected exception in MessageServer");
 | |
|     }
 | |
| }
 | |
| 
 | |
| void MessageServer::impl::tick ()
 | |
| {
 | |
|   auto now = QDateTime::currentDateTime ();
 | |
|   auto iter = std::begin (clients_);
 | |
|   while (iter != std::end (clients_))
 | |
|     {
 | |
|       if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse))
 | |
|         {
 | |
|           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
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           ++iter;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| auto MessageServer::impl::check_status (QDataStream const& stream) const -> StreamStatus
 | |
| {
 | |
|   auto stat = stream.status ();
 | |
|   StreamStatus result {Fail};
 | |
|   switch (stat)
 | |
|     {
 | |
|     case QDataStream::ReadPastEnd:
 | |
|       result = Short;
 | |
|       break;
 | |
| 
 | |
|     case QDataStream::ReadCorruptData:
 | |
|       Q_EMIT self_->error ("Message serialization error: read corrupt data");
 | |
|       break;
 | |
| 
 | |
|     case QDataStream::WriteFailed:
 | |
|       Q_EMIT self_->error ("Message serialization error: write error");
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       result = OK;
 | |
|       break;
 | |
|     }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| MessageServer::MessageServer (QObject * parent, QString const& version, QString const& revision)
 | |
|   : QObject {parent}
 | |
|   , m_ {this, version, revision}
 | |
| {
 | |
| }
 | |
| 
 | |
| void MessageServer::start (port_type port, QHostAddress const& multicast_group_address
 | |
|                            , QSet<QString> const& network_interface_names)
 | |
| {
 | |
|   // 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::UnconnectedState != m_->state ())
 | |
|         {
 | |
|           m_->close ();
 | |
|         }
 | |
|       if (!(multicast_group_address.isNull () || is_multicast_address (multicast_group_address)))
 | |
|         {
 | |
|           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_->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 (ClientKey const& key, quint8 window)
 | |
| {
 | |
|   auto iter = m_->clients_.find (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|     {
 | |
|       QByteArray message;
 | |
|       NetworkMessage::Builder out {&message, NetworkMessage::Clear, key.second, (*iter).negotiated_schema_number_};
 | |
|       out << window;
 | |
|       m_->send_message (out, message, key.first, (*iter).sender_port_);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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 (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|     {
 | |
|       QByteArray message;
 | |
|       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, key.first, (*iter).sender_port_);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void MessageServer::replay (ClientKey const& key)
 | |
| {
 | |
|   auto iter = m_->clients_.find (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|     {
 | |
|       QByteArray message;
 | |
|       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 (ClientKey const& key)
 | |
| {
 | |
|   auto iter = m_->clients_.find (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|     {
 | |
|       QByteArray message;
 | |
|       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 (ClientKey const& key, bool auto_only)
 | |
| {
 | |
|   auto iter = m_->clients_.find (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|     {
 | |
|       QByteArray message;
 | |
|       NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, key.second, (*iter).negotiated_schema_number_};
 | |
|       out << auto_only;
 | |
|       m_->send_message (out, message, key.first, (*iter).sender_port_);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void MessageServer::free_text (ClientKey const& key, QString const& text, bool send)
 | |
| {
 | |
|   auto iter = m_->clients_.find (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|     {
 | |
|       QByteArray message;
 | |
|       NetworkMessage::Builder out {&message, NetworkMessage::FreeText, key.second, (*iter).negotiated_schema_number_};
 | |
|       out << text.toUtf8 () << send;
 | |
|       m_->send_message (out, message, key.first, (*iter).sender_port_);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void MessageServer::location (ClientKey const& key, QString const& loc)
 | |
| {
 | |
|   auto iter = m_->clients_.find (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|   {
 | |
|     QByteArray message;
 | |
|     NetworkMessage::Builder out {&message, NetworkMessage::Location, key.second, (*iter).negotiated_schema_number_};
 | |
|     out << loc.toUtf8 ();
 | |
|     m_->send_message (out, message, key.first, (*iter).sender_port_);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void MessageServer::highlight_callsign (ClientKey const& key, QString const& callsign
 | |
|                                         , QColor const& bg, QColor const& fg, bool last_only)
 | |
| {
 | |
|   auto iter = m_->clients_.find (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|   {
 | |
|     QByteArray message;
 | |
|     NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, key.second, (*iter).negotiated_schema_number_};
 | |
|     out << callsign.toUtf8 () << bg << fg << last_only;
 | |
|     m_->send_message (out, message, key.first, (*iter).sender_port_);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void MessageServer::switch_configuration (ClientKey const& key, QString const& configuration_name)
 | |
| {
 | |
|   auto iter = m_->clients_.find (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|   {
 | |
|     QByteArray message;
 | |
|     NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, key.second, (*iter).negotiated_schema_number_};
 | |
|     out << configuration_name.toUtf8 ();
 | |
|     m_->send_message (out, message, key.first, (*iter).sender_port_);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 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 (key);
 | |
|   if (iter != std::end (m_->clients_))
 | |
|   {
 | |
|     QByteArray message;
 | |
|     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, key.first, (*iter).sender_port_);
 | |
|   }
 | |
| }
 |