mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-12 11:26:11 -05:00
Merge pull request #1809 from srcejon/cli_remote_tcp
Command line options to start Remote TCP Sink
This commit is contained in:
commit
df179ddf6b
22
app/main.cpp
22
app/main.cpp
@ -36,6 +36,7 @@
|
||||
|
||||
#include "loggerwithfile.h"
|
||||
#include "mainwindow.h"
|
||||
#include "remotetcpsinkstarter.h"
|
||||
#include "dsp/dsptypes.h"
|
||||
|
||||
static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *logger)
|
||||
@ -74,7 +75,6 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
|
||||
if (settings.contains(uiScaleFactor))
|
||||
{
|
||||
QString scaleFactor = settings.value(uiScaleFactor).toString();
|
||||
qDebug() << "Setting QT_SCALE_FACTOR to" << scaleFactor;
|
||||
qputenv("QT_SCALE_FACTOR", scaleFactor.toLatin1());
|
||||
}
|
||||
|
||||
@ -178,7 +178,27 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
|
||||
applicationPid);
|
||||
#endif
|
||||
|
||||
if (parser.getListDevices())
|
||||
{
|
||||
// Disable log on console, so we can more easily see device list
|
||||
logger->setConsoleMinMessageLevel(QtFatalMsg);
|
||||
// Don't pass logger to MainWindow, otherwise it can reenable log output
|
||||
logger = nullptr;
|
||||
}
|
||||
|
||||
MainWindow w(logger, parser);
|
||||
|
||||
if (parser.getListDevices())
|
||||
{
|
||||
// List available physical devices and exit
|
||||
RemoteTCPSinkStarter::listAvailableDevices();
|
||||
exit (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
if (parser.getRemoteTCPSink()) {
|
||||
RemoteTCPSinkStarter::start(parser);
|
||||
}
|
||||
|
||||
w.show();
|
||||
|
||||
return a.exec();
|
||||
|
@ -1,8 +1,6 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2017 Edouard Griffiths, F4EXB. //
|
||||
// //
|
||||
// Swagger server adapter interface //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
@ -26,6 +24,7 @@
|
||||
#include "loggerwithfile.h"
|
||||
#include "mainparser.h"
|
||||
#include "mainserver.h"
|
||||
#include "remotetcpsinkstarter.h"
|
||||
#include "dsp/dsptypes.h"
|
||||
|
||||
void handler(int sig) {
|
||||
@ -92,20 +91,39 @@ static int runQtApplication(int argc, char* argv[], qtwebapp::LoggerWithFile *lo
|
||||
QCoreApplication::applicationPid());
|
||||
#endif
|
||||
|
||||
if (parser.getListDevices())
|
||||
{
|
||||
// Disable log on console, so we can more easily see device list
|
||||
logger->setConsoleMinMessageLevel(QtFatalMsg);
|
||||
// Don't pass logger to MainServer, otherwise it can reenable log output
|
||||
logger = nullptr;
|
||||
}
|
||||
|
||||
MainServer m(logger, parser, &a);
|
||||
|
||||
// This will cause the application to exit when the main core is finished
|
||||
QObject::connect(&m, SIGNAL(finished()), &a, SLOT(quit()));
|
||||
|
||||
return a.exec();
|
||||
if (parser.getListDevices())
|
||||
{
|
||||
// List available physical devices and exit
|
||||
RemoteTCPSinkStarter::listAvailableDevices();
|
||||
exit (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (parser.getRemoteTCPSink()) {
|
||||
RemoteTCPSinkStarter::start(parser);
|
||||
}
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
qtwebapp::LoggerWithFile *logger = new qtwebapp::LoggerWithFile(qApp);
|
||||
logger->installMsgHandler();
|
||||
int res = runQtApplication(argc, argv, logger);
|
||||
delete logger;
|
||||
qWarning("SDRangel quit.");
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
@ -285,6 +285,7 @@ set(sdrbase_SOURCES
|
||||
|
||||
mainparser.cpp
|
||||
maincore.cpp
|
||||
remotetcpsinkstarter.cpp
|
||||
|
||||
resources/webapi.qrc
|
||||
)
|
||||
@ -519,6 +520,7 @@ set(sdrbase_HEADERS
|
||||
|
||||
mainparser.h
|
||||
maincore.h
|
||||
remotetcpsinkstarter.h
|
||||
)
|
||||
|
||||
include_directories(
|
||||
|
@ -36,7 +36,13 @@ MainParser::MainParser() :
|
||||
"file",
|
||||
""),
|
||||
m_scratchOption("scratch", "Start from scratch (no current config)."),
|
||||
m_soapyOption("soapy", "Activate Soapy SDR support.")
|
||||
m_soapyOption("soapy", "Activate Soapy SDR support."),
|
||||
m_remoteTCPSinkOption("remote-tcp", "Start Remote TCP Sink"),
|
||||
m_remoteTCPSinkAddressOption("remote-tcp-address", "Remote TCP Sink interface IP address (Default 127.0.0.1).", "address", "127.0.0.1"),
|
||||
m_remoteTCPSinkPortOption("remote-tcp-port", "Remote TCP Sink port (Default 1234).", "port", "1234"),
|
||||
m_remoteTCPSinkHWTypeOption("remote-tcp-hwtype", "Remote TCP Sink device hardware type (Optional. E.g. RTLSDR/SDRplayV3/AirspyHF).", "hwtype"),
|
||||
m_remoteTCPSinkSerialOption("remote-tcp-serial", "Remote TCP Sink device serial (Optional).", "serial"),
|
||||
m_listDevicesOption("list-devices", "List available physical devices.")
|
||||
{
|
||||
|
||||
m_serverAddress = ""; // Bind to any address
|
||||
@ -44,6 +50,12 @@ MainParser::MainParser() :
|
||||
m_scratch = false;
|
||||
m_soapy = false;
|
||||
m_fftwfWindowFileName = "";
|
||||
m_remoteTCPSink = false;
|
||||
m_remoteTCPSinkAddress = "127.0.0.1";
|
||||
m_remoteTCPSinkPort = 1234;
|
||||
m_remoteTCPSinkHWType = "";
|
||||
m_remoteTCPSinkSerial = "";
|
||||
m_listDevices = false;
|
||||
|
||||
m_parser.setApplicationDescription("Software Defined Radio application");
|
||||
m_parser.addHelpOption();
|
||||
@ -54,6 +66,12 @@ MainParser::MainParser() :
|
||||
m_parser.addOption(m_fftwfWisdomOption);
|
||||
m_parser.addOption(m_scratchOption);
|
||||
m_parser.addOption(m_soapyOption);
|
||||
m_parser.addOption(m_remoteTCPSinkOption);
|
||||
m_parser.addOption(m_remoteTCPSinkAddressOption);
|
||||
m_parser.addOption(m_remoteTCPSinkPortOption);
|
||||
m_parser.addOption(m_remoteTCPSinkHWTypeOption);
|
||||
m_parser.addOption(m_remoteTCPSinkSerialOption);
|
||||
m_parser.addOption(m_listDevicesOption);
|
||||
}
|
||||
|
||||
MainParser::~MainParser()
|
||||
@ -65,12 +83,6 @@ void MainParser::parse(const QCoreApplication& app)
|
||||
|
||||
int pos;
|
||||
bool ok;
|
||||
|
||||
// server address
|
||||
|
||||
QString serverAddress = m_parser.value(m_serverAddressOption);
|
||||
if (!serverAddress.isEmpty())
|
||||
{
|
||||
QString ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])";
|
||||
QRegularExpression ipRegex ("^" + ipRange
|
||||
+ "\\." + ipRange
|
||||
@ -78,6 +90,11 @@ void MainParser::parse(const QCoreApplication& app)
|
||||
+ "\\." + ipRange + "$");
|
||||
QRegularExpressionValidator ipValidator(ipRegex);
|
||||
|
||||
// server address
|
||||
|
||||
QString serverAddress = m_parser.value(m_serverAddressOption);
|
||||
if (!serverAddress.isEmpty())
|
||||
{
|
||||
if (ipValidator.validate(serverAddress, pos) == QValidator::Acceptable) {
|
||||
m_serverAddress = serverAddress;
|
||||
} else {
|
||||
@ -104,4 +121,37 @@ void MainParser::parse(const QCoreApplication& app)
|
||||
|
||||
// Soapy SDR support
|
||||
m_soapy = m_parser.isSet(m_soapyOption);
|
||||
|
||||
// Remote TCP Sink options
|
||||
|
||||
m_remoteTCPSink = m_parser.isSet(m_remoteTCPSinkOption);
|
||||
|
||||
QString remoteTCPSinkAddress = m_parser.value(m_remoteTCPSinkAddressOption);
|
||||
if (!remoteTCPSinkAddress.isEmpty())
|
||||
{
|
||||
if (ipValidator.validate(remoteTCPSinkAddress, pos) == QValidator::Acceptable) {
|
||||
m_remoteTCPSinkAddress = remoteTCPSinkAddress;
|
||||
} else {
|
||||
qWarning() << "MainParser::parse: remote TCP Sink address invalid. Defaulting to " << m_remoteTCPSinkAddress;
|
||||
}
|
||||
}
|
||||
|
||||
QString remoteTCPSinkPortStr = m_parser.value(m_remoteTCPSinkPortOption);
|
||||
int remoteTCPSinkPort = remoteTCPSinkPortStr.toInt(&ok);
|
||||
|
||||
if (ok && (remoteTCPSinkPort > 1023) && (remoteTCPSinkPort < 65536)) {
|
||||
m_remoteTCPSinkPort = remoteTCPSinkPort;
|
||||
} else {
|
||||
qWarning() << "MainParser::parse: remote TCP Sink port invalid. Defaulting to " << m_serverPort;
|
||||
}
|
||||
|
||||
m_remoteTCPSinkHWType = m_parser.value(m_remoteTCPSinkHWTypeOption);
|
||||
m_remoteTCPSinkSerial = m_parser.value(m_remoteTCPSinkSerialOption);
|
||||
m_listDevices = m_parser.isSet(m_listDevicesOption);
|
||||
|
||||
if (m_remoteTCPSink && m_remoteTCPSinkHWType.isEmpty() && m_remoteTCPSinkSerial.isEmpty())
|
||||
{
|
||||
qCritical() << "You must specify a device with either -remote-tcp-hwtype or -remote-tcp-serial";
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,12 @@ public:
|
||||
bool getScratch() const { return m_scratch; }
|
||||
bool getSoapy() const { return m_soapy; }
|
||||
const QString& getFFTWFWisdomFileName() const { return m_fftwfWindowFileName; }
|
||||
bool getRemoteTCPSink() const { return m_remoteTCPSink; }
|
||||
const QString& getRemoteTCPSinkAddressOption() const { return m_remoteTCPSinkAddress; }
|
||||
int getRemoteTCPSinkPortOption() const { return m_remoteTCPSinkPort; }
|
||||
const QString& getRemoteTCPSinkHWType() const { return m_remoteTCPSinkHWType; }
|
||||
const QString& getRemoteTCPSinkSerial() const { return m_remoteTCPSinkSerial; }
|
||||
bool getListDevices() const { return m_listDevices; }
|
||||
|
||||
private:
|
||||
QString m_serverAddress;
|
||||
@ -44,6 +50,12 @@ private:
|
||||
QString m_fftwfWindowFileName;
|
||||
bool m_scratch;
|
||||
bool m_soapy;
|
||||
bool m_remoteTCPSink;
|
||||
QString m_remoteTCPSinkAddress;
|
||||
int m_remoteTCPSinkPort;
|
||||
QString m_remoteTCPSinkHWType;
|
||||
QString m_remoteTCPSinkSerial;
|
||||
bool m_listDevices;
|
||||
|
||||
QCommandLineParser m_parser;
|
||||
QCommandLineOption m_serverAddressOption;
|
||||
@ -51,6 +63,12 @@ private:
|
||||
QCommandLineOption m_fftwfWisdomOption;
|
||||
QCommandLineOption m_scratchOption;
|
||||
QCommandLineOption m_soapyOption;
|
||||
QCommandLineOption m_remoteTCPSinkOption;
|
||||
QCommandLineOption m_remoteTCPSinkAddressOption;
|
||||
QCommandLineOption m_remoteTCPSinkPortOption;
|
||||
QCommandLineOption m_remoteTCPSinkHWTypeOption;
|
||||
QCommandLineOption m_remoteTCPSinkSerialOption;
|
||||
QCommandLineOption m_listDevicesOption;
|
||||
};
|
||||
|
||||
|
||||
|
200
sdrbase/remotetcpsinkstarter.cpp
Normal file
200
sdrbase/remotetcpsinkstarter.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QTimer>
|
||||
|
||||
#include "remotetcpsinkstarter.h"
|
||||
|
||||
#include "maincore.h"
|
||||
#include "device/deviceset.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "device/deviceenumerator.h"
|
||||
#include "dsp/devicesamplesource.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGRemoteTCPSinkSettings.h"
|
||||
#include "SWGDeviceState.h"
|
||||
|
||||
// Lists available physical devices to stdout
|
||||
void RemoteTCPSinkStarter::listAvailableDevices()
|
||||
{
|
||||
int nbSamplingDevices = DeviceEnumerator::instance()->getNbRxSamplingDevices();
|
||||
|
||||
printf("Available devices:\n");
|
||||
for (int i = 0; i < nbSamplingDevices; i++)
|
||||
{
|
||||
const PluginInterface::SamplingDevice *samplingDevice;
|
||||
|
||||
samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(i);
|
||||
if (samplingDevice->type == PluginInterface::SamplingDevice::PhysicalDevice)
|
||||
{
|
||||
printf(" HWType: %s", qPrintable(samplingDevice->hardwareId));
|
||||
if (!samplingDevice->serial.isEmpty()) {
|
||||
printf(" Serial: %s", qPrintable(samplingDevice->serial));
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate specified sampling source device and create a RemoteTCPSink channel
|
||||
// on the specified address and port and start the device
|
||||
static void startRemoteTCPSink(const QString& address, int port, const QString& hwType, const QString& serial)
|
||||
{
|
||||
MainCore *mainCore = MainCore::instance();
|
||||
|
||||
// Delete any existing device sets, in case requested device is already in use
|
||||
int initialDeviceSets = mainCore->getDeviceSets().size();
|
||||
for (int i = 0; i < initialDeviceSets; i++)
|
||||
{
|
||||
MainCore::MsgRemoveLastDeviceSet *msg = MainCore::MsgRemoveLastDeviceSet::create();
|
||||
mainCore->getMainMessageQueue()->push(msg);
|
||||
}
|
||||
|
||||
// Wait until they've been deleted
|
||||
if (initialDeviceSets > 0)
|
||||
{
|
||||
do
|
||||
{
|
||||
QTime dieTime = QTime::currentTime().addMSecs(100);
|
||||
while (QTime::currentTime() < dieTime) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
}
|
||||
}
|
||||
while (mainCore->getDeviceSets().size() > 0);
|
||||
}
|
||||
|
||||
// Create DeviceSet
|
||||
unsigned int deviceSetIndex = mainCore->getDeviceSets().size();
|
||||
MainCore::MsgAddDeviceSet *msg = MainCore::MsgAddDeviceSet::create(0);
|
||||
mainCore->getMainMessageQueue()->push(msg);
|
||||
|
||||
// Switch to requested device type
|
||||
int nbSamplingDevices = DeviceEnumerator::instance()->getNbRxSamplingDevices();
|
||||
bool found = false;
|
||||
for (int i = 0; i < nbSamplingDevices; i++)
|
||||
{
|
||||
const PluginInterface::SamplingDevice *samplingDevice;
|
||||
|
||||
samplingDevice = DeviceEnumerator::instance()->getRxSamplingDevice(i);
|
||||
|
||||
if (!hwType.isEmpty() && (hwType != samplingDevice->hardwareId)) {
|
||||
continue;
|
||||
}
|
||||
if (!serial.isEmpty() && (serial != samplingDevice->serial)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int direction = 0;
|
||||
MainCore::MsgSetDevice *msg = MainCore::MsgSetDevice::create(deviceSetIndex, i, direction);
|
||||
mainCore->getMainMessageQueue()->push(msg);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
qCritical() << "startRemoteTCPSink: Failed to find device";
|
||||
return;
|
||||
}
|
||||
|
||||
// Add RemoteTCPSink channel
|
||||
PluginAPI::ChannelRegistrations *channelRegistrations = mainCore->getPluginManager()->getRxChannelRegistrations();
|
||||
int nbRegistrations = channelRegistrations->size();
|
||||
int index = 0;
|
||||
for (; index < nbRegistrations; index++)
|
||||
{
|
||||
if (channelRegistrations->at(index).m_channelId == "RemoteTCPSink") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (index < nbRegistrations)
|
||||
{
|
||||
MainCore::MsgAddChannel *msg = MainCore::MsgAddChannel::create(deviceSetIndex, index, 0);
|
||||
mainCore->getMainMessageQueue()->push(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical() << "startRemoteTCPSink: RemoteTCPSink is not available";
|
||||
return;
|
||||
}
|
||||
int channelIndex = 0;
|
||||
|
||||
// Wait until device & channel are created - is there a better way?
|
||||
DeviceSet *deviceSet = nullptr;
|
||||
ChannelAPI *channelAPI = nullptr;
|
||||
do
|
||||
{
|
||||
QTime dieTime = QTime::currentTime().addMSecs(100);
|
||||
while (QTime::currentTime() < dieTime) {
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
}
|
||||
|
||||
if (mainCore->getDeviceSets().size() > deviceSetIndex)
|
||||
{
|
||||
deviceSet = mainCore->getDeviceSets()[deviceSetIndex];
|
||||
if (deviceSet) {
|
||||
channelAPI = deviceSet->m_deviceAPI->getChanelSinkAPIAt(channelIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
while (channelAPI == nullptr);
|
||||
|
||||
// Set TCP settings
|
||||
QStringList channelSettingsKeys = {"dataAddress", "dataPort"};
|
||||
SWGSDRangel::SWGChannelSettings response;
|
||||
response.init();
|
||||
SWGSDRangel::SWGRemoteTCPSinkSettings *sinkSettings = response.getRemoteTcpSinkSettings();
|
||||
sinkSettings->setDataAddress(new QString(address));
|
||||
sinkSettings->setDataPort(port);
|
||||
QString errorMessage;
|
||||
channelAPI->webapiSettingsPutPatch(false, channelSettingsKeys, response, errorMessage);
|
||||
|
||||
// Wait some time for settings to be applied
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
|
||||
|
||||
// Start the device (use WebAPI so GUI is updated)
|
||||
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
|
||||
QStringList deviceActionsKeys;
|
||||
SWGSDRangel::SWGDeviceState state;
|
||||
state.init();
|
||||
int res = source->webapiRun(true, state, errorMessage);
|
||||
if (res != 200) {
|
||||
qCritical() << "startRemoteTCPSink: Failed to start device: " << res;
|
||||
} else {
|
||||
qInfo().nospace().noquote() << "Remote TCP Sink started on " << address << ":" << port;
|
||||
}
|
||||
}
|
||||
|
||||
// Start Remote TCP Sink on specified device, with specified address and port
|
||||
void RemoteTCPSinkStarter::start(const MainParser& parser)
|
||||
{
|
||||
QString remoteTCPSinkAddress = parser.getRemoteTCPSinkAddressOption();
|
||||
int remoteTCPSinkPort = parser.getRemoteTCPSinkPortOption();
|
||||
QString remoteTCPSinkHWType = parser.getRemoteTCPSinkHWType();
|
||||
QString remoteTCPSinkSerial = parser.getRemoteTCPSinkSerial();
|
||||
|
||||
QTimer::singleShot(250, [=] {
|
||||
startRemoteTCPSink(
|
||||
remoteTCPSinkAddress,
|
||||
remoteTCPSinkPort,
|
||||
remoteTCPSinkHWType,
|
||||
remoteTCPSinkSerial);
|
||||
});
|
||||
}
|
32
sdrbase/remotetcpsinkstarter.h
Normal file
32
sdrbase/remotetcpsinkstarter.h
Normal file
@ -0,0 +1,32 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef REMOTETCPSINKSTARTER_H
|
||||
#define REMOTETCPSINKSTARTER_H
|
||||
|
||||
#include "mainparser.h"
|
||||
#include "export.h"
|
||||
|
||||
class SDRBASE_API RemoteTCPSinkStarter {
|
||||
|
||||
public:
|
||||
static void listAvailableDevices();
|
||||
static void start(const MainParser& parser);
|
||||
|
||||
};
|
||||
|
||||
#endif /* REMOTETCPSINKSTARTER_H */
|
Loading…
Reference in New Issue
Block a user