2019-02-17 15:26:11 +01:00
|
|
|
// qt
|
|
|
|
#include <QTcpSocket>
|
|
|
|
#include <QHostAddress>
|
|
|
|
#include <QTimer>
|
|
|
|
#include <QRgb>
|
|
|
|
|
2021-02-23 14:25:32 +01:00
|
|
|
// project includes
|
|
|
|
#include "ProtoClientConnection.h"
|
|
|
|
|
|
|
|
|
2020-07-12 09:19:59 +02:00
|
|
|
// TODO Remove this class if third-party apps have been migrated (eg. Hyperion Android Grabber, Windows Screen grabber etc.)
|
2019-02-17 15:26:11 +01:00
|
|
|
|
2020-08-08 13:09:15 +02:00
|
|
|
ProtoClientConnection::ProtoClientConnection(QTcpSocket* socket, int timeout, QObject *parent)
|
2019-02-17 15:26:11 +01:00
|
|
|
: QObject(parent)
|
|
|
|
, _log(Logger::getInstance("PROTOSERVER"))
|
|
|
|
, _socket(socket)
|
|
|
|
, _clientAddress(socket->peerAddress().toString())
|
|
|
|
, _timeoutTimer(new QTimer(this))
|
|
|
|
, _timeout(timeout * 1000)
|
|
|
|
, _priority()
|
|
|
|
{
|
|
|
|
// timer setup
|
|
|
|
_timeoutTimer->setSingleShot(true);
|
|
|
|
_timeoutTimer->setInterval(_timeout);
|
|
|
|
connect(_timeoutTimer, &QTimer::timeout, this, &ProtoClientConnection::forceClose);
|
|
|
|
|
|
|
|
// connect socket signals
|
|
|
|
connect(_socket, &QTcpSocket::readyRead, this, &ProtoClientConnection::readyRead);
|
|
|
|
connect(_socket, &QTcpSocket::disconnected, this, &ProtoClientConnection::disconnected);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::readyRead()
|
|
|
|
{
|
|
|
|
_receiveBuffer += _socket->readAll();
|
|
|
|
|
|
|
|
// check if we can read a message size
|
|
|
|
if (_receiveBuffer.size() <= 4)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read the message size
|
|
|
|
uint32_t messageSize =
|
|
|
|
((_receiveBuffer[0]<<24) & 0xFF000000) |
|
|
|
|
((_receiveBuffer[1]<<16) & 0x00FF0000) |
|
|
|
|
((_receiveBuffer[2]<< 8) & 0x0000FF00) |
|
|
|
|
((_receiveBuffer[3] ) & 0x000000FF);
|
|
|
|
|
|
|
|
// check if we can read a complete message
|
|
|
|
if ((uint32_t) _receiveBuffer.size() < messageSize + 4)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read a message
|
|
|
|
proto::HyperionRequest message;
|
|
|
|
if (!message.ParseFromArray(_receiveBuffer.data() + 4, messageSize))
|
|
|
|
{
|
|
|
|
sendErrorReply("Unable to parse message");
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle the message
|
|
|
|
handleMessage(message);
|
|
|
|
|
|
|
|
// remove message data from buffer
|
|
|
|
_receiveBuffer = _receiveBuffer.mid(messageSize + 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::forceClose()
|
|
|
|
{
|
|
|
|
_socket->close();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::disconnected()
|
|
|
|
{
|
|
|
|
Debug(_log, "Socket Closed");
|
2019-07-29 19:09:26 +02:00
|
|
|
_socket->deleteLater();
|
|
|
|
emit clearGlobalInput(_priority);
|
2019-02-17 15:26:11 +01:00
|
|
|
emit clientDisconnected();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::handleMessage(const proto::HyperionRequest & message)
|
|
|
|
{
|
|
|
|
switch (message.command())
|
|
|
|
{
|
|
|
|
case proto::HyperionRequest::COLOR:
|
|
|
|
if (!message.HasExtension(proto::ColorRequest::colorRequest))
|
|
|
|
{
|
|
|
|
sendErrorReply("Received COLOR command without ColorRequest");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
handleColorCommand(message.GetExtension(proto::ColorRequest::colorRequest));
|
|
|
|
break;
|
|
|
|
case proto::HyperionRequest::IMAGE:
|
|
|
|
if (!message.HasExtension(proto::ImageRequest::imageRequest))
|
|
|
|
{
|
|
|
|
sendErrorReply("Received IMAGE command without ImageRequest");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
handleImageCommand(message.GetExtension(proto::ImageRequest::imageRequest));
|
|
|
|
break;
|
|
|
|
case proto::HyperionRequest::CLEAR:
|
|
|
|
if (!message.HasExtension(proto::ClearRequest::clearRequest))
|
|
|
|
{
|
|
|
|
sendErrorReply("Received CLEAR command without ClearRequest");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
handleClearCommand(message.GetExtension(proto::ClearRequest::clearRequest));
|
|
|
|
break;
|
|
|
|
case proto::HyperionRequest::CLEARALL:
|
|
|
|
handleClearallCommand();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
handleNotImplemented();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::handleColorCommand(const proto::ColorRequest &message)
|
|
|
|
{
|
|
|
|
// extract parameters
|
|
|
|
int priority = message.priority();
|
|
|
|
int duration = message.has_duration() ? message.duration() : -1;
|
2020-02-26 18:54:56 +01:00
|
|
|
std::vector<ColorRgb> color{ ColorRgb{ uint8_t(qRed(message.rgbcolor())), uint8_t(qGreen(message.rgbcolor())), uint8_t(qBlue(message.rgbcolor())) } };
|
2019-02-17 15:26:11 +01:00
|
|
|
|
2019-08-02 21:12:13 +02:00
|
|
|
if (priority < 100 || priority >= 200)
|
|
|
|
{
|
2019-08-12 19:06:11 +02:00
|
|
|
sendErrorReply("The priority " + std::to_string(priority) + " is not in the valid priority range between 100 and 199.");
|
|
|
|
Error(_log, "The priority %d is not in the proto-connection range between 100 and 199.", priority);
|
2019-08-02 21:12:13 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-17 15:26:11 +01:00
|
|
|
// make sure the prio is registered before setColor()
|
|
|
|
if(priority != _priority)
|
|
|
|
{
|
2019-07-29 19:09:26 +02:00
|
|
|
emit clearGlobalInput(_priority);
|
|
|
|
emit registerGlobalInput(priority, hyperion::COMP_PROTOSERVER, "Proto@"+_clientAddress);
|
2019-02-17 15:26:11 +01:00
|
|
|
_priority = priority;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set output
|
2019-07-29 19:09:26 +02:00
|
|
|
emit setGlobalInputColor(_priority, color, duration);
|
2019-02-17 15:26:11 +01:00
|
|
|
|
|
|
|
// send reply
|
|
|
|
sendSuccessReply();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::handleImageCommand(const proto::ImageRequest &message)
|
|
|
|
{
|
|
|
|
// extract parameters
|
|
|
|
int priority = message.priority();
|
|
|
|
int duration = message.has_duration() ? message.duration() : -1;
|
|
|
|
int width = message.imagewidth();
|
|
|
|
int height = message.imageheight();
|
|
|
|
const std::string & imageData = message.imagedata();
|
|
|
|
|
2019-08-02 21:12:13 +02:00
|
|
|
if (priority < 100 || priority >= 200)
|
|
|
|
{
|
2019-08-12 19:06:11 +02:00
|
|
|
sendErrorReply("The priority " + std::to_string(priority) + " is not in the valid priority range between 100 and 199.");
|
|
|
|
Error(_log, "The priority %d is not in the proto-connection range between 100 and 199.", priority);
|
2019-08-02 21:12:13 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-17 15:26:11 +01:00
|
|
|
// make sure the prio is registered before setInput()
|
|
|
|
if(priority != _priority)
|
|
|
|
{
|
2019-07-29 19:09:26 +02:00
|
|
|
emit clearGlobalInput(_priority);
|
|
|
|
emit registerGlobalInput(priority, hyperion::COMP_PROTOSERVER, "Proto@"+_clientAddress);
|
2019-02-17 15:26:11 +01:00
|
|
|
_priority = priority;
|
|
|
|
}
|
|
|
|
|
2022-01-09 17:23:50 +01:00
|
|
|
if (width <= 0 || height <= 0)
|
|
|
|
{
|
|
|
|
sendErrorReply("Size of image data does not match with the width and height");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-17 15:26:11 +01:00
|
|
|
// check consistency of the size of the received data
|
2022-01-09 17:23:50 +01:00
|
|
|
int channelCount = (int)imageData.size()/(width*height);
|
|
|
|
if (channelCount != 3 && channelCount != 4)
|
2019-02-17 15:26:11 +01:00
|
|
|
{
|
|
|
|
sendErrorReply("Size of image data does not match with the width and height");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create ImageRgb
|
2022-01-09 17:23:50 +01:00
|
|
|
Image<ColorRgb> imageRGB(width, height);
|
|
|
|
if (channelCount == 3)
|
|
|
|
{
|
|
|
|
memmove(imageRGB.memptr(), imageData.c_str(), imageData.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (channelCount == 4)
|
|
|
|
{
|
|
|
|
for (int source=0, destination=0; source < width * height * static_cast<int>(sizeof(ColorRgb)); source+=sizeof(ColorRgb), destination+=sizeof(ColorRgba))
|
|
|
|
{
|
|
|
|
memmove((uint8_t*)imageRGB.memptr() + source, imageData.c_str() + destination, sizeof(ColorRgb));
|
|
|
|
}
|
|
|
|
}
|
2019-02-17 15:26:11 +01:00
|
|
|
|
2022-01-09 17:23:50 +01:00
|
|
|
emit setGlobalInputImage(_priority, imageRGB, duration);
|
2022-05-29 17:47:08 +02:00
|
|
|
emit setBufferImage("ProtoBuffer", imageRGB);
|
2019-02-17 15:26:11 +01:00
|
|
|
|
|
|
|
// send reply
|
|
|
|
sendSuccessReply();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ProtoClientConnection::handleClearCommand(const proto::ClearRequest &message)
|
|
|
|
{
|
|
|
|
// extract parameters
|
|
|
|
int priority = message.priority();
|
|
|
|
|
|
|
|
// clear priority
|
2019-07-29 19:09:26 +02:00
|
|
|
emit clearGlobalInput(priority);
|
2019-02-17 15:26:11 +01:00
|
|
|
// send reply
|
|
|
|
sendSuccessReply();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::handleClearallCommand()
|
|
|
|
{
|
2019-07-29 19:09:26 +02:00
|
|
|
// clear all priority
|
2020-02-26 18:54:56 +01:00
|
|
|
emit clearGlobalInput(-1);
|
2019-02-17 15:26:11 +01:00
|
|
|
|
|
|
|
// send reply
|
|
|
|
sendSuccessReply();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void ProtoClientConnection::handleNotImplemented()
|
|
|
|
{
|
|
|
|
sendErrorReply("Command not implemented");
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::sendMessage(const google::protobuf::Message &message)
|
|
|
|
{
|
|
|
|
std::string serializedReply = message.SerializeAsString();
|
2020-11-14 17:58:56 +01:00
|
|
|
uint32_t size = static_cast<uint32_t>(serializedReply.size());
|
2019-02-17 15:26:11 +01:00
|
|
|
uint8_t sizeData[] = {uint8_t(size >> 24), uint8_t(size >> 16), uint8_t(size >> 8), uint8_t(size)};
|
|
|
|
_socket->write((const char *) sizeData, sizeof(sizeData));
|
|
|
|
_socket->write(serializedReply.data(), serializedReply.length());
|
|
|
|
_socket->flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::sendSuccessReply()
|
|
|
|
{
|
|
|
|
// create reply
|
|
|
|
proto::HyperionReply reply;
|
|
|
|
reply.set_type(proto::HyperionReply::REPLY);
|
|
|
|
reply.set_success(true);
|
|
|
|
|
|
|
|
// send reply
|
|
|
|
sendMessage(reply);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProtoClientConnection::sendErrorReply(const std::string &error)
|
|
|
|
{
|
|
|
|
// create reply
|
|
|
|
proto::HyperionReply reply;
|
|
|
|
reply.set_type(proto::HyperionReply::REPLY);
|
|
|
|
reply.set_success(false);
|
|
|
|
reply.set_error(error);
|
|
|
|
|
|
|
|
// send reply
|
|
|
|
sendMessage(reply);
|
|
|
|
}
|