InputRoku.cpp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. //
  2. // Created by Tobias Hieta on 02/02/16.
  3. //
  4. #include "InputRoku.h"
  5. #include "QsLog.h"
  6. #include "qhttpserverresponse.hpp"
  7. #include "qhttpserverrequest.hpp"
  8. #include <QUrl>
  9. #include <QUdpSocket>
  10. #include <QTimeZone>
  11. #include <QXmlStreamWriter>
  12. using namespace qhttp::server;
  13. #define ROKU_SERIAL_NUMBER "12345678900"
  14. /////////////////////////////////////////////////////////////////////////////////////////
  15. bool InputRoku::initInput()
  16. {
  17. m_server = new QHttpServer(this);
  18. if (!m_server->listen(QHostAddress::Any, 8060))
  19. {
  20. QLOG_WARN() << "Failed to start roku component on port 8060";
  21. return false;
  22. }
  23. connect(m_server, &QHttpServer::newRequest, this, &InputRoku::handleRequest);
  24. m_ssdpSocket = new QUdpSocket(this);
  25. if (!m_ssdpSocket->bind(QHostAddress::AnyIPv4, 1900, QUdpSocket::ReuseAddressHint | QUdpSocket::ShareAddress))
  26. {
  27. QLOG_WARN() << "Failed to bind to SSDP socket";
  28. return false;
  29. }
  30. QHostAddress multicast("239.255.255.250");
  31. m_ssdpSocket->joinMulticastGroup(multicast);
  32. connect(m_ssdpSocket, &QUdpSocket::readyRead, this, &InputRoku::ssdpRead);
  33. return true;
  34. }
  35. /////////////////////////////////////////////////////////////////////////////////////////
  36. void InputRoku::ssdpRead()
  37. {
  38. while (m_ssdpSocket->hasPendingDatagrams())
  39. {
  40. QByteArray datagram;
  41. datagram.resize((int)m_ssdpSocket->pendingDatagramSize());
  42. QHostAddress sender;
  43. quint16 senderPort;
  44. m_ssdpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort);
  45. parseSSDPData(datagram, sender, senderPort);
  46. }
  47. }
  48. /////////////////////////////////////////////////////////////////////////////////////////
  49. void InputRoku::parseSSDPData(const QByteArray& data, const QHostAddress& sender, quint16 port)
  50. {
  51. if (data.contains("M-SEARCH * HTTP/1.1"))
  52. m_ssdpSocket->writeDatagram(getSSDPPacket(), sender, port);
  53. }
  54. /////////////////////////////////////////////////////////////////////////////////////////
  55. QByteArray InputRoku::getSSDPPacket()
  56. {
  57. QByteArray packetData;
  58. // Header
  59. packetData.append("HTTP/1.1 200 OK\r\n");
  60. packetData.append("Cache-Control: max-age=300\r\n");
  61. packetData.append("ST: roku:ecp\r\n");
  62. packetData.append("Ext: \r\n");
  63. packetData.append("Server: Roku UPnP/1.0 MiniUPnPd/1.4\r\n");
  64. packetData.append("Location: http://");
  65. packetData.append(Utils::PrimaryIPv4Address());
  66. packetData.append(":8060/\r\n");
  67. packetData.append("USN: uuid:roku:ecp:");
  68. packetData.append(ROKU_SERIAL_NUMBER);
  69. packetData.append("\r\n\r\n");
  70. return packetData;
  71. }
  72. /////////////////////////////////////////////////////////////////////////////////////////
  73. void InputRoku::handleRequest(QHttpRequest* request, QHttpResponse* response)
  74. {
  75. QString path = request->url().path();
  76. if (path == "/")
  77. {
  78. handleRootInfo(request, response);
  79. }
  80. else if (path == "/query/apps")
  81. {
  82. handleQueryApps(request, response);
  83. }
  84. else if (path == "/query/device-info")
  85. {
  86. handleQueryDeviceInfo(request, response);
  87. }
  88. else if (path.startsWith("/keypress/") || path.startsWith("/keydown/"))
  89. {
  90. handleKeyPress(request, response);
  91. }
  92. else if (path.startsWith("/keyup/"))
  93. {
  94. response->setStatusCode(qhttp::ESTATUS_OK);
  95. response->end();
  96. }
  97. else
  98. {
  99. QLOG_WARN() << "Could not handle roku input:" << path;
  100. response->setStatusCode(qhttp::ESTATUS_NOT_FOUND);
  101. response->end();
  102. }
  103. }
  104. /////////////////////////////////////////////////////////////////////////////////////////
  105. void InputRoku::handleQueryApps(QHttpRequest* request, QHttpResponse* response)
  106. {
  107. if (request->method() != qhttp::EHTTP_GET)
  108. {
  109. response->setStatusCode(qhttp::ESTATUS_METHOD_NOT_ALLOWED);
  110. response->end();
  111. return;
  112. }
  113. QByteArray data;
  114. QXmlStreamWriter writer(&data);
  115. writer.setAutoFormatting(true);
  116. writer.writeStartDocument();
  117. writer.writeStartElement("apps");
  118. writer.writeStartElement("app");
  119. writer.writeAttribute("id", "1");
  120. writer.writeCharacters("Plex Media Player");
  121. writer.writeEndElement(); // app
  122. writer.writeEndElement(); // apps
  123. writer.writeEndDocument();
  124. response->setStatusCode(qhttp::ESTATUS_OK);
  125. response->write(data);
  126. response->end();
  127. }
  128. /////////////////////////////////////////////////////////////////////////////////////////
  129. void InputRoku::handleQueryDeviceInfo(QHttpRequest* request, QHttpResponse* response)
  130. {
  131. QByteArray data;
  132. QXmlStreamWriter writer(&data);
  133. writer.setAutoFormatting(true);
  134. writer.writeStartDocument();
  135. writer.writeStartElement("device-info");
  136. writer.writeTextElement("udn", Utils::ClientUUID());
  137. writer.writeTextElement("serial-number", ROKU_SERIAL_NUMBER);
  138. writer.writeTextElement("device-id", ROKU_SERIAL_NUMBER);
  139. writer.writeTextElement("vendor-name", "Roku");
  140. writer.writeTextElement("model-number", "4200X");
  141. writer.writeTextElement("model-name", "Roku 3");
  142. writer.writeTextElement("wifi-mac", "00:00:00:00:00:00");
  143. writer.writeTextElement("ethernet-mac", "00:00:00:00:00:00");
  144. writer.writeTextElement("network-type", "ethernet");
  145. writer.writeTextElement("user-device-name", Utils::ComputerName());
  146. writer.writeTextElement("software-version", "7.00");
  147. writer.writeTextElement("software-build", "09021");
  148. QLocale locale = QLocale::system();
  149. QString lcl = locale.name();
  150. QStringList landc = lcl.split("_");
  151. writer.writeTextElement("language", landc.value(0));
  152. writer.writeTextElement("country", landc.value(1));
  153. writer.writeTextElement("locale", locale.name());
  154. QTimeZone tz = QTimeZone::systemTimeZone();
  155. writer.writeTextElement("time-zone", tz.displayName(QTimeZone::StandardTime, QTimeZone::LongName));
  156. writer.writeTextElement("time-zone-offset", QString::number(tz.offsetFromUtc(QDateTime::currentDateTime()) / 60));
  157. writer.writeEndElement(); // device-info
  158. writer.writeEndDocument();
  159. response->setStatusCode(qhttp::ESTATUS_OK);
  160. response->write(data);
  161. response->end();
  162. }
  163. /////////////////////////////////////////////////////////////////////////////////////////
  164. void InputRoku::handleKeyPress(QHttpRequest* request, QHttpResponse* response)
  165. {
  166. QString path = request->url().path();
  167. QStringList pathsplit = path.split("/");
  168. if (pathsplit.count() != 3)
  169. {
  170. response->setStatusCode(qhttp::ESTATUS_BAD_REQUEST);
  171. response->end();
  172. return;
  173. }
  174. emit receivedInput("roku", pathsplit.value(2));
  175. response->setStatusCode(qhttp::ESTATUS_OK);
  176. response->end();
  177. return;
  178. }
  179. /////////////////////////////////////////////////////////////////////////////////////////
  180. void InputRoku::handleRootInfo(QHttpRequest* request, QHttpResponse* response)
  181. {
  182. QByteArray data;
  183. QXmlStreamWriter writer(&data);
  184. writer.setAutoFormatting(true);
  185. writer.writeStartDocument();
  186. writer.writeStartElement("urn:schemas-upnp-org:device-1-0", "root");
  187. writer.writeStartElement("specVersion");
  188. writer.writeTextElement("major", "1");
  189. writer.writeTextElement("minor", "0");
  190. writer.writeEndElement(); // specVersion
  191. writer.writeStartElement("device");
  192. writer.writeTextElement("deviceType", "urn:roku-com:device:player:1-0");
  193. writer.writeTextElement("friendlyName", Utils::ComputerName());
  194. writer.writeTextElement("manufacturer", "Roku");
  195. writer.writeTextElement("manufacturerURL", "http://www.roku.com");
  196. writer.writeTextElement("modelDescription", "Roku Streaming Player Network Media");
  197. writer.writeTextElement("modelName", "Roku 3");
  198. writer.writeTextElement("modelNumber", "4200X");
  199. writer.writeTextElement("modelURL", "http://www.roku.com");
  200. writer.writeTextElement("serialNumber", ROKU_SERIAL_NUMBER);
  201. writer.writeTextElement("UDN", "uuid:" + Utils::ClientUUID());
  202. writer.writeStartElement("serviceList");
  203. writer.writeStartElement("service");
  204. writer.writeTextElement("serviceType", "urn:roku-com:service:ecp:1");
  205. writer.writeTextElement("serviceId", "urn:roku-com:serviceId:ecp1-0");
  206. writer.writeTextElement("controlURL", "");
  207. writer.writeTextElement("eventSubURL", "");
  208. writer.writeTextElement("SCPDURL", "ecp_SCPD.xml");
  209. writer.writeEndElement(); // service
  210. writer.writeEndElement(); // serviceList
  211. writer.writeEndElement(); // root
  212. writer.writeEndDocument();
  213. response->setStatusCode(qhttp::ESTATUS_OK);
  214. response->write(data);
  215. response->end();
  216. }