diff --git a/bin/sugar-activity-web b/bin/sugar-activity-web index 32065db5..388a5346 100644 --- a/bin/sugar-activity-web +++ b/bin/sugar-activity-web @@ -17,4 +17,8 @@ # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. -exec sugar-activity sugar3.activity.webactivity.WebActivity $@ +if [ "$SUGAR_USE_WEBKIT1" == "yes" ]; then + exec sugar-activity sugar3.activity.webkit1.WebActivity $@ +else + exec sugar-activity sugar3.activity.webactivity.WebActivity $@ +fi diff --git a/src/sugar3/activity/Makefile.am b/src/sugar3/activity/Makefile.am index b80b81be..ace612e9 100644 --- a/src/sugar3/activity/Makefile.am +++ b/src/sugar3/activity/Makefile.am @@ -6,6 +6,7 @@ sugar_PYTHON = \ activityhandle.py \ activityservice.py \ bundlebuilder.py \ + webkit1.py \ webactivity.py \ i18n.py \ widgets.py diff --git a/src/sugar3/activity/webkit1.py b/src/sugar3/activity/webkit1.py new file mode 100644 index 00000000..deaaa128 --- /dev/null +++ b/src/sugar3/activity/webkit1.py @@ -0,0 +1,203 @@ +# Copyright (C) 2013 Gonzalo Odiard +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +import json +import os +import logging + +from gi.repository import Gio +from gi.repository import Gtk +from gi.repository import GdkX11 +assert GdkX11 + +from gi.repository import GObject +GObject.threads_init() +from gi.repository import WebKit +import socket +from threading import Thread +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer +import SocketServer +import select +import errno +import mimetypes + +from gi.repository import SugarExt +from sugar3.activity import activity + + +class LocalRequestHandler(BaseHTTPRequestHandler): + + #Handler for the GET requests + def do_GET(self): + new_path = self.server.path + '/' + self.path + if not os.path.exists(new_path): + logging.error('file %s not found.', new_path) + return False + + with open(new_path) as f: + content = f.read() + self.send_response(200) + mime, _encoding = mimetypes.guess_type(self.path) + self.send_header("Content-type", mime) + self.end_headers() + self.wfile.write(content) + return False + + +class LocalHTTPServer(HTTPServer): + + def __init__(self, server_address, request_handler, path): + self.path = path + HTTPServer.__init__(self, server_address, request_handler) + + def serve_forever(self, poll_interval=0.5): + """Overridden version of BaseServer.serve_forever that + does not fail to work when EINTR is received. + """ + self._BaseServer__serving = True + self._BaseServer__is_shut_down.clear() + while self._BaseServer__serving: + + # XXX: Consider using another file descriptor or + # connecting to the socket to wake this up instead of + # polling. Polling reduces our responsiveness to a + # shutdown request and wastes cpu at all other times. + try: + r, w, e = select.select([self], [], [], poll_interval) + except select.error, e: + if e[0] == errno.EINTR: + logging.debug("got eintr") + continue + raise + if r: + self._handle_request_noblock() + self._BaseServer__is_shut_down.set() + + def server_bind(self): + """Override server_bind in HTTPServer to not use + getfqdn to get the server name because is very slow.""" + SocketServer.TCPServer.server_bind(self) + _host, port = self.socket.getsockname()[:2] + self.server_name = 'localhost' + self.server_port = port + + +class WebActivity(Gtk.Window): + def __init__(self, handle): + Gtk.Window.__init__(self) + + self._activity_id = handle.activity_id + self._object_id = handle.object_id + self._bundle_id = os.environ["SUGAR_BUNDLE_ID"] + self._bundle_path = os.environ["SUGAR_BUNDLE_PATH"] + self._inspector_visible = False + + self.set_decorated(False) + self.maximize() + + self.connect('realize', self._realize_cb) + self.connect('destroy', self._destroy_cb) + + # Get a free socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('', 0)) + sock.listen(socket.SOMAXCONN) + _ipaddr, self.port = sock.getsockname() + sock.shutdown(socket.SHUT_RDWR) + logging.error('Using port %d', self.port) + + # start the local web server + httpd = LocalHTTPServer(('', self.port), + lambda *args: LocalRequestHandler(*args), + activity.get_bundle_path()) + self._server = Thread(target=httpd.serve_forever) + self._server.setDaemon(True) + self._server.start() + + self._web_view = WebKit.WebView() + self._web_view.connect("notify::load-status", + self._loading_changed_cb) + self._web_view.connect("resource-request-starting", + self._resource_request_starting_cb) + + self.add(self._web_view) + self._web_view.show() + + self._web_view.load_uri("activity://%s/index.html" % self._bundle_id) + + self.set_title(activity.get_bundle_name()) + + def run_main_loop(self): + Gtk.main() + + def _resource_request_starting_cb(self, webview, web_frame, web_resource, + request, response): + # this is used only in the case of webkit1 + uri = web_resource.get_uri() + if uri.startswith('activity://'): + prefix = "activity://%s" % self._bundle_id + new_prefix = "http://0.0.0.0:%d" % self.port + new_uri = new_prefix + uri[len(prefix):] + + request.set_uri(new_uri) + + def _realize_cb(self, window): + xid = window.get_window().get_xid() + SugarExt.wm_set_bundle_id(xid, self._bundle_id) + SugarExt.wm_set_activity_id(xid, str(self._activity_id)) + + def _destroy_cb(self, window): + self.destroy() + Gtk.main_quit() + + def _loading_changed_cb(self, web_view, load_event): + + status = web_view.get_load_status() + + if status == WebKit.LoadStatus.FINISHED: + key = os.environ["SUGAR_APISOCKET_KEY"] + port = os.environ["SUGAR_APISOCKET_PORT"] + + env_json = json.dumps({"apiSocketKey": key, + "apiSocketPort": port, + "activityId": self._activity_id, + "bundleId": self._bundle_id, + "objectId": self._object_id, + "activityName": activity.get_bundle_name()}) + + script = """ + var environment = %s; + + if (window.sugar === undefined) { + window.sugar = {}; + } + + window.sugar.environment = environment; + + if (window.sugar.onEnvironmentSet) + window.sugar.onEnvironmentSet(); + """ % env_json + + self._web_view.execute_script(script) + + def _app_scheme_cb(self, request, user_data): + path = os.path.join(self._bundle_path, + os.path.relpath(request.get_path(), "/")) + + request.finish(Gio.File.new_for_path(path).read(None), + -1, Gio.content_type_guess(path, None)[0])