You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

236 lines
7.9 KiB
Python

# 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 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 six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from six.moves import socketserver
import select
import errno
import mimetypes
from gi.repository import SugarExt
from sugar3.activity import activity
from sugar3.graphics.objectchooser import ObjectChooser
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)
self.send_response(404)
self.end_headers()
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 as 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 FilePicker(ObjectChooser):
def __init__(self, parent):
ObjectChooser.__init__(self, parent)
def run(self):
jobject = None
_file = None
try:
result = ObjectChooser.run(self)
if result == Gtk.ResponseType.ACCEPT:
jobject = self.get_selected_object()
logging.debug('FilePicker.run: %r', jobject)
if jobject and jobject.file_path:
_file = jobject.file_path
logging.debug('FilePicker.run: file=%r', _file)
finally:
if jobject is not None:
jobject.destroy()
return _file
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)
try:
self._web_view.connect('run-file-chooser', self.__run_file_chooser)
except TypeError:
# Only present in WebKit1 > 1.9.3 and WebKit2
pass
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 __run_file_chooser(self, browser, request):
picker = FilePicker(self)
chosen = picker.run()
picker.destroy()
if chosen:
request.select_files([chosen])
return True