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...
1 parent 6f47c03 commit 107fcd22bcfec4d629a8c321aef8b888ac2e78a2 Mark Kendall committed Feb 4, 2011
Showing with 186 additions and 35 deletions.
  1. +172 −31 mythtv/programs/mythfrontend/mythfexml.cpp
  2. +14 −4 mythtv/programs/mythfrontend/mythfexml.h
View
203 mythtv/programs/mythfrontend/mythfexml.cpp
@@ -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));
+}
View
18 mythtv/programs/mythfrontend/mythfexml.h
@@ -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);

0 comments on commit 107fcd2

Please sign in to comment.