Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

UPnP: Extend the MythFEXML interface to send actions directly to the

frontend.

In summary:-

Send a message to the frontend (N.B. syntax change).
- http://mythfrontend-ip:6547/MythFE/SendMessage?text='Hello world'

Send an action (e.g. UP) to the frontend.
- http://mythfrontend-ip:6547/MythFE/SendAction?action=UP

A list of valid actions is cached internally and used to validate the
submitted action. To retrieve a simple XML formatted list of those
actions, their descriptions and the contexts in which they are valid.
- http://mythfrontend-ip:6547/MythFE/GetActionList

To display an html test page of all valid actions.
- http://mythfrontend-ip:6547/MythFE/GetActionTest

GetServeDesc returns the service description for the first time (the
description needs updating).

N.B. There is every possibility that this interface will be tweaked
before 0.25 is released.

There are some issues around languages/translations with the action
descriptions and I make no apologies for the quality of my html/xml
(patches willingly accepted)
  • Loading branch information...
commit 107fcd22bcfec4d629a8c321aef8b888ac2e78a2 1 parent 6f47c03
Mark Kendall authored
203 mythtv/programs/mythfrontend/mythfexml.cpp
View
@@ -19,9 +19,12 @@
#include <QFile>
#include <QRegExp>
#include <QBuffer>
+#include <QKeyEvent>
#include "../../config.h"
+#include "keybindings.h"
+
/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
@@ -52,10 +55,14 @@ MythFEXML::~MythFEXML()
//
/////////////////////////////////////////////////////////////////////////////
-MythFEXMLMethod MythFEXML::GetMethod( const QString &sURI )
+MythFEXMLMethod MythFEXML::GetMethod(const QString &sURI)
{
+ if (sURI == "GetServDesc") return MFEXML_GetServiceDescription;
if (sURI == "GetScreenShot") return MFEXML_GetScreenShot;
- if (sURI == "Message") return MFEXML_Message;
+ if (sURI == "SendMessage") return MFEXML_Message;
+ if (sURI == "SendAction") return MFEXML_Action;
+ if (sURI == "GetActionList") return MFEXML_ActionList;
+ if (sURI == "GetActionTest") return MFEXML_ActionListTest;
return( MFEXML_Unknown );
}
@@ -66,38 +73,39 @@ MythFEXMLMethod MythFEXML::GetMethod( const QString &sURI )
bool MythFEXML::ProcessRequest( HttpWorkerThread *pThread, HTTPRequest *pRequest )
{
- try
- {
- if (pRequest)
- {
- if (pRequest->m_sBaseUrl != m_sControlUrl)
- return( false );
+ if (!pRequest)
+ return false;
- VERBOSE(VB_UPNP, QString("MythFEXML::ProcessRequest: %1 : %2")
- .arg(pRequest->m_sMethod)
- .arg(pRequest->m_sRawRequest));
+ if (pRequest->m_sBaseUrl != m_sControlUrl)
+ return false;
- switch( GetMethod( pRequest->m_sMethod ))
- {
- case MFEXML_GetScreenShot : GetScreenShot ( pRequest ); return true;
- case MFEXML_Message : SendMessage ( pRequest ); return true;
+ VERBOSE(VB_UPNP, QString("MythFEXML::ProcessRequest: %1 : %2")
+ .arg(pRequest->m_sMethod).arg(pRequest->m_sRawRequest));
-
- default:
- {
- UPnp::FormatErrorResponse( pRequest, UPnPResult_InvalidAction );
-
- return true;
- }
- }
- }
- }
- catch( ... )
+ switch(GetMethod(pRequest->m_sMethod))
{
- VERBOSE( VB_IMPORTANT, "MythFEXML::ProcessRequest() - Unexpected Exception" );
+ case MFEXML_GetServiceDescription:
+ pRequest->FormatFileResponse(m_sServiceDescFileName);
+ break;
+ case MFEXML_GetScreenShot:
+ GetScreenShot(pRequest);
+ break;
+ case MFEXML_Message:
+ SendMessage(pRequest);
+ break;
+ case MFEXML_Action:
+ SendAction(pRequest);
+ break;
+ case MFEXML_ActionList:
+ GetActionList(pRequest);
+ break;
+ case MFEXML_ActionListTest:
+ GetActionListTest(pRequest);
+ break;
+ default:
+ UPnp::FormatErrorResponse(pRequest, UPnPResult_InvalidAction);
}
-
- return( false );
+ return true;
}
// ==========================================================================
@@ -108,7 +116,7 @@ bool MythFEXML::ProcessRequest( HttpWorkerThread *pThread, HTTPRequest *pRequest
//
/////////////////////////////////////////////////////////////////////////////
-void MythFEXML::GetScreenShot( HTTPRequest *pRequest )
+void MythFEXML::GetScreenShot(HTTPRequest *pRequest)
{
pRequest->m_eResponseType = ResponseTypeFile;
@@ -141,7 +149,7 @@ void MythFEXML::GetScreenShot( HTTPRequest *pRequest )
pRequest->m_sFileName = sFileName;
}
-void MythFEXML::SendMessage( HTTPRequest *pRequest )
+void MythFEXML::SendMessage(HTTPRequest *pRequest)
{
pRequest->m_eResponseType = ResponseTypeNone;
QString sText = pRequest->m_mapParams[ "text" ];
@@ -151,3 +159,136 @@ void MythFEXML::SendMessage( HTTPRequest *pRequest )
MythEvent* me = new MythEvent(MythEvent::MythUserMessage, sText);
qApp->postEvent(window, me);
}
+
+void MythFEXML::SendAction(HTTPRequest *pRequest)
+{
+ pRequest->m_eResponseType = ResponseTypeNone;
+ QString sText = pRequest->m_mapParams["action"];
+ VERBOSE(VB_UPNP, QString("UPNP Action: ") + sText);
+
+ InitActions();
+ if (!m_actionList.contains(sText))
+ {
+ VERBOSE(VB_GENERAL, QString("UPNP Action: %1 is invalid").arg(sText));
+ return;
+ }
+
+ MythMainWindow *window = GetMythMainWindow();
+ QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0, Qt::NoModifier, sText);
+ qApp->postEvent(window, (QEvent*)ke);
+}
+
+void MythFEXML::GetActionListTest(HTTPRequest *pRequest)
+{
+ InitActions();
+
+ pRequest->m_eResponseType = ResponseTypeHTML;
+ pRequest->m_mapRespHeaders[ "Cache-Control" ] = "no-cache=\"Ext\", max-age = 5000";
+
+ pRequest->m_response <<
+ "<html>\n"
+ " <script type =\"text/javascript\">\n"
+ " function postaction(action) {\n"
+ " var myForm = document.createElement(\"form\");\n"
+ " myForm.method =\"Post\";\n"
+ " myForm.action =\"SendAction?\";\n"
+ " myForm.target =\"post_target\";\n"
+ " var myInput = document.createElement(\"input\");\n"
+ " myInput.setAttribute(\"name\", \"action\");\n"
+ " myInput.setAttribute(\"value\", action);\n"
+ " myForm.appendChild(myInput);\n"
+ " document.body.appendChild(myForm);\n"
+ " myForm.submit();\n"
+ " document.body.removeChild(myForm);\n"
+ " }\n"
+ " </script>\n"
+ " <body>\n"
+ " <iframe id=\"hidden_target\" name=\"post_target\" src=\"\""
+ " style=\"width:0;height:0;border:0px solid #fff;\"></iframe>\n";
+
+ QHashIterator<QString,QStringList> contexts(m_actionDescriptions);
+ while (contexts.hasNext())
+ {
+ contexts.next();
+ QStringList actions = contexts.value();
+ foreach (QString action, actions)
+ {
+ QStringList split = action.split(",");
+ if (split.size() == 2)
+ {
+ pRequest->m_response <<
+ QString(" <div>%1&nbsp;<input type=\"button\" value=\"%2\" onClick=\"postaction('%2');\"></input>&nbsp;%3</div>\n")
+ .arg(contexts.key()).arg(split[0]).arg(split[1]);
+ }
+ }
+ }
+
+ pRequest->m_response <<
+ " </body>\n"
+ "</html>\n";
+
+}
+
+void MythFEXML::GetActionList(HTTPRequest *pRequest)
+{
+ InitActions();
+
+ pRequest->m_eResponseType = ResponseTypeXML;
+ pRequest->m_mapRespHeaders[ "Cache-Control" ] = "no-cache=\"Ext\", max-age = 5000";
+
+ pRequest->m_response << "<mythactions version=\"1\">";
+
+ QHashIterator<QString,QStringList> contexts(m_actionDescriptions);
+ while (contexts.hasNext())
+ {
+ contexts.next();
+ pRequest->m_response << QString("<context name=\"%1\">")
+ .arg(contexts.key());
+ QStringList actions = contexts.value();
+ foreach (QString action, actions)
+ {
+ QStringList split = action.split(",");
+ if (split.size() == 2)
+ {
+ pRequest->m_response <<
+ QString("<action name=\"%1\">%2</action>")
+ .arg(split[0]).arg(split[1]);
+ }
+ }
+ pRequest->m_response << "</context>";
+ }
+ pRequest->m_response << "</mythactions>";
+}
+
+void MythFEXML::InitActions(void)
+{
+ static bool initialised = false;
+ if (initialised)
+ return;
+
+ initialised = true;
+ KeyBindings *bindings = new KeyBindings(gCoreContext->GetHostName());
+ if (bindings)
+ {
+ QStringList contexts = bindings->GetContexts();
+ contexts.sort();
+ foreach (QString context, contexts)
+ {
+ m_actionDescriptions[context] = QStringList();
+ QStringList ctx_actions = bindings->GetActions(context);
+ ctx_actions.sort();
+ m_actionList += ctx_actions;
+ foreach (QString actions, ctx_actions)
+ {
+ QString desc = actions + "," +
+ bindings->GetActionDescription(context, actions);
+ m_actionDescriptions[context].append(desc);
+ }
+ }
+ }
+ m_actionList.removeDuplicates();
+ m_actionList.sort();
+
+ foreach (QString actions, m_actionList)
+ VERBOSE(VB_UPNP, QString("MythFEXML Action: %1").arg(actions));
+}
18 mythtv/programs/mythfrontend/mythfexml.h
View
@@ -16,10 +16,13 @@
typedef enum
{
- MFEXML_Unknown = 0,
- MFEXML_GetScreenShot = 1,
- MFEXML_Message = 2,
-
+ MFEXML_Unknown = 0,
+ MFEXML_GetServiceDescription,
+ MFEXML_GetScreenShot,
+ MFEXML_Message,
+ MFEXML_Action,
+ MFEXML_ActionList,
+ MFEXML_ActionListTest,
} MythFEXMLMethod;
class MythFEXML : public Eventing
@@ -29,6 +32,9 @@ class MythFEXML : public Eventing
QString m_sControlUrl;
QString m_sServiceDescFileName;
+ QStringList m_actionList;
+ QHash<QString,QStringList> m_actionDescriptions;
+
protected:
// Implement UPnpServiceImpl methods that we can
@@ -44,6 +50,10 @@ class MythFEXML : public Eventing
void GetScreenShot ( HTTPRequest *pRequest );
void SendMessage ( HTTPRequest *pRequest );
+ void SendAction ( HTTPRequest *pRequest );
+ void GetActionList ( HTTPRequest *pRequest );
+ void GetActionListTest( HTTPRequest *pRequest );
+ void InitActions ( void );
public:
MythFEXML( UPnpDevice *pDevice , const QString sSharePath);
Please sign in to comment.
Something went wrong with that request. Please try again.