-from Plugins.Plugin import PluginDescriptor
-
-sessions = [ ]
-
-def startWebserver():
- from twisted.internet import reactor
- from twisted.web2 import server, http, static, resource, stream, http_headers, responsecode
- from twisted.python import util
- import webif
-
- class ScreenPage(resource.Resource):
- def __init__(self, path):
- self.path = path
-
- def render(self, req):
- global sessions
- if sessions == [ ]:
- return http.Response(200, stream="please wait until enigma has booted")
-
- s = stream.ProducerStream()
- webif.renderPage(s, self.path, sessions[0]) # login?
- return http.Response(stream=s)
-
- def locateChild(self, request, segments):
- path = '/'.join(["web"] + segments)
- if path[-1:] == "/":
- path += "index"
-
- path += ".xml"
- return ScreenPage(path), ()
-
- class Toplevel(resource.Resource):
- addSlash = True
-
- def render(self, req):
- return http.Response(responsecode.OK, {'Content-type': http_headers.MimeType('text', 'html')},
- stream='Hello! you want probably go to <a href="/web">the test</a> instead.')
-
- child_web = ScreenPage("/") # "/web"
- child_hdd = static.File("/hdd")
- child_webdata = static.File(util.sibpath(__file__, "web-data"))
-
- site = server.Site(Toplevel())
-
- reactor.listenTCP(80, http.HTTPFactory(site))
-
-def autostart(reason, **kwargs):
- if "session" in kwargs:
- global sessions
- sessions.append(kwargs["session"])
- return
-
- if reason == 0:
- #try:
- startWebserver()
- #except ImportError:
- # print "twisted not available, not starting web services"
-
-def Plugins(**kwargs):
- return PluginDescriptor(where = [PluginDescriptor.WHERE_SESSIONSTART, PluginDescriptor.WHERE_AUTOSTART], fnc = autostart)
--- /dev/null
+<e2:screen name="TestScreen"><html>
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type"/>
+ <title>Enigma 2 realtime OSD example </title>
+ <script src="/webdata/tools.js" type="text/javascript" />
+</head>
+<style type="text/css">
+body { background-color:#445566; color:#FFCC99; }
+a:link { color:#FF9966; }
+a:visited { color:#FF9900; }
+a:active { color:#FFFFFF; }
+#CurrentTime {
+ position: absolute; top:30px; right:30px;
+ color: white;
+ font-family:Arial,sans-serif;
+ font-size:2em;
+ font-weight:normal;
+}
+
+#CurrentService {
+ color: white;
+ font-family:Arial,sans-serif;
+ font-size:4em;
+ font-weight:normal;
+}
+
+#Event_Now_Name {
+ color: #998888;
+ font-family:Arial,sans-serif;
+ font-size:3em;
+ font-weight:bold;
+}
+
+#Event_Now_Begin {
+ color: #999999;
+ font-family:Arial,sans-serif;
+ font-size:2em;
+ font-weight:bold;
+}
+
+#Event_Now_Remaining {
+ position: absolute; right: 30px;
+ color: #999999;
+ font-family:Arial,sans-serif;
+ font-size:2em;
+ font-weight:bold;
+}
+
+#Event_Now_Description {
+ color: #99aaaa;
+ font-family:Arial,sans-serif;
+ font-size:1em;
+ font-weight:bold;
+}
+
+#Event_Now_Extended_Description {
+ color: #99bbbb;
+ font-family:Arial,sans-serif;
+ font-size:1em;
+ font-weight:bold;
+}
+
+
+ /* allow room for 3 columns */
+ol
+{
+ width: 45em;
+}
+
+ /* float and allow room for the widest item */
+ol li
+{
+ float: left;
+ width: 15em;
+}
+
+/* stop the float */
+br
+{
+ clear: left;
+}
+
+ /* separate the list from subsequent markup */
+div.wrapper
+{
+ margin-bottom: 1em;
+}
+
+</style>
+<body>
+
+<div id="CurrentTime"> </div>
+<div id="CurrentService"> </div>
+<div>
+<span id="Event_Now_Begin"> </span> <span id="Event_Now_Name"> </span>
+<span id="Event_Now_Remaining"> </span>
+</div>
+<div id="Event_Now_Description"> </div>
+<div id="Event_Now_Extended_Description"> </div>
+
+<div id="ChannelList">
+<ol>
+<e2:element source="ServiceList">
+
+<e2:convert type="web:ListFiller">
+<li onclick="zap(this)" id="<e2:item name="Reference" />"><e2:item name="Name"/></li>
+</e2:convert>
+
+ <e2:convert type="web:TextToHTML" />
+</e2:element>
+</ol>
+</div>
+
+<script type="text/javascript" language="javascript">
+function set(what, value)
+{
+ document.getElementById(what).innerHTML = value;
+}
+
+function updatePage()
+{
+}
+
+function zap(li)
+{
+ var request = getHTTPObject();
+ var url = "/web/zap?ZapTo=" + escape(li.id);
+ request.open("GET", url, true);
+ request.onreadystatechange = updatePage;
+ request.send(null);
+}
+
+</script>
+</body>
+
+<!-- realtime updates follow -->
+<e2:element source="CurrentTime" streaming="yes">
+ <e2:convert type="ClockToText">WithSeconds</e2:convert>
+ <e2:convert type="web:JavascriptUpdate" />
+</e2:element>
+<e2:element source="CurrentService" streaming="yes">
+ <e2:convert type="ServiceName">Name</e2:convert>
+ <e2:convert type="web:JavascriptUpdate" />
+</e2:element>
+<e2:element source="Event_Now" id="Event_Now_Name" streaming="yes">
+ <e2:convert type="EventName">Name</e2:convert>
+ <e2:convert type="web:JavascriptUpdate" />
+</e2:element>
+<e2:element source="Event_Now" id="Event_Now_Description" streaming="yes">
+ <e2:convert type="EventName">Description</e2:convert>
+ <e2:convert type="web:JavascriptUpdate" />
+</e2:element>
+<e2:element source="Event_Now" id="Event_Now_Extended_Description" streaming="yes">
+ <e2:convert type="EventName">ExtendedDescription</e2:convert>
+ <e2:convert type="web:JavascriptUpdate" />
+</e2:element>
+<e2:element source="Event_Now" id="Event_Now_Remaining" streaming="yes">
+ <e2:convert type="EventTime">Remaining</e2:convert>
+ <e2:convert type="RemainingToText">InMinutes</e2:convert>
+ <e2:convert type="web:JavascriptUpdate" />
+</e2:element>
+<e2:element source="Event_Now" id="Event_Now_Begin" streaming="yes">
+ <e2:convert type="EventTime">StartTime</e2:convert>
+ <e2:convert type="ClockToText">Default</e2:convert>
+ <e2:convert type="web:JavascriptUpdate" />
+</e2:element>
+</e2:screen>
#
-# OK, this is more a proof of concept
+# OK, this is more than a proof of concept
# things to improve:
# - nicer code
# - screens need to be defined somehow else.
# for our testscreen
from Screens.InfoBarGenerics import InfoBarServiceName, InfoBarEvent
from Components.Sources.Clock import Clock
+#from Components.Sources.Config import Config
+from Components.Sources.ServiceList import ServiceList
+from Components.Converter.Converter import Converter
+#from Components.config import config
+from Components.Element import Element
from xml.sax import make_parser
from xml.sax.handler import ContentHandler, feature_namespaces
InfoBarServiceName.__init__(self)
InfoBarEvent.__init__(self)
self["CurrentTime"] = Clock()
+# self["TVSystem"] = Config(config.av.tvsystem)
+# self["OSDLanguage"] = Config(config.osd.language)
+# self["FirstRun"] = Config(config.misc.firstrun)
+ from enigma import eServiceReference
+ fav = eServiceReference('1:7:1:0:0:0:0:0:0:0:(type == 1) || (type == 17) || (type == 195) || (type == 25) FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet')
+ self["ServiceList"] = ServiceList(fav, command_func = self.zapTo)
+ self["ServiceListBrowse"] = ServiceList(fav, command_func = self.browseTo)
-# turns .text into __str__
-class Element:
- def __init__(self, source):
- self.source = source
+ def browseTo(self, reftobrowse):
+ self["ServiceListBrowse"].root = refttobrowse
- def __str__(self):
- return self.source.text
+ def zapTo(self, reftozap):
+ self.session.nav.playService(reftozap)
+
+# implements the 'render'-call.
+# this will act as a downstream_element, like a renderer.
+class OneTimeElement(Element):
+ def __init__(self, id):
+ Element.__init__(self)
+ self.source_id = id
+
+ # CHECKME: is this ok performance-wise?
+ def handleCommand(self, args):
+ for c in args.get(self.source_id, []):
+ self.source.handleCommand(c)
+
+ def render(self, stream):
+ t = self.source.getHTML(self.source_id)
+ if isinstance(t, unicode):
+ t = t.encode("utf-8")
+ stream.write(t)
+
+ def destroy(self):
+ pass
+
+class StreamingElement(OneTimeElement):
+ def __init__(self, id):
+ OneTimeElement.__init__(self, id)
+ self.stream = None
+
+ def changed(self, what):
+ if self.stream:
+ self.render(self.stream)
+
+ def setStream(self, stream):
+ self.stream = stream
# a to-be-filled list item
class ListItem:
def __init__(self, name):
self.name = name
+class TextToHTML(Converter):
+ def __init__(self, arg):
+ Converter.__init__(self, arg)
+
+ def getHTML(self, id):
+ return self.source.text # encode & etc. here!
+
+# a null-output. Useful if you only want to issue a command.
+class Null(Converter):
+ def __init__(self, arg):
+ Converter.__init__(self, arg)
+
+ def getHTML(self, id):
+ return ""
+
+def escape(s):
+ return s.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"')
+
+class JavascriptUpdate(Converter):
+ def __init__(self, arg):
+ Converter.__init__(self, arg)
+
+ def getHTML(self, id):
+ return '<script>set("' + id + '", "' + escape(self.source.text) + '");</script>\n'
+
# the performant 'listfiller'-engine (plfe)
-class ListFiller(object):
+class ListFiller(Converter):
def __init__(self, arg):
- self.template = arg
-
+ Converter.__init__(self, arg)
+
def getText(self):
l = self.source.list
lut = self.source.lut
# now build a ["string", 1, "string", 2]-styled list, with indices into the
# list to avoid lookup of item name for each entry
lutlist = []
- for element in self.template:
+ for element in self.converter_arguments:
if isinstance(element, str):
lutlist.append(element)
elif isinstance(element, ListItem):
self.mode = 0
self.screen = None
self.session = session
+ self.screens = [ ]
def startElement(self, name, attrs):
if name == "e2:screen":
self.screen = eval(attrs["name"])(self.session) # fixme
+ self.screens.append(self.screen)
return
if name[:3] == "e2:":
self.res.append(tag)
elif self.mode == 1: # expect "<e2:element>"
assert name == "e2:element", "found %s instead of e2:element" % name
- self.source = self.screen[attrs["source"]]
+ source = attrs["source"]
+ self.source_id = str(attrs.get("id", source))
+ self.source = self.screen[source]
+ self.is_streaming = "streaming" in attrs
elif self.mode == 2: # expect "<e2:convert>"
if name[:3] == "e2:":
assert name == "e2:convert"
self.sub.append(tag)
elif self.mode == 3:
assert name == "e2:item", "found %s instead of e2:item!" % name
+ assert "name" in attrs, "e2:item must have a name= attribute!"
self.sub.append(ListItem(attrs["name"]))
def endElement(self, name):
del self.sub
elif self.mode == 1: # closed 'element'
- self.res.append(Element(self.source))
+ # instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
+ if not self.is_streaming:
+ c = OneTimeElement(self.source_id)
+ else:
+ c = StreamingElement(self.source_id)
+
+ c.connect(self.source)
+ self.res.append(c)
+ self.screen.renderer.append(c)
del self.source
if name[:3] == "e2:":
def startEntity(self, name):
self.res.append('&' + name + ';');
+ def cleanup(self):
+ print "screen cleanup!"
+ for screen in self.screens:
+ screen.doClose()
+ self.screens = [ ]
+
def lreduce(list):
# ouch, can be made better
res = [ ]
string = None
for x in list:
if isinstance(x, str) or isinstance(x, unicode):
- x = x.encode("UTF-8")
+ if isinstance(x, unicode):
+ x = x.encode("UTF-8")
if string is None:
string = x
else:
string = None
return res
-def renderPage(stream, path, session):
+def renderPage(stream, path, req, session):
+
+ # read in the template, create required screens
+ # we don't have persistense yet.
+ # if we had, this first part would only be done once.
handler = webifHandler(session)
parser = make_parser()
parser.setFeature(feature_namespaces, 0)
parser.setContentHandler(handler)
parser.parse(open(util.sibpath(__file__, path)))
+
+ # by default, we have non-streaming pages
+ finish = True
+
+ # first, apply "commands" (aka. URL argument)
+ for x in handler.res:
+ if isinstance(x, Element):
+ x.handleCommand(req.args)
+
+ # now, we have a list with static texts mixed
+ # with non-static Elements.
+ # flatten this list, write into the stream.
for x in lreduce(handler.res):
- stream.write(str(x))
- stream.finish() # must be done, unless we "callLater"
+ if isinstance(x, Element):
+ if isinstance(x, StreamingElement):
+ finish = False
+ x.setStream(stream)
+ x.render(stream)
+ else:
+ stream.write(str(x))
+
+ def ping(s):
+ from twisted.internet import reactor
+ s.write("PING<br/>\n");
+ reactor.callLater(3, ping, s)
+
+ # if we met a "StreamingElement", there is at least one
+ # element which wants to output data more than once,
+ # i.e. on host-originated changes.
+ # in this case, don't finish yet, don't cleanup yet,
+ # but instead do that when the client disconnects.
+ if finish:
+ handler.cleanup()
+ stream.finish()
+ else:
+ # ok.
+ # you *need* something which constantly sends something in a regular interval,
+ # in order to detect disconnected clients.
+ # i agree that this "ping" sucks terrible, so better be sure to have something
+ # similar. A "CurrentTime" is fine. Or anything that creates *some* output.
+# ping(stream)
+ stream.closed_callback = lambda: handler.cleanup()