InputRoku.cpp 8.4 KB

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