← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~j-corwin/openlp/remote into lp:openlp

 

Jonathan Corwin has proposed merging lp:~j-corwin/openlp/remote into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)


Control OpenLP via the web 

No security at present, well other than switching the plugin off or securing the wifi...
-- 
https://code.launchpad.net/~j-corwin/openlp/remote/+merge/24548
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/core/lib/eventreceiver.py'
--- openlp/core/lib/eventreceiver.py	2010-04-22 21:22:09 +0000
+++ openlp/core/lib/eventreceiver.py	2010-05-01 12:15:26 +0000
@@ -65,11 +65,29 @@
     ``slidecontroller_{live|preview}_last``
         Moves to the last slide
 
+    ``slidecontroller_{live|preview}_set``
+        Moves to a specific slide, by index
+
     ``slidecontroller_{live|preview}_started``
         Broadcasts that an item has been made live/previewed
 
     ``slidecontroller_{live|preview}_change``
-        Informs the slidecontroller that a slide change has occurred
+        Informs the slidecontroller that a slide change has occurred and to 
+        update itself
+
+    ``slidecontroller_{live|preview}_changed``
+        Broadcasts that the slidecontroller has changed the current slide
+
+    ``slidecontroller_{live|preview}_text_request``
+        Request the text for the current item in the controller
+        Returns a slidecontroller_{live|preview}_text_response with an 
+        array of dictionaries with the tag and verse text
+
+    ``slidecontroller_{live|preview}_blank``
+        Request that the output screen is blanked
+
+    ``slidecontroller_{live|preview}_unblank``
+        Request that the output screen is unblanked
 
     ``slidecontroller_live_spin_delay``
         Pushes out the loop delay
@@ -77,9 +95,19 @@
     ``slidecontroller_live_stop_loop``
         Stop the loop on the main display
 
-    ``servicecontroller_next_item``
+    ``servicemanager_previous_item``
+        Display the previous item in the service
+
+    ``servicemanager_next_item``
         Display the next item in the service
 
+    ``servicemanager_set_item``
+        Go live on a specific item, by index
+        
+    ``servicemanager_list_request``
+        Request the service list. Responds with servicemanager_list_response
+        containing a array of dictionaries
+
     ``maindisplay_blank``
         Blank the maindisplay window 
 
@@ -110,6 +138,9 @@
     ``videodisplay_stop``
         Stop playing a media item
 
+    ``videodisplay_background``
+        Replace the background video
+
     ``theme_update_list``
         send out message with new themes
 
@@ -174,6 +205,10 @@
     ``bibles_stop_import``
         Stops the Bible Import
 
+    ``remotes_poll_request``
+        Waits for openlp to do something "interesting" and sends a
+        remotes_poll_response signal when it does
+
     """
     def __init__(self):
         """

=== modified file 'openlp/core/ui/servicemanager.py'
--- openlp/core/ui/servicemanager.py	2010-04-30 22:38:15 +0000
+++ openlp/core/ui/servicemanager.py	2010-05-01 12:15:26 +0000
@@ -188,6 +188,12 @@
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'servicemanager_next_item'), self.nextItem)
         QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'servicemanager_previous_item'), self.previousItem)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'servicemanager_set_item'), self.onSetItem)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'servicemanager_list_request'), self.listRequest)
+        QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'config_updated'), self.regenerateServiceItems)
         # Last little bits of setting up
         self.service_theme = unicode(QtCore.QSettings().value(
@@ -289,6 +295,41 @@
                 lookFor = 1
             serviceIterator += 1
 
+    def previousItem(self):
+        """
+        Called by the SlideController to select the
+        previous service item
+        """
+        if len(self.ServiceManagerList.selectedItems()) == 0:
+            return
+        selected = self.ServiceManagerList.selectedItems()[0]
+        prevItem = None
+        serviceIterator = QtGui.QTreeWidgetItemIterator(self.ServiceManagerList)
+        while serviceIterator.value():
+            if serviceIterator.value() == selected:
+                if prevItem:
+                    self.ServiceManagerList.setCurrentItem(prevItem)
+                    self.makeLive()
+                return
+            if serviceIterator.value().parent() is None:
+                prevItem = serviceIterator.value()
+            serviceIterator += 1
+
+    def onSetItem(self, message):
+        """
+        Called by a signal to select a specific item
+        """
+        self.setItem(int(message[0]))
+        
+    def setItem(self, index):
+        """
+        Makes a specific item in the service live
+        """
+        if index >= 0 and index < self.ServiceManagerList.topLevelItemCount:
+            item = self.ServiceManagerList.topLevelItem(index)
+            self.ServiceManagerList.setCurrentItem(item)
+            self.makeLive()
+
     def onMoveSelectionUp(self):
         """
         Moves the selection up the window
@@ -855,3 +896,20 @@
             return item.data(0, QtCore.Qt.UserRole).toInt()[0]
         else:
             return parentitem.data(0, QtCore.Qt.UserRole).toInt()[0]
+            
+    def listRequest(self, message=None):
+        data = []
+        curindex, count = self.findServiceItem()
+        if curindex >= 0 and curindex < len(self.serviceItems):
+            curitem = self.serviceItems[curindex]
+        else:
+            curitem = None
+        for item in self.serviceItems:
+            service_item = item[u'service_item']
+            data_item = {}
+            data_item[u'title'] = unicode(service_item.title)
+            data_item[u'plugin'] = unicode(service_item.name)
+            data_item[u'notes'] = unicode(service_item.notes)
+            data_item[u'selected'] = (item == curitem)
+            data.append(data_item)
+        Receiver.send_message(u'servicemanager_list_response', data)

=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py	2010-04-30 22:38:15 +0000
+++ openlp/core/ui/slidecontroller.py	2010-05-01 12:15:26 +0000
@@ -338,6 +338,18 @@
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'slidecontroller_%s_change' % self.type_prefix),
             self.onSlideChange)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'slidecontroller_%s_set' % self.type_prefix), 
+            self.onSlideSelectedIndex)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'slidecontroller_%s_blank' % self.type_prefix), 
+            self.onSlideBlank)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'slidecontroller_%s_unblank' % self.type_prefix), 
+            self.onSlideUnblank)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'slidecontroller_%s_text_request' % self.type_prefix), 
+            self.onTextRequest)
         QtCore.QObject.connect(self.Splitter,
             QtCore.SIGNAL(u'splitterMoved(int, int)'), self.trackSplitter)
         QtCore.QObject.connect(Receiver.get_receiver(),
@@ -556,12 +568,30 @@
         self.enableToolBar(serviceItem)
         self.onSlideSelected()
         self.PreviewListWidget.setFocus()
-        Receiver.send_message(u'%s_%s_started' %
-            (self.serviceItem.name.lower(),
-            'live' if self.isLive else 'preview'),
+        Receiver.send_message(u'slidecontroller_%s_started' % self.type_prefix,
             [serviceItem])
         log.log(15, u'Display Rendering took %4s' % (time.time() - before))
 
+    def onTextRequest(self):
+        """
+        Return the text for the current item in controller
+        """
+        data = []
+        if self.serviceItem:
+            for framenumber, frame in enumerate(self.serviceItem.get_frames()):
+                data_item = {}
+                if self.serviceItem.is_text():
+                    data_item[u'tag'] = unicode(frame[u'verseTag'])
+                    data_item[u'text'] = unicode(frame[u'text'])
+                else:
+                    data_item[u'tag'] = unicode(framenumber)
+                    data_item[u'text'] = u''
+                data_item[u'selected'] = \
+                    (self.PreviewListWidget.currentRow() == framenumber)
+                data.append(data_item)
+        Receiver.send_message(u'slidecontroller_%s_text_response' 
+            % self.type_prefix, data)            
+
     #Screen event methods
     def onSlideSelectedFirst(self):
         """
@@ -577,6 +607,33 @@
             self.PreviewListWidget.selectRow(0)
             self.onSlideSelected()
 
+    def onSlideSelectedIndex(self, message):
+        """
+        Go to the requested slide
+        """
+        index = int(message[0])
+        if not self.serviceItem:
+            return
+        Receiver.send_message(u'%s_slide' % self.serviceItem.name.lower(), 
+            [self.serviceItem, self.isLive, index])
+        if self.serviceItem.is_command():
+            self.updatePreview()
+        else:
+            self.PreviewListWidget.selectRow(index)
+            self.onSlideSelected()
+
+    def onSlideBlank(self):
+        """
+        Handle the slidecontroller blank event
+        """
+        self.onBlankDisplay(True)
+
+    def onSlideUnblank(self):
+        """
+        Handle the slidecontroller unblank event
+        """
+        self.onBlankDisplay(False)
+
     def onBlankDisplay(self, checked):
         """
         Handle the blank screen button
@@ -665,6 +722,8 @@
                 if self.isLive:
                     self.mainDisplay.frameView(frame, True)
             self.selectedRow = row
+        Receiver.send_message(u'slidecontroller_%s_changed' % self.type_prefix,
+            row)
 
     def onSlideChange(self, row):
         """
@@ -672,6 +731,8 @@
         """
         self.PreviewListWidget.selectRow(row)
         self.updatePreview()
+        Receiver.send_message(u'slidecontroller_%s_changed' % self.type_prefix,
+            row)
 
     def updatePreview(self):
         rm = self.parent.RenderManager

=== modified file 'openlp/plugins/alerts/lib/alertsmanager.py'
--- openlp/plugins/alerts/lib/alertsmanager.py	2010-04-21 21:56:48 +0000
+++ openlp/plugins/alerts/lib/alertsmanager.py	2010-05-01 12:15:26 +0000
@@ -46,7 +46,7 @@
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'maindisplay_active'), self.generateAlert)
         QtCore.QObject.connect(Receiver.get_receiver(),
-            QtCore.SIGNAL(u'alerts_text'), self.displayAlert)
+            QtCore.SIGNAL(u'alerts_text'), self.onAlertText)
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'config_screen_changed'), self.screenChanged)
 
@@ -70,6 +70,16 @@
         self.parent.maindisplay.setAlertSize(self.alertScreenPosition,\
             self.alertHeight)
 
+    def onAlertText(self, message):
+        """
+        Called via a alerts_text event. Message is single element array
+        containing text
+        """
+        if message:
+            self.displayAlert(message[0])
+        else:
+            self.displayAlert(u'')
+            
     def displayAlert(self, text=u''):
         """
         Called from the Alert Tab to display an alert

=== added directory 'openlp/plugins/remotes/html'
=== added file 'openlp/plugins/remotes/html/index.html'
--- openlp/plugins/remotes/html/index.html	1970-01-01 00:00:00 +0000
+++ openlp/plugins/remotes/html/index.html	2010-05-01 12:15:26 +0000
@@ -0,0 +1,117 @@
+<html>
+<head>
+<title>OpenLP Controller</title>
+<script type='text/javascript'>
+
+function send_event(eventname, data){
+    var req = new XMLHttpRequest();
+    req.onreadystatechange = function() {
+        if(req.readyState==4)
+            response(eventname, req);
+    }
+    var url = '';
+    if(eventname.substr(-8) == '_request')
+        url = 'request';
+    else 
+        url = 'send';
+    url += '/' + eventname;
+    if(data!=null)
+        url += '?q=' + escape(data);
+    req.open('GET', url, true);
+    req.send();
+}
+function failed_response(eventname, req){
+    switch(eventname){
+        case 'remotes_poll_request':
+            if(req.status==408)
+                send_event("remotes_poll_request");
+            break;
+    }
+}
+function response(eventname, req){
+    if(req.status!=200){
+        failed_response(eventname, req);
+        return;
+    }
+    text = req.responseText;
+    switch(eventname){
+        case 'servicemanager_list_request':
+            var data = eval('(' + text + ')');
+            var html = '<table>';
+            for(row in data){
+                html += '<tr onclick="send_event('
+                html += "'servicemanager_set_item', " + row + ')"';
+                if(data[row]['selected'])
+                    html += ' style="font-weight: bold"';
+                html += '>'
+                html += '<td>' + (parseInt(row)+1) + '</td>'
+                html += '<td>' + data[row]['title'] + '</td>'
+                html += '<td>' + data[row]['plugin'] + '</td>'
+                html += '<td>' + data[row]['notes'] + '</td>'
+                html += '</tr>';
+            }
+            html += '</table>';
+            document.getElementById('service').innerHTML = html;        
+            break;
+        case 'slidecontroller_live_text_request':
+            var data = eval('(' + text + ')');
+            var html = '<table>';
+            for(row in data){
+                html += '<tr onclick="send_event('
+                html += "'slidecontroller_live_set', " + row + ')"';
+                if(data[row]['selected'])
+                    html += ' style="font-weight: bold"';
+                html += '>';
+                html += '<td>' + data[row]['tag'] + '</td>';
+                html += '<td>' + data[row]['text'].replace(/\\n/g, '<br>');
+                html += '</td></tr>';
+            }
+            html += '</table>';
+            document.getElementById('currentitem').innerHTML = html;        
+            break;
+        case 'remotes_poll_request':
+            send_event("remotes_poll_request");
+            send_event("servicemanager_list_request");
+            send_event("slidecontroller_live_text_request");
+            break;
+    }
+}
+send_event("servicemanager_list_request");
+send_event("slidecontroller_live_text_request");
+send_event("remotes_poll_request");
+</script>
+</head>
+<body>
+    <h1>OpenLP Controller</h1>
+    <input type='button' value='<- Previous Slide' 
+        onclick='send_event("slidecontroller_live_previous");' />
+    <input type='button' value='Next Slide ->' 
+        onclick='send_event("slidecontroller_live_next");' />
+    <br/>
+    <input type='button' value='<- Previous Item' 
+        onclick='send_event("servicemanager_previous_item");' />
+    <input type='button' value='Next Item ->' 
+        onclick='send_event("servicemanager_next_item");' />
+    <br/>
+    <input type='button' value='Blank' 
+        onclick='send_event("slidecontroller_live_blank");' />
+    <input type='button' value='Unblank' 
+        onclick='send_event("slidecontroller_live_unblank");' />
+    <br/>
+    <label>Alert text</label><input id='alert' type='text' />
+    <input type='button' value='Send' 
+        onclick='send_event("alerts_text", 
+        document.getElementById("alert").value);' />
+    <hr>
+    <input type='button' value='Order of service' 
+        onclick='send_event("servicemanager_list_request");'>
+    <div id='service'></div>
+    <hr>
+    <input type='button' value='Current item' 
+        onclick='send_event("slidecontroller_live_text_request");'>
+    <div id='currentitem'></div>
+    <hr>
+    <a href="http://www.openlp.org/";>OpenLP website</a>
+</body>
+</html>
+

=== modified file 'openlp/plugins/remotes/lib/__init__.py'
--- openlp/plugins/remotes/lib/__init__.py	2010-03-21 23:58:01 +0000
+++ openlp/plugins/remotes/lib/__init__.py	2010-05-01 12:15:26 +0000
@@ -24,3 +24,4 @@
 ###############################################################################
 
 from remotetab import RemoteTab
+from httpserver import HttpServer

=== added file 'openlp/plugins/remotes/lib/httpserver.py'
--- openlp/plugins/remotes/lib/httpserver.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/remotes/lib/httpserver.py	2010-05-01 12:15:26 +0000
@@ -0,0 +1,319 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2010 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2010 Tim Bentley, Jonathan Corwin, Michael      #
+# Gorven, Scott Guerrieri, Christian Richter, Maikel Stuivenberg, Martin      #
+# Thompson, Jon Tibble, Carsten Tinggaard                                     #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+
+import logging
+import os
+import json
+import urlparse
+
+from PyQt4 import QtCore, QtNetwork
+
+from openlp.core.lib import Receiver
+from openlp.core.utils import AppLocation
+
+log = logging.getLogger(__name__)
+
+class HttpServer(object):
+    """ 
+    Ability to control OpenLP via a webbrowser
+    e.g.  http://localhost:4316/send/slidecontroller_live_next
+          http://localhost:4316/send/alerts_text?q=your%20alert%20text
+    """
+    def __init__(self, parent):
+        """
+        Initialise the httpserver, and start the server
+        """
+        log.debug(u'Initialise httpserver')
+        self.parent = parent
+        self.html_dir = os.path.join(
+            AppLocation.get_directory(AppLocation.PluginsDir),
+            u'remotes', u'html')
+        self.connections = []
+        self.current_item = None
+        self.current_slide = None
+        self.start_tcp()
+
+    def start_tcp(self):
+        """
+        Start the http server, use the port in the settings default to 4316
+        Listen out for slide and song changes so they can be broadcast to 
+        clients. Listen out for socket connections
+        """
+        log.debug(u'Start TCP server')
+        port = QtCore.QSettings().value(
+            self.parent.settingsSection + u'/remote port',
+            QtCore.QVariant(4316)).toInt()[0]
+        self.server = QtNetwork.QTcpServer()
+        self.server.listen(QtNetwork.QHostAddress(QtNetwork.QHostAddress.Any), 
+            port)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'slidecontroller_live_changed'), 
+            self.slide_change)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'slidecontroller_live_started'), 
+            self.item_change)
+        QtCore.QObject.connect(self.server,
+            QtCore.SIGNAL(u'newConnection()'), self.new_connection)
+        log.debug(u'TCP listening on port %d' % port)
+
+    def slide_change(self, row):
+        """
+        Slide change listener. Store the item and tell the clients
+        """
+        self.current_slide = row
+        self.send_poll()
+
+    def item_change(self, items):
+        """
+        Item (song) change listener. Store the slide and tell the clients
+        """
+        self.current_item = items[0].title
+        self.send_poll()
+                        
+    def send_poll(self):
+        """
+        Tell the clients something has changed
+        """
+        Receiver.send_message(u'remotes_poll_response',
+            {'slide': self.current_slide,
+             'item': self.current_item})
+        
+    def new_connection(self):
+        """
+        A new http connection has been made. Create a client object to handle
+        communication
+        """
+        log.debug(u'new http connection')
+        socket = self.server.nextPendingConnection()
+        if socket:
+            self.connections.append(HttpConnection(self, socket))
+    
+    def close_connection(self, connection):
+        """
+        The connection has been closed. Clean up
+        """
+        log.debug(u'close http connection')
+        self.connections.remove(connection)
+
+    def close(self):
+        """
+        Close down the http server
+        """
+        log.debug(u'close http server')
+        self.server.close()
+        
+class HttpConnection(object):
+    """ 
+    A single connection, this handles communication between the server
+    and the client
+    """
+    def __init__(self, parent, socket):
+        """
+        Initialise the http connection. Listen out for socket signals
+        """
+        log.debug(u'Initialise HttpConnection: %s' % 
+            socket.peerAddress().toString())
+        self.socket = socket
+        self.parent = parent
+        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
+            self.ready_read)
+        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'),
+            self.disconnected)
+
+    def ready_read(self):
+        """
+        Data has been sent from the client. Respond to it
+        """
+        log.debug(u'ready to read socket')
+        if self.socket.canReadLine():
+            data = unicode(self.socket.readLine())
+            log.debug(u'received: ' + data)
+            words = data.split(u' ')
+            html = None
+            if words[0] == u'GET':
+                url = urlparse.urlparse(words[1])
+                params = self.load_params(url.query)
+                folders = url.path.split(u'/')
+                if folders[1] == u'':
+                    html = self.serve_file(u'')
+                elif folders[1] == u'files':
+                    html = self.serve_file(folders[2])
+                elif folders[1] == u'send':
+                    html = self.process_event(folders[2], params)
+                elif folders[1] == u'request':
+                    if self.process_request(folders[2], params):
+                        return
+            if html:
+                html = self.get_200_ok() + html + u'\n'
+            else:
+                html = self.get_404_not_found()
+            self.socket.write(html)
+            self.close()
+
+    def serve_file(self, filename):
+        """
+        Send a file to the socket. For now, just .html files
+        and must be top level inside the html folder. 
+        If subfolders requested return 404, easier for security for the present.
+
+        Ultimately for i18n, this could first look for xx/file.html before
+        falling back to file.html... where xx is the language, e.g. 'en'
+        """
+        log.debug(u'serve file request %s' % filename)
+        if not filename:
+            filename = u'index.html'
+        if os.path.basename(filename) != filename:
+            return None
+        (fileroot, ext) = os.path.splitext(filename)
+        if ext != u'.html':
+            return None
+        path = os.path.join(self.parent.html_dir, filename)
+        try:
+            f = open(path, u'rb')
+        except:
+            log.exception(u'Failed to open %s' % path)
+            return None
+        log.debug(u'Opened %s' % path)
+        html = f.read()
+        f.close()
+        return html
+                               
+    def load_params(self, query):
+        """
+        Decode the query string parameters sent from the browser
+        """
+        params = urlparse.parse_qs(query)
+        if not params:
+            return None
+        else:
+            return params['q']        
+        
+    def process_event(self, event, params):
+        """
+        Send a signal to openlp to perform an action.
+        Currently lets anything through. Later we should restrict and perform
+        basic parameter checking, otherwise rogue clients could crash openlp
+        """
+        if params:
+            Receiver.send_message(event, params)    
+        else:                  
+            Receiver.send_message(event)    
+        return u'OK'
+
+    def process_request(self, event, params):
+        """
+        Client has requested data. Send the signal and parameters for openlp
+        to handle, then listen out for a corresponding _request signal
+        which will have the data to return.
+        For most event timeout after 10 seconds (i.e. incase the signal 
+        recipient isn't listening) 
+        remotes_poll_request is a special case, this is a ajax long poll which
+        is just waiting for slide change/song change activity. This can wait
+        longer (one minute)
+        """
+        if not event.endswith(u'_request'):
+            return False
+        self.event = event
+        response = event.replace(u'_request', u'_response')
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(response), self.process_response)
+        self.timer = QtCore.QTimer()
+        self.timer.setSingleShot(True)
+        QtCore.QObject.connect(self.timer,
+            QtCore.SIGNAL(u'timeout()'), self.timeout)
+        if event == 'remotes_poll_request':
+            self.timer.start(60000)
+        else:
+            self.timer.start(10000)
+        if params:
+            Receiver.send_message(event, params)    
+        else:                  
+            Receiver.send_message(event)    
+        return True
+
+    def process_response(self, data):
+        """
+        The recipient of a _request signal has sent data. Convert this to 
+        json and return it to client
+        """
+        if not self.socket:
+            return
+        self.timer.stop()
+        html = json.dumps(data)
+        html = self.get_200_ok() + html + u'\n'
+        self.socket.write(html)
+        self.close()
+
+    def get_200_ok(self):
+        """
+        Successful request. Send OK headers. Assume html for now. 
+        """
+        return u'HTTP/1.1 200 OK\r\n' + \
+            u'Content-Type: text/html; charset="utf-8"\r\n' + \
+            u'\r\n'
+
+    def get_404_not_found(self):
+        """
+        Invalid url. Say so
+        """
+        return u'HTTP/1.1 404 Not Found\r\n'+ \
+            u'Content-Type: text/html; charset="utf-8"\r\n' + \
+            u'\r\n'
+
+    def get_408_timeout(self):
+        """
+        A _request hasn't returned anything in the timeout period. 
+        Return timeout
+        """
+        return u'HTTP/1.1 408 Request Timeout\r\n'
+            
+    def timeout(self):
+        """
+        Listener for timeout signal
+        """
+        if not self.socket:
+            return
+        html = self.get_408_timeout()
+        self.socket.write(html)
+        self.close()
+                
+    def disconnected(self):
+        """
+        The client has disconnected. Tidy up
+        """
+        log.debug(u'socket disconnected')
+        self.close()
+                    
+    def close(self):
+        """
+        The server has closed the connection. Tidy up
+        """
+        if not self.socket:
+            return
+        log.debug(u'close socket')
+        self.socket.close()
+        self.socket = None
+        self.parent.close_connection(self)
+

=== modified file 'openlp/plugins/remotes/remoteplugin.py'
--- openlp/plugins/remotes/remoteplugin.py	2010-04-30 22:38:15 +0000
+++ openlp/plugins/remotes/remoteplugin.py	2010-05-01 12:15:26 +0000
@@ -28,7 +28,7 @@
 from PyQt4 import QtNetwork, QtCore
 
 from openlp.core.lib import Plugin, Receiver
-from openlp.plugins.remotes.lib import RemoteTab
+from openlp.plugins.remotes.lib import RemoteTab, HttpServer
 
 log = logging.getLogger(__name__)
 
@@ -36,22 +36,26 @@
     log.info(u'Remote Plugin loaded')
 
     def __init__(self, plugin_helpers):
+        """
+        remotes constructor
+        """
         Plugin.__init__(self, u'Remotes', u'1.9.1', plugin_helpers)
         self.weight = -1
         self.server = None
 
     def initialise(self):
+        """
+        Initialise the remotes plugin, and start the http server
+        """
         log.debug(u'initialise')
         Plugin.initialise(self)
         self.insert_toolbox_item()
-        self.server = QtNetwork.QUdpSocket()
-        self.server.bind(
-            QtCore.QSettings().value(self.settingsSection + u'/remote port',
-            QtCore.QVariant(4316)).toInt()[0])
-        QtCore.QObject.connect(self.server,
-            QtCore.SIGNAL(u'readyRead()'), self.readData)
+        self.server = HttpServer(self)
 
     def finalise(self):
+        """
+        Tidy up and close down the http server
+        """
         log.debug(u'finalise')
         self.remove_toolbox_item()
         if self.server:
@@ -62,28 +66,13 @@
         Create the settings Tab
         """
         return RemoteTab(self.name)
-
-    def readData(self):
-        log.info(u'Remoted data has arrived')
-        while self.server.hasPendingDatagrams():
-            datagram, host, port = self.server.readDatagram(
-                self.server.pendingDatagramSize())
-            self.handle_datagram(datagram)
-
-    def handle_datagram(self, datagram):
-        log.info(u'Sending event %s ', datagram)
-        pos = datagram.find(u':')
-        event = unicode(datagram[:pos].lower())
-        if event == u'alert':
-            Receiver.send_message(u'alerts_text', unicode(datagram[pos + 1:]))
-        elif event == u'next_slide':
-            Receiver.send_message(u'slidecontroller_live_next')
-        else:
-            Receiver.send_message(event, unicode(datagram[pos + 1:]))
             
     def about(self):
+        """
+        Information about this plugin
+        """
         about_text = self.trUtf8('<b>Remote Plugin</b><br>This plugin '
             'provides the ability to send messages to a running version of '
-            'openlp on a different computer.<br>The Primary use for this '
-            'would be to send alerts from a creche')
+            'openlp on a different computer via a web browser or other app<br>'
+            'The Primary use for this would be to send alerts from a creche')
         return about_text

=== modified file 'openlp/plugins/songusage/songusageplugin.py'
--- openlp/plugins/songusage/songusageplugin.py	2010-04-30 22:38:15 +0000
+++ openlp/plugins/songusage/songusageplugin.py	2010-05-01 12:15:26 +0000
@@ -108,7 +108,7 @@
         log.info(u'SongUsage Initialising')
         Plugin.initialise(self)
         QtCore.QObject.connect(Receiver.get_receiver(),
-            QtCore.SIGNAL(u'slidecontroller_live_started'),
+            QtCore.SIGNAL(u'songs_live_started'),
             self.onReceiveSongUsage)
         self.SongUsageActive = QtCore.QSettings().value(
             self.settingsSection + u'/active',

=== modified file 'scripts/openlp-remoteclient.py'
--- scripts/openlp-remoteclient.py	2010-04-09 19:15:26 +0000
+++ scripts/openlp-remoteclient.py	2010-05-01 12:15:26 +0000
@@ -24,34 +24,33 @@
 # Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
 ###############################################################################
 
-import socket
+import urllib
 import sys
 from optparse import OptionParser
 
-def sendData(options, message):
-    addr = (options.address, options.port)
+def sendData(options):
+    addr = 'http://%s:%s/send/%s?q=%s' % (options.address, options.port,
+        options.event, options.message)
     try:
-        UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
-        UDPSock.sendto(message, addr)
-        print u'message sent ', message, addr
+        urllib.urlopen(addr)
+        print u'Message sent ', addr
     except:
-        print u'Errow thrown ', sys.exc_info()[1]
-
-def format_message(options):
-    return u'%s:%s' % (u'alert', options.message)
+        print u'Error thrown ', sys.exc_info()[1]
 
 def main():
-    usage = "usage: %prog [options] arg1 arg2"
+    usage = "usage: %prog [-a address] [-p port] [-e event] [-m message]"
     parser = OptionParser(usage=usage)
-    parser.add_option("-v", "--verbose",
-                      action="store_true", dest="verbose", default=True,
-                      help="make lots of noise [%default]")
     parser.add_option("-p", "--port", default=4316,
                       help="IP Port number %default ")
     parser.add_option("-a", "--address",
-                      help="Recipient address ")
+                      help="Recipient address ",
+                      default="localhost")
+    parser.add_option("-e", "--event",
+                      help="Action to be performed", 
+                      default="alerts_text")
     parser.add_option("-m", "--message",
-                      help="Message to be passed for the action")
+                      help="Message to be passed for the action",
+                      default="")
 
     (options, args) = parser.parse_args()
     if args:
@@ -60,12 +59,8 @@
     elif options.address is None:
         parser.print_help()
         parser.error("IP address missing")
-    elif options.message is None:
-        parser.print_help()
-        parser.error("No message passed")
     else:
-        text = format_message(options)
-        sendData(options, text)
+        sendData(options)
 
 if __name__ == u'__main__':
     main()


Follow ups