|
@@ -9,10 +9,13 @@
|
|
|
#include "qhttpserverrequest.hpp"
|
|
|
|
|
|
#include <QUrl>
|
|
|
+#include <QUdpSocket>
|
|
|
#include <QXmlStreamWriter>
|
|
|
|
|
|
using namespace qhttp::server;
|
|
|
|
|
|
+#define ROKU_SERIAL_NUMBER "12345678900"
|
|
|
+
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
bool InputRoku::initInput()
|
|
|
{
|
|
@@ -26,14 +29,93 @@ bool InputRoku::initInput()
|
|
|
|
|
|
connect(m_server, &QHttpServer::newRequest, this, &InputRoku::handleRequest);
|
|
|
|
|
|
+ m_ssdpSocket = new QUdpSocket(this);
|
|
|
+ if (!m_ssdpSocket->bind(QHostAddress::AnyIPv4, 1900, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress))
|
|
|
+ {
|
|
|
+ QLOG_WARN() << "Failed to bind to SSDP socket";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ QHostAddress multicast("239.255.255.250");
|
|
|
+ m_ssdpSocket->joinMulticastGroup(multicast);
|
|
|
+
|
|
|
+ connect(m_ssdpSocket, &QUdpSocket::readyRead, this, &InputRoku::ssdpRead);
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
+void InputRoku::ssdpRead()
|
|
|
+{
|
|
|
+ while (m_ssdpSocket->hasPendingDatagrams())
|
|
|
+ {
|
|
|
+ QLOG_DEBUG() << "Got SSDP datagram";
|
|
|
+
|
|
|
+ QByteArray datagram;
|
|
|
+ datagram.resize((int)m_ssdpSocket->pendingDatagramSize());
|
|
|
+
|
|
|
+ QHostAddress sender;
|
|
|
+ quint16 senderPort;
|
|
|
+
|
|
|
+ m_ssdpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
|
|
|
+
|
|
|
+ QLOG_DEBUG() << "datagram:" << QString::fromUtf8(datagram);
|
|
|
+ parseSSDPData(datagram, sender, senderPort);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
+void InputRoku::parseSSDPData(const QByteArray& data, const QHostAddress& sender, quint16 port)
|
|
|
+{
|
|
|
+ if (data.contains("M-SEARCH * HTTP/1.1"))
|
|
|
+ m_ssdpSocket->writeDatagram(getSSDPPacket(), sender, port);
|
|
|
+}
|
|
|
+
|
|
|
+/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
+QByteArray InputRoku::getSSDPPacket()
|
|
|
+{
|
|
|
+
|
|
|
+ /*
|
|
|
+ HTTP/1.1 200 OK
|
|
|
+ Cache-Control: max-age=300
|
|
|
+ ST: roku:ecp
|
|
|
+ Location: http://192.168.1.134:8060/
|
|
|
+ USN: uuid:roku:ecp:P0A070000007
|
|
|
+ */
|
|
|
+
|
|
|
+ QByteArray packetData;
|
|
|
+
|
|
|
+ // Header
|
|
|
+ packetData.append("HTTP/1.1 200 OK\r\n");
|
|
|
+ packetData.append("Cache-Control: max-age=300\r\n");
|
|
|
+ packetData.append("ST: roku:ecp\r\n");
|
|
|
+ packetData.append("Ext: \r\n");
|
|
|
+ packetData.append("Server: Roku UPnP/1.0 MiniUPnPd/1.4\r\n");
|
|
|
+
|
|
|
+ packetData.append("Location: http://");
|
|
|
+ packetData.append(Utils::PrimaryIPv4Address());
|
|
|
+ packetData.append(":8060/\r\n");
|
|
|
+
|
|
|
+ packetData.append("USN: uuid:roku:ecp:");
|
|
|
+ packetData.append(ROKU_SERIAL_NUMBER);
|
|
|
+ packetData.append("\r\n\r\n");
|
|
|
+
|
|
|
+ QLOG_DEBUG() << "Reply: " << QString::fromUtf8(packetData);
|
|
|
+
|
|
|
+ return packetData;
|
|
|
+}
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
void InputRoku::handleRequest(QHttpRequest* request, QHttpResponse* response)
|
|
|
{
|
|
|
QString path = request->url().path();
|
|
|
- if (path == "/query/apps")
|
|
|
+
|
|
|
+ QLOG_DEBUG() << "Request:" << path;
|
|
|
+
|
|
|
+ if (path == "/")
|
|
|
+ {
|
|
|
+ handleRootInfo(request, response);
|
|
|
+ }
|
|
|
+ else if (path == "/query/apps")
|
|
|
{
|
|
|
handleQueryApps(request, response);
|
|
|
}
|
|
@@ -154,3 +236,48 @@ void InputRoku::handleKeyPress(QHttpRequest* request, QHttpResponse* response)
|
|
|
response->end();
|
|
|
return;
|
|
|
}
|
|
|
+
|
|
|
+/////////////////////////////////////////////////////////////////////////////////////////
|
|
|
+void InputRoku::handleRootInfo(QHttpRequest* request, QHttpResponse* response)
|
|
|
+{
|
|
|
+ QByteArray data;
|
|
|
+ QXmlStreamWriter writer(&data);
|
|
|
+ writer.setAutoFormatting(true);
|
|
|
+
|
|
|
+ writer.writeStartDocument();
|
|
|
+ writer.writeStartElement("urn:schemas-upnp-org:device-1-0", "root");
|
|
|
+
|
|
|
+ writer.writeStartElement("specVersion");
|
|
|
+ writer.writeTextElement("major", "1");
|
|
|
+ writer.writeTextElement("minor", "0");
|
|
|
+ writer.writeEndElement(); // specVersion
|
|
|
+
|
|
|
+ writer.writeStartElement("device");
|
|
|
+ writer.writeTextElement("deviceType", "urn:roku-com:device:player:1-0");
|
|
|
+ writer.writeTextElement("friendlyName", Utils::ComputerName());
|
|
|
+ writer.writeTextElement("manufacturer", "Roku");
|
|
|
+ writer.writeTextElement("manufacturerURL", "http://www.roku.com");
|
|
|
+ writer.writeTextElement("modelDescription", "Roku Streaming Player Network Media");
|
|
|
+ writer.writeTextElement("modelName", "Roku 3");
|
|
|
+ writer.writeTextElement("modelNumber", "4200X");
|
|
|
+ writer.writeTextElement("modelURL", "http://www.roku.com");
|
|
|
+ writer.writeTextElement("serialNumber", ROKU_SERIAL_NUMBER);
|
|
|
+ writer.writeTextElement("UDN", "uuid:" + Utils::ClientUUID());
|
|
|
+
|
|
|
+ writer.writeStartElement("serviceList");
|
|
|
+ writer.writeStartElement("service");
|
|
|
+ writer.writeTextElement("serviceType", "urn:roku-com:service:ecp:1");
|
|
|
+ writer.writeTextElement("serviceId", "urn:roku-com:serviceId:ecp1-0");
|
|
|
+ writer.writeTextElement("controlURL", "");
|
|
|
+ writer.writeTextElement("eventSubURL", "");
|
|
|
+ writer.writeTextElement("SCPDURL", "ecp_SCPD.xml");
|
|
|
+ writer.writeEndElement(); // service
|
|
|
+ writer.writeEndElement(); // serviceList
|
|
|
+
|
|
|
+ writer.writeEndElement(); // root
|
|
|
+ writer.writeEndDocument();
|
|
|
+
|
|
|
+ response->setStatusCode(qhttp::ESTATUS_OK);
|
|
|
+ response->write(data);
|
|
|
+ response->end();
|
|
|
+}
|