InputRoku.cpp 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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/") || path.startsWith("/keyup/"))
  89. {
  90. handleKeyPress(request, response);
  91. }
  92. else
  93. {
  94. QLOG_WARN() << "Could not handle roku input:" << path;
  95. response->setStatusCode(qhttp::ESTATUS_NOT_FOUND);
  96. response->end();
  97. }
  98. }
  99. /////////////////////////////////////////////////////////////////////////////////////////
  100. void InputRoku::handleQueryApps(QHttpRequest* request, QHttpResponse* response)
  101. {
  102. if (request->method() != qhttp::EHTTP_GET)
  103. {
  104. response->setStatusCode(qhttp::ESTATUS_METHOD_NOT_ALLOWED);
  105. response->end();
  106. return;
  107. }
  108. QByteArray data;
  109. QXmlStreamWriter writer(&data);
  110. writer.setAutoFormatting(true);
  111. writer.writeStartDocument();
  112. writer.writeStartElement("apps");
  113. writer.writeStartElement("app");
  114. writer.writeAttribute("id", "1");
  115. writer.writeCharacters("Plex Media Player");
  116. writer.writeEndElement(); // app
  117. writer.writeEndElement(); // apps
  118. writer.writeEndDocument();
  119. response->setStatusCode(qhttp::ESTATUS_OK);
  120. response->write(data);
  121. response->end();
  122. }
  123. /////////////////////////////////////////////////////////////////////////////////////////
  124. void InputRoku::handleQueryDeviceInfo(QHttpRequest* request, QHttpResponse* response)
  125. {
  126. QByteArray data;
  127. QXmlStreamWriter writer(&data);
  128. writer.setAutoFormatting(true);
  129. writer.writeStartDocument();
  130. writer.writeStartElement("device-info");
  131. writer.writeTextElement("udn", Utils::ClientUUID());
  132. writer.writeTextElement("serial-number", ROKU_SERIAL_NUMBER);
  133. writer.writeTextElement("device-id", ROKU_SERIAL_NUMBER);
  134. writer.writeTextElement("vendor-name", "Roku");
  135. writer.writeTextElement("model-number", "4200X");
  136. writer.writeTextElement("model-name", "Roku 3");
  137. writer.writeTextElement("wifi-mac", "00:00:00:00:00:00");
  138. writer.writeTextElement("ethernet-mac", "00:00:00:00:00:00");
  139. writer.writeTextElement("network-type", "ethernet");
  140. writer.writeTextElement("user-device-name", Utils::ComputerName());
  141. writer.writeTextElement("software-version", "7.00");
  142. writer.writeTextElement("software-build", "09021");
  143. QLocale locale = QLocale::system();
  144. QString lcl = locale.name();
  145. QStringList landc = lcl.split("_");
  146. writer.writeTextElement("language", landc.value(0));
  147. writer.writeTextElement("country", landc.value(1));
  148. writer.writeTextElement("locale", locale.name());
  149. QTimeZone tz = QTimeZone::systemTimeZone();
  150. writer.writeTextElement("time-zone", tz.displayName(QTimeZone::StandardTime, QTimeZone::LongName));
  151. writer.writeTextElement("time-zone-offset", QString::number(tz.offsetFromUtc(QDateTime::currentDateTime()) / 60));
  152. writer.writeEndElement(); // device-info
  153. writer.writeEndDocument();
  154. response->setStatusCode(qhttp::ESTATUS_OK);
  155. response->write(data);
  156. response->end();
  157. }
  158. /////////////////////////////////////////////////////////////////////////////////////////
  159. void InputRoku::handleKeyPress(QHttpRequest* request, QHttpResponse* response)
  160. {
  161. QString path = request->url().path();
  162. QStringList pathsplit = path.split("/");
  163. if (pathsplit.count() != 3)
  164. {
  165. response->setStatusCode(qhttp::ESTATUS_BAD_REQUEST);
  166. response->end();
  167. return;
  168. }
  169. auto url = request->url().toString();
  170. if (url.startsWith("/keydown/") || url.startsWith("/keypress/"))
  171. emit receivedInput("roku", pathsplit.value(2), true);
  172. if (url.startsWith("/keyup/") || url.startsWith("/keypress/"))
  173. emit receivedInput("roku", pathsplit.value(2), false);
  174. response->setStatusCode(qhttp::ESTATUS_OK);
  175. response->end();
  176. return;
  177. }
  178. /////////////////////////////////////////////////////////////////////////////////////////
  179. void InputRoku::handleRootInfo(QHttpRequest* request, QHttpResponse* response)
  180. {
  181. QByteArray data;
  182. QXmlStreamWriter writer(&data);
  183. writer.setAutoFormatting(true);
  184. writer.writeStartDocument();
  185. writer.writeStartElement("urn:schemas-upnp-org:device-1-0", "root");
  186. writer.writeStartElement("specVersion");
  187. writer.writeTextElement("major", "1");
  188. writer.writeTextElement("minor", "0");
  189. writer.writeEndElement(); // specVersion
  190. writer.writeStartElement("device");
  191. writer.writeTextElement("deviceType", "urn:roku-com:device:player:1-0");
  192. writer.writeTextElement("friendlyName", Utils::ComputerName());
  193. writer.writeTextElement("manufacturer", "Roku");
  194. writer.writeTextElement("manufacturerURL", "http://www.roku.com");
  195. writer.writeTextElement("modelDescription", "Roku Streaming Player Network Media");
  196. writer.writeTextElement("modelName", "Roku 3");
  197. writer.writeTextElement("modelNumber", "4200X");
  198. writer.writeTextElement("modelURL", "http://www.roku.com");
  199. writer.writeTextElement("serialNumber", ROKU_SERIAL_NUMBER);
  200. writer.writeTextElement("UDN", "uuid:" + Utils::ClientUUID());
  201. writer.writeStartElement("serviceList");
  202. writer.writeStartElement("service");
  203. writer.writeTextElement("serviceType", "urn:roku-com:service:ecp:1");
  204. writer.writeTextElement("serviceId", "urn:roku-com:serviceId:ecp1-0");
  205. writer.writeTextElement("controlURL", "");
  206. writer.writeTextElement("eventSubURL", "");
  207. writer.writeTextElement("SCPDURL", "ecp_SCPD.xml");
  208. writer.writeEndElement(); // service
  209. writer.writeEndElement(); // serviceList
  210. writer.writeEndElement(); // root
  211. writer.writeEndDocument();
  212. response->setStatusCode(qhttp::ESTATUS_OK);
  213. response->write(data);
  214. response->end();
  215. }