Skip to content

Commit

Permalink
UPnP: Extend the MythFEXML interface to send actions directly to the
Browse files Browse the repository at this point in the history
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
Mark Kendall committed Feb 4, 2011
1 parent 6f47c03 commit 107fcd2
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 35 deletions.
203 changes: 172 additions & 31 deletions mythtv/programs/mythfrontend/mythfexml.cpp
Expand Up @@ -19,9 +19,12 @@
#include <QFile>
#include <QRegExp>
#include <QBuffer>
#include <QKeyEvent>

#include "../../config.h"

#include "keybindings.h"

/////////////////////////////////////////////////////////////////////////////
//
/////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -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 );
}
Expand All @@ -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;
}

// ==========================================================================
Expand All @@ -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;

Expand Down Expand Up @@ -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" ];
Expand All @@ -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 changes: 14 additions & 4 deletions mythtv/programs/mythfrontend/mythfexml.h
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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);
Expand Down

0 comments on commit 107fcd2

Please sign in to comment.