[arch-commits] Commit in sugar-toolkit-gtk3/repos/community-testing-x86_64 (3 files)

Balló György bgyorgy at archlinux.org
Fri Mar 29 10:01:14 UTC 2019


    Date: Friday, March 29, 2019 @ 10:01:13
  Author: bgyorgy
Revision: 445977

archrelease: copy trunk to community-testing-x86_64

Added:
  sugar-toolkit-gtk3/repos/community-testing-x86_64/PKGBUILD
    (from rev 445976, sugar-toolkit-gtk3/trunk/PKGBUILD)
  sugar-toolkit-gtk3/repos/community-testing-x86_64/six.patch
    (from rev 445976, sugar-toolkit-gtk3/trunk/six.patch)
Deleted:
  sugar-toolkit-gtk3/repos/community-testing-x86_64/PKGBUILD

-----------+
 PKGBUILD  |   83 -
 six.patch | 2987 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 3033 insertions(+), 37 deletions(-)

Deleted: PKGBUILD
===================================================================
--- PKGBUILD	2019-03-29 10:01:08 UTC (rev 445976)
+++ PKGBUILD	2019-03-29 10:01:13 UTC (rev 445977)
@@ -1,37 +0,0 @@
-# Maintainer: Balló György <ballogyor+arch at gmail dot com>
-
-pkgname=sugar-toolkit-gtk3
-pkgver=0.113
-pkgrel=1
-pkgdesc="Sugar GTK library"
-arch=('x86_64')
-url="https://sugarlabs.org/"
-license=('LGPL')
-depends=('alsa-lib' 'gtk3' 'libsm' 'python2-cairo' 'python2-dateutil' 'python2-decorator' 'python2-six'
-         'python2-telepathy' 'sugar-artwork' 'sugar-datastore' 'telepathy-mission-control' 'unzip')
-makedepends=('intltool' 'gobject-introspection')
-optdepends=('webkit2gtk: run sugar-activity-web')
-source=(https://download.sugarlabs.org/sources/sucrose/glucose/$pkgname/$pkgname-$pkgver.tar.xz)
-sha256sums=('4062d981d551f1567d073389385ffc28793b75f0ed2485e8f9250a145239cba7')
-
-build() {
-  cd $pkgname-$pkgver
-  # Disable hardened build until fixed upstream
-  # https://bugs.sugarlabs.org/ticket/4886
-  export CFLAGS=${CFLAGS/ -fno-plt}
-  export LDFLAGS=${LDFLAGS/,-z,now}
-
-  # Don't use incomplete python3 port
-  sed -i 's/PYTHON=python3/PYTHON=python2/' configure
-  sed -i 's/exec sugar-activity3/exec sugar-activity/' bin/sugar-activity-web
-  sed -i '/dist_bin_SCRIPTS/ s/ sugar-activity3//' bin/Makefile.in
-
-  ./configure --prefix=/usr
-  sed -i 's/ -shared / -Wl,-O1,--as-needed\0/g' libtool
-  make
-}
-
-package() {
-  cd $pkgname-$pkgver
-  make DESTDIR="$pkgdir" install
-}

Copied: sugar-toolkit-gtk3/repos/community-testing-x86_64/PKGBUILD (from rev 445976, sugar-toolkit-gtk3/trunk/PKGBUILD)
===================================================================
--- PKGBUILD	                        (rev 0)
+++ PKGBUILD	2019-03-29 10:01:13 UTC (rev 445977)
@@ -0,0 +1,46 @@
+# Maintainer: Balló György <ballogyor+arch at gmail dot com>
+
+pkgname=sugar-toolkit-gtk3
+pkgver=0.113
+pkgrel=2
+pkgdesc="Sugar GTK library"
+arch=('x86_64')
+url="https://sugarlabs.org/"
+license=('LGPL')
+depends=('alsa-lib' 'gtk3' 'libsm' 'python2-cairo' 'python2-dateutil' 'python2-decorator'
+         'python2-telepathy' 'sugar-artwork' 'sugar-datastore' 'telepathy-mission-control' 'unzip')
+makedepends=('intltool' 'gobject-introspection')
+optdepends=('webkit2gtk: run sugar-activity-web')
+source=(https://download.sugarlabs.org/sources/sucrose/glucose/$pkgname/$pkgname-$pkgver.tar.xz
+        six.patch)
+sha256sums=('4062d981d551f1567d073389385ffc28793b75f0ed2485e8f9250a145239cba7'
+            '3adf9d6ddc817c31887f1c22e9976d9865ca17c608a5142ef97ffbd8c0de3424')
+
+prepare() {
+  cd $pkgname-$pkgver
+  # Revert 'Port from Python 2 to six', it's not complete yet
+  patch -RNp1 -i ../six.patch
+  autoreconf -fi
+}
+
+build() {
+  cd $pkgname-$pkgver
+  # Disable hardened build until fixed upstream
+  # https://bugs.sugarlabs.org/ticket/4886
+  export CFLAGS=${CFLAGS/ -fno-plt}
+  export LDFLAGS=${LDFLAGS/,-z,now}
+
+  # Don't use incomplete python3 port
+  sed -i 's/PYTHON=python3/PYTHON=python2/' configure
+  sed -i 's/exec sugar-activity3/exec sugar-activity/' bin/sugar-activity-web
+  sed -i '/dist_bin_SCRIPTS/ s/ sugar-activity3//' bin/Makefile.in
+
+  ./configure --prefix=/usr
+  sed -i 's/ -shared / -Wl,-O1,--as-needed\0/g' libtool
+  make
+}
+
+package() {
+  cd $pkgname-$pkgver
+  make DESTDIR="$pkgdir" install
+}

Copied: sugar-toolkit-gtk3/repos/community-testing-x86_64/six.patch (from rev 445976, sugar-toolkit-gtk3/trunk/six.patch)
===================================================================
--- six.patch	                        (rev 0)
+++ six.patch	2019-03-29 10:01:13 UTC (rev 445977)
@@ -0,0 +1,2987 @@
+From aa8a5e70c415e6c2acf4ff373d9b366ac4692bb1 Mon Sep 17 00:00:00 2001
+From: Pro-Panda <f2016015 at pilani.bits-pilani.ac.in>
+Date: Thu, 1 Mar 2018 17:28:56 +0530
+Subject: [PATCH] Port from Python 2 to six
+
+Signed-off-by: James Cameron <quozl at laptop.org>
+---
+ Makefile.am                                   |   2 +-
+ bin/Makefile.am                               |   2 +-
+ bin/sugar-activity                            | 218 +----------------
+ bin/sugar-activity-web                        |   4 +-
+ bin/sugar-activity3                           |   5 +
+ configure.ac                                  |   2 +-
+ doc/conf.py                                   |  26 +-
+ examples/alert.py                             |   6 +-
+ examples/animator.py                          |   2 +-
+ examples/colorbutton.py                       |   2 +-
+ examples/combobox.py                          |   2 +-
+ examples/customdestroy.py                     |   6 +-
+ examples/gtktreesensitive.py                  |   2 +-
+ examples/iconentry.py                         |   4 +-
+ examples/radiotoolbutton.py                   |   2 +-
+ examples/scrollingdetector.py                 |   6 +-
+ examples/tabs.py                              |   2 +-
+ examples/toolbutton.py                        |   2 +-
+ src/sugar3/activity/Makefile.am               |   1 +
+ src/sugar3/activity/activity.py               |  34 +--
+ src/sugar3/activity/activityfactory.py        |  13 +-
+ src/sugar3/activity/activityhandle.py         |  14 +-
+ src/sugar3/activity/activityinstance.py       | 222 ++++++++++++++++++
+ src/sugar3/activity/activityservice.py        |   2 +-
+ src/sugar3/activity/bundlebuilder.py          |  44 ++--
+ src/sugar3/activity/webkit1.py                |   8 +-
+ src/sugar3/activity/widgets.py                |   2 +-
+ src/sugar3/bundle/__init__.py                 |   6 +-
+ src/sugar3/bundle/activitybundle.py           |   4 +-
+ src/sugar3/bundle/bundle.py                   |  10 +-
+ src/sugar3/bundle/bundleversion.py            |   1 +
+ src/sugar3/bundle/contentbundle.py            |   7 +-
+ src/sugar3/datastore/datastore.py             |  17 +-
+ src/sugar3/dispatch/dispatcher.py             |  20 +-
+ src/sugar3/dispatch/saferef.py                |  48 ++--
+ src/sugar3/env.py                             |   4 +-
+ src/sugar3/graphics/Makefile.am               |   1 -
+ src/sugar3/graphics/alert.py                  |  22 +-
+ src/sugar3/graphics/colorbutton.py            |  42 ++--
+ src/sugar3/graphics/icon.py                   |  21 +-
+ src/sugar3/graphics/iconentry.py              |   2 +-
+ src/sugar3/graphics/objectchooser.py          |  24 +-
+ src/sugar3/graphics/palettegroup.py           |   2 +-
+ src/sugar3/graphics/palettemenu.py            |   2 +-
+ src/sugar3/graphics/palettewindow.py          |  14 +-
+ src/sugar3/graphics/popwindow.py              | 202 ----------------
+ src/sugar3/graphics/progressicon.py           |   1 +
+ src/sugar3/graphics/scrollingdetector.py      |  12 +-
+ src/sugar3/graphics/style.py                  |   5 +-
+ src/sugar3/graphics/toolbarbox.py             |   2 +
+ src/sugar3/graphics/toolbox.py                |  14 +-
+ src/sugar3/graphics/toolbutton.py             |   2 +-
+ src/sugar3/graphics/tray.py                   |   4 +
+ src/sugar3/graphics/xocolor.py                |  24 +-
+ src/sugar3/logger.py                          |  25 +-
+ src/sugar3/mime.py                            |   8 +-
+ src/sugar3/network.py                         |  24 +-
+ src/sugar3/presence/activity.py               |  24 +-
+ src/sugar3/presence/buddy.py                  |  16 +-
+ src/sugar3/presence/connectionmanager.py      |   6 +-
+ src/sugar3/presence/presenceservice.py        |   7 +-
+ src/sugar3/presence/tubeconn.py               |   7 +-
+ src/sugar3/profile.py                         |   3 +-
+ src/sugar3/speech.py                          |   9 +-
+ src/sugar3/test/Makefile.am                   |   2 +-
+ src/sugar3/test/{unittest.py => _unittest.py} |  10 +-
+ src/sugar3/test/discover.py                   |   2 -
+ src/sugar3/test/uitree.py                     |   2 +-
+ src/sugar3/util.py                            |  20 +-
+ .../sample.activity/activity/activity.info    |   2 +-
+ tests/data/sample.activity/setup.py           |   2 +-
+ tests/graphics/progressicon.py                |   1 +
+ tests/test_mime.py                            |   2 +-
+ tests/test_uitree.py                          |   3 +-
+ 74 files changed, 623 insertions(+), 700 deletions(-)
+ create mode 100755 bin/sugar-activity3
+ create mode 100644 src/sugar3/activity/activityinstance.py
+ delete mode 100644 src/sugar3/graphics/popwindow.py
+ rename src/sugar3/test/{unittest.py => _unittest.py} (93%)
+
+diff --git a/Makefile.am b/Makefile.am
+index 70ed1a8ff..d89c4a61b 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -26,4 +26,4 @@ check-po:
+ test: check-po
+ 	pyflakes $(top_srcdir)
+ 	pep8 $(top_srcdir)
+-	python -m sugar3.test.discover $(top_srcdir)/tests
++	python3 -m sugar3.test.discover $(top_srcdir)/tests
+diff --git a/bin/Makefile.am b/bin/Makefile.am
+index 2d214b3cc..3b5f6f8f8 100644
+--- a/bin/Makefile.am
++++ b/bin/Makefile.am
+@@ -1 +1 @@
+-dist_bin_SCRIPTS = sugar-activity sugar-activity-web
++dist_bin_SCRIPTS = sugar-activity sugar-activity-web sugar-activity3
+diff --git a/bin/sugar-activity b/bin/sugar-activity
+index a184655ae..9e12d61ae 100755
+--- a/bin/sugar-activity
++++ b/bin/sugar-activity
+@@ -1,219 +1,5 @@
+ #!/usr/bin/env python2
+ 
+-# Copyright (C) 2006-2008, Red Hat, Inc.
+-#
+-# 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; either version 2 of the License, or
+-# (at your option) any later version.
+-#
+-# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
++from sugar3.activity import activityinstance
+ 
+-import os
+-import sys
+-
+-# Change the default encoding to avoid UnicodeDecodeError
+-# http://lists.sugarlabs.org/archive/sugar-devel/2012-August/038928.html
+-reload(sys)
+-sys.setdefaultencoding('utf-8')
+-
+-import gettext
+-from optparse import OptionParser
+-
+-import dbus
+-import dbus.service
+-from dbus.mainloop.glib import DBusGMainLoop
+-DBusGMainLoop(set_as_default=True)
+-
+-from sugar3.activity import activityhandle
+-from sugar3 import config
+-from sugar3.bundle.activitybundle import ActivityBundle
+-from sugar3 import logger
+-
+-from sugar3.bundle.bundle import MalformedBundleException
+-
+-from distutils.dir_util import mkpath
+-import time
+-import hashlib
+-import random
+-
+-def create_activity_instance(constructor, handle):
+-    activity = constructor(handle)
+-    activity.show()
+-    return activity
+-
+-
+-def get_single_process_name(bundle_id):
+-    return bundle_id
+-
+-
+-def get_single_process_path(bundle_id):
+-    return '/' + bundle_id.replace('.', '/')
+-
+-
+-class SingleProcess(dbus.service.Object):
+-
+-    def __init__(self, name_service, constructor):
+-        self.constructor = constructor
+-
+-        bus = dbus.SessionBus()
+-        bus_name = dbus.service.BusName(name_service, bus=bus)
+-        object_path = get_single_process_path(name_service)
+-        dbus.service.Object.__init__(self, bus_name, object_path)
+-
+-    @dbus.service.method('org.laptop.SingleProcess', in_signature='a{sv}')
+-    def create(self, handle_dict):
+-        handle = activityhandle.create_from_dict(handle_dict)
+-        create_activity_instance(self.constructor, handle)
+-
+-
+-def main():
+-    usage = 'usage: %prog [options] [activity dir] [python class]'
+-    epilog = 'If you are running from a directory containing an Activity, ' \
+-             'the argument may be omitted.  Otherwise please provide either '\
+-             'a directory containing a Sugar Activity [activity dir], a '\
+-             '[python_class], or both.'
+-
+-    parser = OptionParser(usage=usage, epilog=epilog)
+-    parser.add_option('-b', '--bundle-id', dest='bundle_id',
+-                      help='identifier of the activity bundle')
+-    parser.add_option('-a', '--activity-id', dest='activity_id',
+-                      help='identifier of the activity instance')
+-    parser.add_option('-o', '--object-id', dest='object_id',
+-                      help='identifier of the associated datastore object')
+-    parser.add_option('-u', '--uri', dest='uri',
+-                      help='URI to load')
+-    parser.add_option('-s', '--single-process', dest='single_process',
+-                      action='store_true',
+-                      help='start all the instances in the same process')
+-    parser.add_option('-i', '--invited', dest='invited',
+-                      action='store_true', default=False,
+-                      help='the activity is being launched for handling an '
+-                           'invite from the network')
+-    (options, args) = parser.parse_args()
+-
+-    logger.start()
+-
+-    activity_class = None
+-    if len(args) == 2:
+-        activity_class = args[1]
+-        os.chdir(args[0])
+-    elif len(args) == 1:
+-        if os.path.isdir(args[0]):
+-            os.chdir(args[0])
+-        else:
+-            activity_class = args[0]
+-
+-    os.environ['SUGAR_BUNDLE_PATH'] = os.path.abspath(os.curdir)
+-
+-    bundle_path = os.environ['SUGAR_BUNDLE_PATH']
+-    sys.path.insert(0, bundle_path)
+-
+-    try:
+-        bundle = ActivityBundle(bundle_path)
+-    except MalformedBundleException:
+-        parser.print_help()
+-        exit(0)
+-
+-    if not activity_class:
+-        if bundle.get_command().startswith('sugar-activity'):
+-            activity_class = bundle.get_command().split(" ")[1]
+-
+-    if 'SUGAR_VERSION' not in os.environ:
+-        profile_id = os.environ.get('SUGAR_PROFILE', 'default')
+-        home_dir = os.environ.get('SUGAR_HOME', os.path.expanduser('~/.sugar'))
+-        base = os.path.join(home_dir, profile_id)
+-        activity_root = os.path.join(base, bundle.get_bundle_id())
+-
+-        instance_dir = os.path.join(activity_root, 'instance')
+-        mkpath(instance_dir)
+-
+-        data_dir = os.path.join(activity_root, 'data')
+-        mkpath(data_dir)
+-
+-        tmp_dir = os.path.join(activity_root, 'tmp')
+-        mkpath(tmp_dir)
+-
+-        os.environ['SUGAR_ACTIVITY_ROOT'] = activity_root
+-        os.environ['SUGAR_BUNDLE_PATH'] = bundle.get_path()
+-
+-    os.environ['SUGAR_BUNDLE_ID'] = bundle.get_bundle_id()
+-    os.environ['SUGAR_BUNDLE_NAME'] = bundle.get_name()
+-    os.environ['SUGAR_BUNDLE_VERSION'] = str(bundle.get_activity_version())
+-
+-    # must be done early, some activities set translations globally, SL #3654
+-    activity_locale_path = os.environ.get("SUGAR_LOCALEDIR",
+-                                          config.locale_path)
+-
+-    gettext.bindtextdomain(bundle.get_bundle_id(), activity_locale_path)
+-    gettext.bindtextdomain('sugar-toolkit-gtk3', config.locale_path)
+-    gettext.textdomain(bundle.get_bundle_id())
+-
+-    splitted_module = activity_class.rsplit('.', 1)
+-    module_name = splitted_module[0]
+-    class_name = splitted_module[1]
+-
+-    module = __import__(module_name)
+-    for comp in module_name.split('.')[1:]:
+-        module = getattr(module, comp)
+-
+-    activity_constructor = getattr(module, class_name)
+-
+-    if not options.activity_id:
+-        # Generate random hash
+-        data = '%s%s' % (time.time(), random.randint(10000, 100000))
+-        random_hash = hashlib.sha1(data).hexdigest()
+-        options.activity_id = random_hash
+-        options.bundle_id = bundle.get_bundle_id()
+-
+-    activity_handle = activityhandle.ActivityHandle(
+-        activity_id=options.activity_id,
+-        object_id=options.object_id, uri=options.uri,
+-        invited=options.invited)
+-
+-    if options.single_process is True:
+-        sessionbus = dbus.SessionBus()
+-
+-        service_name = get_single_process_name(options.bundle_id)
+-        service_path = get_single_process_path(options.bundle_id)
+-
+-        bus_object = sessionbus.get_object(
+-            'org.freedesktop.DBus', '/org/freedesktop/DBus')
+-        try:
+-            name = bus_object.GetNameOwner(
+-                service_name, dbus_interface='org.freedesktop.DBus')
+-        except dbus.DBusException:
+-            name = None
+-
+-        if not name:
+-            SingleProcess(service_name, activity_constructor)
+-        else:
+-            try:
+-                single_process = sessionbus.get_object(service_name,
+-                                                       service_path)
+-                single_process.create(
+-                    activity_handle.get_dict(),
+-                    dbus_interface='org.laptop.SingleProcess')
+-
+-                print 'Created %s in a single process.' % service_name
+-                sys.exit(0)
+-            except (TypeError, dbus.DBusException):
+-                print 'Could not communicate with the instance process,' \
+-                    'launching a new process'
+-
+-    if hasattr(module, 'start'):
+-        module.start()
+-
+-    instance = create_activity_instance(activity_constructor, activity_handle)
+-
+-    if hasattr(instance, 'run_main_loop'):
+-        instance.run_main_loop()
+-
+-main()
++activityinstance.main()
+diff --git a/bin/sugar-activity-web b/bin/sugar-activity-web
+index b204b3e69..8fd7a59ce 100644
+--- a/bin/sugar-activity-web
++++ b/bin/sugar-activity-web
+@@ -18,7 +18,7 @@
+ # Boston, MA 02111-1307, USA.
+ 
+ if [ "$SUGAR_USE_WEBKIT1" = "yes" ]; then
+-    exec sugar-activity sugar3.activity.webkit1.WebActivity $@
++    exec sugar-activity3 sugar3.activity.webkit1.WebActivity $@
+ else
+-    exec sugar-activity sugar3.activity.webactivity.WebActivity $@
++    exec sugar-activity3 sugar3.activity.webactivity.WebActivity $@
+ fi
+diff --git a/bin/sugar-activity3 b/bin/sugar-activity3
+new file mode 100755
+index 000000000..6798bc328
+--- /dev/null
++++ b/bin/sugar-activity3
+@@ -0,0 +1,5 @@
++#!/usr/bin/env python3
++
++from sugar3.activity import activityinstance
++
++activityinstance.main()
+diff --git a/configure.ac b/configure.ac
+index e0cfdc6d7..e28e4b310 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -15,7 +15,7 @@ GNOME_COMPILE_WARNINGS(maximum)
+ 
+ AC_PATH_PROG([GLIB_GENMARSHAL], [glib-genmarshal])
+ 
+-PYTHON=python2
++PYTHON=python3
+ AM_PATH_PYTHON
+ 
+ PKG_CHECK_MODULES(EXT, gtk+-3.0 gdk-3.0 gdk-pixbuf-2.0 sm ice alsa
+diff --git a/src/sugar3/activity/Makefile.am b/src/sugar3/activity/Makefile.am
+index ace612e96..e3cf05bcd 100644
+--- a/src/sugar3/activity/Makefile.am
++++ b/src/sugar3/activity/Makefile.am
+@@ -2,6 +2,7 @@ sugardir = $(pythondir)/sugar3/activity
+ sugar_PYTHON =              \
+ 	__init__.py             \
+ 	activity.py             \
++	activityinstance.py     \
+ 	activityfactory.py      \
+ 	activityhandle.py       \
+ 	activityservice.py      \
+diff --git a/src/sugar3/activity/activity.py b/src/sugar3/activity/activity.py
+index fdb7db01a..0fbd1de4f 100644
+--- a/src/sugar3/activity/activity.py
++++ b/src/sugar3/activity/activity.py
+@@ -158,6 +158,7 @@ class MySpecialToolbar(Gtk.Toolbar):
+ You may copy it and use it as a template.
+ '''
+ 
++import six
+ import gettext
+ import logging
+ import os
+@@ -165,7 +166,6 @@ class MySpecialToolbar(Gtk.Toolbar):
+ import time
+ from hashlib import sha1
+ from functools import partial
+-import StringIO
+ import cairo
+ import json
+ 
+@@ -206,7 +206,9 @@ class MySpecialToolbar(Gtk.Toolbar):
+ 
+ from gi.repository import SugarExt
+ 
+-_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg)
++
++def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg)
++
+ 
+ SCOPE_PRIVATE = 'private'
+ SCOPE_INVITE_ONLY = 'invite'  # shouldn't be shown in UI, it's implicit
+@@ -881,7 +883,7 @@ def get_preview(self):
+         cr.set_source_surface(screenshot_surface)
+         cr.paint()
+ 
+-        preview_str = StringIO.StringIO()
++        preview_str = six.BytesIO()
+         preview_surface.write_to_png(preview_str)
+         return preview_str.getvalue()
+ 
+@@ -919,7 +921,7 @@ def save(self):
+ 
+         buddies_dict = self._get_buddies()
+         if buddies_dict:
+-            self.metadata['buddies_id'] = json.dumps(buddies_dict.keys())
++            self.metadata['buddies_id'] = json.dumps(list(buddies_dict.keys()))
+             self.metadata['buddies'] = json.dumps(self._get_buddies())
+ 
+         # update spent time before saving
+@@ -1103,8 +1105,9 @@ def share(self, private=False):
+             raise RuntimeError('Activity %s already shared.' %
+                                self._activity_id)
+         verb = private and 'private' or 'public'
+-        logging.debug('Requesting %s share of activity %s.' % (verb,
+-                      self._activity_id))
++        logging.debug(
++            'Requesting %s share of activity %s.' %
++            (verb, self._activity_id))
+         pservice = presenceservice.get_instance()
+         pservice.connect('activity-shared', self.__share_cb)
+         pservice.share_activity(self, private=private)
+@@ -1197,8 +1200,8 @@ def __stop_dialog_response_cb(self, alert, response_id):
+         if response_id == Gtk.ResponseType.OK:
+             title = alert.entry.get_text()
+             if self._is_resumed and \
+-                title == self._original_title:
+-                    datastore.delete(self._jobject_old.get_object_id())
++                    title == self._original_title:
++                datastore.delete(self._jobject_old.get_object_id())
+             self._jobject.metadata['title'] = title
+             self._do_close(False)
+ 
+@@ -1222,7 +1225,7 @@ def _get_save_label_tip(self, title):
+         label = _('Save new')
+         tip = _('Save a new journal entry')
+         if self._is_resumed and \
+-            title == self._original_title:
++                title == self._original_title:
+             label = _('Save')
+             tip = _('Save into the old journal entry')
+ 
+@@ -1244,7 +1247,7 @@ def _prepare_close(self, skip_save=False):
+         if not skip_save:
+             try:
+                 self.save()
+-            except:
++            except BaseException:
+                 # pylint: disable=W0702
+                 logging.exception('Error saving activity object to datastore')
+                 self._show_keep_failed_dialog()
+@@ -1306,12 +1309,12 @@ def close(self, skip_save=False):
+ 
+     def __realize_cb(self, window):
+         display_name = Gdk.Display.get_default().get_name()
+-        if ':' in display_name: 
++        if ':' in display_name:
+             # X11 for sure; this only works in X11
+             xid = window.get_window().get_xid()
+             SugarExt.wm_set_bundle_id(xid, self.get_bundle_id())
+             SugarExt.wm_set_activity_id(xid, str(self._activity_id))
+-        elif display_name is 'Broadway': 
++        elif display_name is 'Broadway':
+             # GTK3's HTML5 backend
+             # This is needed so that the window takes the whole browser window
+             self.maximize()
+@@ -1432,7 +1435,7 @@ def __get_filters_cb(self):
+         }
+         filter_dict = dbus.Dictionary(filters, signature='sv')
+         logging.debug('__get_filters_cb %r' % dbus.Array([filter_dict],
+-                      signature='a{sv}'))
++                                                         signature='a{sv}'))
+         return dbus.Array([filter_dict], signature='a{sv}')
+ 
+     @dbus.service.method(dbus_interface=CLIENT_HANDLER,
+@@ -1448,9 +1451,10 @@ def HandleChannels(self, account, connection, channels, requests_satisfied,
+                 handle_type = properties[CHANNEL + '.TargetHandleType']
+                 if channel_type == CHANNEL_TYPE_TEXT:
+                     self._got_channel_cb(connection, object_path, handle_type)
+-        except Exception, e:
++        except Exception as e:
+             logging.exception(e)
+ 
++
+ _session = None
+ 
+ 
+@@ -1503,7 +1507,7 @@ def get_activity_root():
+         activity_root = env.get_profile_path(os.environ['SUGAR_BUNDLE_ID'])
+         try:
+             os.mkdir(activity_root)
+-        except OSError, e:
++        except OSError as e:
+             if e.errno != EEXIST:
+                 raise e
+         return activity_root
+diff --git a/src/sugar3/activity/activityfactory.py b/src/sugar3/activity/activityfactory.py
+index 31895a5d1..b46ecceb0 100644
+--- a/src/sugar3/activity/activityfactory.py
++++ b/src/sugar3/activity/activityfactory.py
+@@ -23,7 +23,6 @@
+ """
+ 
+ import logging
+-
+ import dbus
+ from gi.repository import GObject
+ from gi.repository import GLib
+@@ -53,7 +52,7 @@
+ 
+ 
+ def _close_fds():
+-    for i in xrange(3, MAXFD):
++    for i in range(3, MAXFD):
+         try:
+             os.close(i)
+         # pylint: disable=W0704
+@@ -69,7 +68,7 @@ def create_activity_id():
+ def _mkdir(path):
+     try:
+         os.mkdir(path)
+-    except OSError, e:
++    except OSError as e:
+         if e.errno != EEXIST:
+             raise e
+ 
+@@ -137,10 +136,10 @@ def open_log_file(activity):
+     while True:
+         path = env.get_logs_path('%s-%s.log' % (activity.get_bundle_id(), i))
+         try:
+-            fd = os.open(path, os.O_EXCL | os.O_CREAT | os.O_WRONLY, 0644)
+-            f = os.fdopen(fd, 'w', 0)
++            fd = os.open(path, os.O_EXCL | os.O_CREAT | os.O_WRONLY, 0o644)
++            f = os.fdopen(fd, 'w')
+             return (path, f)
+-        except OSError, e:
++        except OSError as e:
+             if e.errno == EEXIST:
+                 i += 1
+             elif e.errno == ENOSPC:
+@@ -225,7 +224,7 @@ def _create_activity(self):
+                               self._handle.object_id, self._handle.uri,
+                               self._handle.invited)
+ 
+-        dev_null = file('/dev/null', 'r')
++        dev_null = open('/dev/null', 'r')
+         child = subprocess.Popen([str(s) for s in command],
+                                  env=environ,
+                                  cwd=str(self._bundle.get_path()),
+diff --git a/src/sugar3/activity/activityhandle.py b/src/sugar3/activity/activityhandle.py
+index d7973a995..d989a8927 100644
+--- a/src/sugar3/activity/activityhandle.py
++++ b/src/sugar3/activity/activityhandle.py
+@@ -24,26 +24,26 @@
+ class ActivityHandle(object):
+     '''
+     Data structure storing simple activity metadata
+-    
++
+     Args:
+         activity_id (string): unique id for the activity to be
+         created
+-        
++
+         object_id (string): identity of the journal object
+         associated with the activity.
+-        
++
+         When you resume an activity from the journal
+         the object_id will be passed in. It is optional
+         since new activities does not have an
+         associated object.
+-        
++
+         uri (string): URI associated with the activity. Used when
+         opening an external file or resource in the
+         activity, rather than a journal object
+         (downloads stored on the file system for
+         example or web pages)
+-        
+-        invited (bool): True if the activity is being 
++
++        invited (bool): True if the activity is being
+         launched for handling an invite from the network
+     '''
+ 
+@@ -55,7 +55,7 @@ def __init__(self, activity_id=None, object_id=None, uri=None,
+         self.invited = invited
+ 
+     def get_dict(self):
+-        '''Returns activity settings as a dictionary in format 
++        '''Returns activity settings as a dictionary in format
+             {activity_id:XXXX, object_id:XXXX, uri:XXXX, invited:BOOL}'''
+         result = {'activity_id': self.activity_id, 'invited': self.invited}
+         if self.object_id:
+diff --git a/src/sugar3/activity/activityinstance.py b/src/sugar3/activity/activityinstance.py
+new file mode 100644
+index 000000000..b756f54f9
+--- /dev/null
++++ b/src/sugar3/activity/activityinstance.py
+@@ -0,0 +1,222 @@
++# Copyright (C) 2006-2008, Red Hat, Inc.
++#
++# 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; either version 2 of the License, or
++# (at your option) any later version.
++#
++# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
++
++import os
++import sys
++import six
++import logging
++
++# Change the default encoding to avoid UnicodeDecodeError
++# http://lists.sugarlabs.org/archive/sugar-devel/2012-August/038928.html
++if six.PY2:
++    reload(sys)
++    sys.setdefaultencoding('utf-8')
++
++import gettext
++from optparse import OptionParser
++
++import dbus
++import dbus.service
++from dbus.mainloop.glib import DBusGMainLoop
++DBusGMainLoop(set_as_default=True)
++
++from sugar3.activity import activityhandle
++from sugar3 import config
++from sugar3.bundle.activitybundle import ActivityBundle
++from sugar3 import logger
++
++from sugar3.bundle.bundle import MalformedBundleException
++
++from distutils.dir_util import mkpath
++import time
++import hashlib
++import random
++
++
++def create_activity_instance(constructor, handle):
++    activity = constructor(handle)
++    activity.show()
++    return activity
++
++
++def get_single_process_name(bundle_id):
++    return bundle_id
++
++
++def get_single_process_path(bundle_id):
++    return '/' + bundle_id.replace('.', '/')
++
++
++class SingleProcess(dbus.service.Object):
++
++    def __init__(self, name_service, constructor):
++        self.constructor = constructor
++
++        bus = dbus.SessionBus()
++        bus_name = dbus.service.BusName(name_service, bus=bus)
++        object_path = get_single_process_path(name_service)
++        dbus.service.Object.__init__(self, bus_name, object_path)
++
++    @dbus.service.method('org.laptop.SingleProcess', in_signature='a{sv}')
++    def create(self, handle_dict):
++        handle = activityhandle.create_from_dict(handle_dict)
++        create_activity_instance(self.constructor, handle)
++
++
++def main():
++    usage = 'usage: %prog [options] [activity dir] [python class]'
++    epilog = 'If you are running from a directory containing an Activity, ' \
++             'the argument may be omitted.  Otherwise please provide either '\
++             'a directory containing a Sugar Activity [activity dir], a '\
++             '[python_class], or both.'
++
++    parser = OptionParser(usage=usage, epilog=epilog)
++    parser.add_option('-b', '--bundle-id', dest='bundle_id',
++                      help='identifier of the activity bundle')
++    parser.add_option('-a', '--activity-id', dest='activity_id',
++                      help='identifier of the activity instance')
++    parser.add_option('-o', '--object-id', dest='object_id',
++                      help='identifier of the associated datastore object')
++    parser.add_option('-u', '--uri', dest='uri',
++                      help='URI to load')
++    parser.add_option('-s', '--single-process', dest='single_process',
++                      action='store_true',
++                      help='start all the instances in the same process')
++    parser.add_option('-i', '--invited', dest='invited',
++                      action='store_true', default=False,
++                      help='the activity is being launched for handling an '
++                           'invite from the network')
++    (options, args) = parser.parse_args()
++
++    logger.start()
++
++    activity_class = None
++    if len(args) == 2:
++        activity_class = args[1]
++        os.chdir(args[0])
++    elif len(args) == 1:
++        if os.path.isdir(args[0]):
++            os.chdir(args[0])
++        else:
++            activity_class = args[0]
++
++    os.environ['SUGAR_BUNDLE_PATH'] = os.path.abspath(os.curdir)
++
++    bundle_path = os.environ['SUGAR_BUNDLE_PATH']
++    sys.path.insert(0, bundle_path)
++
++    try:
++        bundle = ActivityBundle(bundle_path)
++    except MalformedBundleException:
++        parser.print_help()
++        exit(0)
++
++    if not activity_class:
++        command = bundle.get_command()
++        if command.startswith('sugar-activity'):
++            if not command.startswith('sugar-activity3'):
++                logging.warning("Activity written for Python 2, consider porting to Python 3.")
++            activity_class = command.split(" ")[1]
++
++    if 'SUGAR_VERSION' not in os.environ:
++        profile_id = os.environ.get('SUGAR_PROFILE', 'default')
++        home_dir = os.environ.get('SUGAR_HOME', os.path.expanduser('~/.sugar'))
++        base = os.path.join(home_dir, profile_id)
++        activity_root = os.path.join(base, bundle.get_bundle_id())
++
++        instance_dir = os.path.join(activity_root, 'instance')
++        mkpath(instance_dir)
++
++        data_dir = os.path.join(activity_root, 'data')
++        mkpath(data_dir)
++
++        tmp_dir = os.path.join(activity_root, 'tmp')
++        mkpath(tmp_dir)
++
++        os.environ['SUGAR_ACTIVITY_ROOT'] = activity_root
++        os.environ['SUGAR_BUNDLE_PATH'] = bundle.get_path()
++
++    os.environ['SUGAR_BUNDLE_ID'] = bundle.get_bundle_id()
++    os.environ['SUGAR_BUNDLE_NAME'] = bundle.get_name()
++    os.environ['SUGAR_BUNDLE_VERSION'] = str(bundle.get_activity_version())
++
++    # must be done early, some activities set translations globally, SL #3654
++    activity_locale_path = os.environ.get("SUGAR_LOCALEDIR",
++                                          config.locale_path)
++
++    gettext.bindtextdomain(bundle.get_bundle_id(), activity_locale_path)
++    gettext.bindtextdomain('sugar-toolkit-gtk3', config.locale_path)
++    gettext.textdomain(bundle.get_bundle_id())
++
++    splitted_module = activity_class.rsplit('.', 1)
++    module_name = splitted_module[0]
++    class_name = splitted_module[1]
++
++    module = __import__(module_name)
++    for comp in module_name.split('.')[1:]:
++        module = getattr(module, comp)
++
++    activity_constructor = getattr(module, class_name)
++
++    if not options.activity_id:
++        # Generate random hash
++        data = '%s%s' % (time.time(), random.randint(10000, 100000))
++        random_hash = hashlib.sha1(data.encode()).hexdigest()
++        options.activity_id = random_hash
++        options.bundle_id = bundle.get_bundle_id()
++
++    activity_handle = activityhandle.ActivityHandle(
++        activity_id=options.activity_id,
++        object_id=options.object_id, uri=options.uri,
++        invited=options.invited)
++
++    if options.single_process is True:
++        sessionbus = dbus.SessionBus()
++
++        service_name = get_single_process_name(options.bundle_id)
++        service_path = get_single_process_path(options.bundle_id)
++
++        bus_object = sessionbus.get_object(
++            'org.freedesktop.DBus', '/org/freedesktop/DBus')
++        try:
++            name = bus_object.GetNameOwner(
++                service_name, dbus_interface='org.freedesktop.DBus')
++        except dbus.DBusException:
++            name = None
++
++        if not name:
++            SingleProcess(service_name, activity_constructor)
++        else:
++            try:
++                single_process = sessionbus.get_object(service_name,
++                                                       service_path)
++                single_process.create(
++                    activity_handle.get_dict(),
++                    dbus_interface='org.laptop.SingleProcess')
++
++                print('Created %s in a single process.' % service_name)
++                sys.exit(0)
++            except (TypeError, dbus.DBusException):
++                print('Could not communicate with the instance process,'
++                      'launching a new process')
++
++    if hasattr(module, 'start'):
++        module.start()
++
++    instance = create_activity_instance(activity_constructor, activity_handle)
++
++    if hasattr(instance, 'run_main_loop'):
++        instance.run_main_loop()
+diff --git a/src/sugar3/activity/activityservice.py b/src/sugar3/activity/activityservice.py
+index 738e602e0..25329029e 100644
+--- a/src/sugar3/activity/activityservice.py
++++ b/src/sugar3/activity/activityservice.py
+@@ -79,5 +79,5 @@ def HandleViewSource(self):
+     def GetDocumentPath(self, async_cb, async_err_cb):
+         try:
+             self._activity.get_document_path(async_cb, async_err_cb)
+-        except Exception, e:
++        except Exception as e:
+             async_err_cb(e)
+diff --git a/src/sugar3/activity/bundlebuilder.py b/src/sugar3/activity/bundlebuilder.py
+index 403484456..f270346c2 100644
+--- a/src/sugar3/activity/bundlebuilder.py
++++ b/src/sugar3/activity/bundlebuilder.py
+@@ -40,12 +40,13 @@
+ import logging
+ from glob import glob
+ from fnmatch import fnmatch
+-from ConfigParser import ConfigParser
++from six.moves.configparser import ConfigParser
+ import xml.etree.cElementTree as ET
+-from HTMLParser import HTMLParser
++from six.moves.html_parser import HTMLParser
+ 
+ from sugar3 import env
+ from sugar3.bundle.activitybundle import ActivityBundle
++from six.moves import reduce
+ 
+ 
+ IGNORE_DIRS = ['dist', '.git', 'screenshots']
+@@ -150,7 +151,7 @@ def build_locale(self):
+             args = ['msgfmt', '--output-file=%s' % mo_file, file_name]
+             retcode = subprocess.call(args)
+             if retcode:
+-                print 'ERROR - msgfmt failed with return code %i.' % retcode
++                print('ERROR - msgfmt failed with return code %i.' % retcode)
+                 if self._no_fail:
+                     continue
+ 
+@@ -301,8 +302,8 @@ def install(self, prefix, install_mime=True, install_desktop_file=True):
+ 
+             source_to_dest[source_path] = dest_path
+ 
+-        for source, dest in source_to_dest.items():
+-            print 'Install %s to %s.' % (source, dest)
++        for source, dest in list(source_to_dest.items()):
++            print('Install %s to %s.' % (source, dest))
+ 
+             path = os.path.dirname(dest)
+             if not os.path.exists(path):
+@@ -429,7 +430,7 @@ def cmd_check(config, options):
+     if options.choice == 'integration':
+         run_unit_test = False
+ 
+-    print "Running Tests"
++    print("Running Tests")
+ 
+     test_path = os.path.join(config.source_dir, "tests")
+ 
+@@ -443,22 +444,22 @@ def cmd_check(config, options):
+             all_tests = unittest.defaultTestLoader.discover(unit_test_path)
+             unittest.TextTestRunner(verbosity=options.verbose).run(all_tests)
+         elif not run_unit_test:
+-            print "Not running unit tests"
++            print("Not running unit tests")
+         else:
+-            print 'No "unit" directory found.'
++            print('No "unit" directory found.')
+ 
+         if os.path.isdir(integration_test_path) and run_integration_test:
+             all_tests = unittest.defaultTestLoader.discover(
+                 integration_test_path)
+             unittest.TextTestRunner(verbosity=options.verbose).run(all_tests)
+         elif not run_integration_test:
+-            print "Not running integration tests"
++            print("Not running integration tests")
+         else:
+-            print 'No "integration" directory found.'
++            print('No "integration" directory found.')
+ 
+-        print "Finished testing"
++        print("Finished testing")
+     else:
+-        print "Error: No tests/ directory"
++        print("Error: No tests/ directory")
+ 
+ 
+ def cmd_dev(config, options):
+@@ -472,9 +473,9 @@ def cmd_dev(config, options):
+         os.symlink(config.source_dir, bundle_path)
+     except OSError:
+         if os.path.islink(bundle_path):
+-            print 'ERROR - The bundle has been already setup for development.'
++            print('ERROR - The bundle has been already setup for development.')
+         else:
+-            print 'ERROR - A bundle with the same name is already installed.'
++            print('ERROR - A bundle with the same name is already installed.')
+ 
+ 
+ def cmd_dist_xo(config, options):
+@@ -490,9 +491,9 @@ def cmd_dist_xo(config, options):
+ def cmd_fix_manifest(config, options):
+     '''Add missing files to the manifest (OBSOLETE)'''
+ 
+-    print 'WARNING: The fix_manifest command is obsolete.'
+-    print '         The MANIFEST file is no longer used in bundles,'
+-    print '         please remove it.'
++    print('WARNING: The fix_manifest command is obsolete.')
++    print('         The MANIFEST file is no longer used in bundles,')
++    print('         please remove it.')
+ 
+ 
+ def cmd_dist_source(config, options):
+@@ -506,7 +507,10 @@ def cmd_install(config, options):
+     """Install the activity in the system"""
+ 
+     installer = Installer(Builder(config))
+-    installer.install(options.prefix, options.install_mime, options.install_desktop_file)
++    installer.install(
++        options.prefix,
++        options.install_mime,
++        options.install_desktop_file)
+ 
+ 
+ def _po_escape(string):
+@@ -568,7 +572,7 @@ def cmd_genpot(config, options):
+     args += python_files
+     retcode = subprocess.call(args)
+     if retcode:
+-        print 'ERROR - xgettext failed with return code %i.' % retcode
++        print('ERROR - xgettext failed with return code %i.' % retcode)
+ 
+ 
+ def cmd_build(config, options):
+@@ -603,7 +607,7 @@ def start():
+                               choices=['unit', 'integration'],
+                               help="run unit/integration test")
+     check_parser.add_argument("--verbosity", "-v", dest="verbose",
+-                              type=int, choices=range(0, 3),
++                              type=int, choices=list(range(0, 3)),
+                               default=1, nargs='?',
+                               help="verbosity for the unit tests")
+ 
+diff --git a/src/sugar3/activity/webkit1.py b/src/sugar3/activity/webkit1.py
+index a2d015480..8ea7dade8 100644
+--- a/src/sugar3/activity/webkit1.py
++++ b/src/sugar3/activity/webkit1.py
+@@ -28,8 +28,8 @@
+ from gi.repository import WebKit
+ import socket
+ from threading import Thread
+-from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+-import SocketServer
++from six.moves.BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
++from six.moves import socketserver
+ import select
+ import errno
+ import mimetypes
+@@ -80,7 +80,7 @@ def serve_forever(self, poll_interval=0.5):
+             # shutdown request and wastes cpu at all other times.
+             try:
+                 r, w, e = select.select([self], [], [], poll_interval)
+-            except select.error, e:
++            except select.error as e:
+                 if e[0] == errno.EINTR:
+                     logging.debug("got eintr")
+                     continue
+@@ -92,7 +92,7 @@ def serve_forever(self, poll_interval=0.5):
+     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)
++        socketserver.TCPServer.server_bind(self)
+         _host, port = self.socket.getsockname()[:2]
+         self.server_name = 'localhost'
+         self.server_port = port
+diff --git a/src/sugar3/activity/widgets.py b/src/sugar3/activity/widgets.py
+index 06606e807..21e717287 100644
+--- a/src/sugar3/activity/widgets.py
++++ b/src/sugar3/activity/widgets.py
+@@ -34,7 +34,7 @@
+ from sugar3 import profile
+ 
+ 
+-_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg)
++def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg)
+ 
+ 
+ def _create_activity_icon(metadata):
+diff --git a/src/sugar3/bundle/__init__.py b/src/sugar3/bundle/__init__.py
+index 9b8f78f7c..ea06fb45b 100644
+--- a/src/sugar3/bundle/__init__.py
++++ b/src/sugar3/bundle/__init__.py
+@@ -44,7 +44,9 @@
+ * `icon` - the icon file for the activity, shown by Sugar in the list
+   of installed activities,
+ 
+-* `exec` - how to execute the activity, e.g. `sugar-activity module.Class`,
++* `exec` - how to execute the activity, e.g.
++  `sugar-activity3 module.Class` (For activities written for Python 3),
++  `sugar-activity module.Class` (For activities written for Python 2)
+ 
+ Optional metadata keys are;
+ 
+@@ -117,7 +119,7 @@
+     [Activity]
+     name = Browse
+     bundle_id = org.laptop.WebActivity
+-    exec = sugar-activity webactivity.WebActivity -s
++    exec = sugar-activity3 webactivity.WebActivity -s
+     activity_version = 200
+     icon = activity-web
+     max_participants = 100
+diff --git a/src/sugar3/bundle/activitybundle.py b/src/sugar3/bundle/activitybundle.py
+index c0d9d985f..879831e53 100644
+--- a/src/sugar3/bundle/activitybundle.py
++++ b/src/sugar3/bundle/activitybundle.py
+@@ -20,7 +20,7 @@
+ UNSTABLE.
+ """
+ 
+-from ConfigParser import ConfigParser, ParsingError
++from six.moves.configparser import ConfigParser, ParsingError
+ from locale import normalize
+ import os
+ import shutil
+@@ -441,7 +441,7 @@ def uninstall(self, force=False, delete_profile=False):
+         if delete_profile:
+             bundle_profile_path = env.get_profile_path(self._bundle_id)
+             if os.path.exists(bundle_profile_path):
+-                os.chmod(bundle_profile_path, 0775)
++                os.chmod(bundle_profile_path, 0o775)
+                 shutil.rmtree(bundle_profile_path, ignore_errors=True)
+ 
+         self._uninstall(install_path)
+diff --git a/src/sugar3/bundle/bundle.py b/src/sugar3/bundle/bundle.py
+index 59cd885ad..c5716107b 100644
+--- a/src/sugar3/bundle/bundle.py
++++ b/src/sugar3/bundle/bundle.py
+@@ -20,10 +20,10 @@
+ UNSTABLE.
+ """
+ 
++import six
+ import os
+ import logging
+ import shutil
+-import StringIO
+ import zipfile
+ 
+ 
+@@ -74,7 +74,7 @@ def __init__(self, path):
+         if not os.path.isdir(self._path):
+             try:
+                 self._zip_file = zipfile.ZipFile(self._path)
+-            except zipfile.error, exception:
++            except zipfile.error as exception:
+                 raise MalformedBundleException('Error accessing zip file %r: '
+                                                '%s' % (self._path, exception))
+             self._check_zip_bundle()
+@@ -115,7 +115,7 @@ def get_file(self, filename):
+         if self._zip_file is None:
+             path = os.path.join(self._path, filename)
+             try:
+-                f = open(path, 'rb')
++                f = open(path, 'r')
+             except IOError:
+                 logging.debug("cannot open path %s" % path)
+                 return None
+@@ -123,7 +123,7 @@ def get_file(self, filename):
+             path = os.path.join(self._zip_root_dir, filename)
+             try:
+                 data = self._zip_file.read(path)
+-                f = StringIO.StringIO(data)
++                f = six.StringIO(data)
+             except KeyError:
+                 logging.debug('%s not found in zip %s.' % (filename, path))
+                 return None
+@@ -171,7 +171,7 @@ def _unzip(self, install_dir):
+             raise AlreadyInstalledException
+ 
+         if not os.path.isdir(install_dir):
+-            os.mkdir(install_dir, 0775)
++            os.mkdir(install_dir, 0o775)
+ 
+         # zipfile provides API that in theory would let us do this
+         # correctly by hand, but handling all the oddities of
+diff --git a/src/sugar3/bundle/bundleversion.py b/src/sugar3/bundle/bundleversion.py
+index 91df406ae..7ad9e3e09 100644
+--- a/src/sugar3/bundle/bundleversion.py
++++ b/src/sugar3/bundle/bundleversion.py
+@@ -82,6 +82,7 @@ class NormalizedVersion(object):
+     Attributes:
+         parts (list): the numeric parts of the version after normalization.
+     """
++
+     def __init__(self, activity_version):
+         self._activity_version = activity_version
+         self.parts = []
+diff --git a/src/sugar3/bundle/contentbundle.py b/src/sugar3/bundle/contentbundle.py
+index 146e7f8c6..0e61ade08 100644
+--- a/src/sugar3/bundle/contentbundle.py
++++ b/src/sugar3/bundle/contentbundle.py
+@@ -21,10 +21,11 @@
+ UNSTABLE.
+ """
+ 
+-from ConfigParser import ConfigParser
++from six.moves import urllib
++from six.moves.configparser import ConfigParser
++
+ import tempfile
+ import os
+-import urllib
+ 
+ from sugar3 import env
+ from sugar3.bundle.bundle import Bundle, MalformedBundleException
+@@ -142,7 +143,7 @@ def get_icon(self):
+ 
+     def get_start_uri(self):
+         path = os.path.join(self.get_path(), self._activity_start)
+-        return 'file://' + urllib.pathname2url(path)
++        return 'file://' + urllib.request.pathname2url(path)
+ 
+     def get_bundle_id(self):
+         return self._global_name
+diff --git a/src/sugar3/datastore/datastore.py b/src/sugar3/datastore/datastore.py
+index f132f0c8b..e1b3f8b9a 100644
+--- a/src/sugar3/datastore/datastore.py
++++ b/src/sugar3/datastore/datastore.py
+@@ -20,6 +20,7 @@
+ STABLE
+ """
+ 
++import six
+ import logging
+ import time
+ from datetime import datetime
+@@ -69,6 +70,7 @@ def __datastore_updated_cb(object_id):
+ def __datastore_deleted_cb(object_id):
+     deleted.send(None, object_id=object_id)
+ 
++
+ created = dispatch.Signal()
+ deleted = dispatch.Signal()
+ updated = dispatch.Signal()
+@@ -85,6 +87,12 @@ def __init__(self, properties=None):
+         if not properties:
+             self._properties = {}
+         else:
++            if six.PY3:
++                for x, y in properties.items():
++                    try:
++                        properties[x] = y.decode()
++                    except BaseException:
++                        pass
+             self._properties = properties
+ 
+         default_keys = ['activity', 'activity_id',
+@@ -97,6 +105,11 @@ def __getitem__(self, key):
+         return self._properties[key]
+ 
+     def __setitem__(self, key, value):
++        if six.PY3:
++            try:
++                value = value.decode()
++            except BaseException:
++                pass
+         if key not in self._properties or self._properties[key] != value:
+             self._properties[key] = value
+             self.emit('updated')
+@@ -112,7 +125,7 @@ def has_key(self, key):
+         return key in self._properties
+ 
+     def keys(self):
+-        return self._properties.keys()
++        return list(self._properties.keys())
+ 
+     def get_dictionary(self):
+         return self._properties
+@@ -128,7 +141,7 @@ def get(self, key, default=None):
+ 
+     def update(self, properties):
+         """Update all of the metadata"""
+-        for (key, value) in properties.items():
++        for (key, value) in list(properties.items()):
+             self[key] = value
+ 
+ 
+diff --git a/src/sugar3/dispatch/dispatcher.py b/src/sugar3/dispatch/dispatcher.py
+index 89d219c12..4b437a3aa 100644
+--- a/src/sugar3/dispatch/dispatcher.py
++++ b/src/sugar3/dispatch/dispatcher.py
+@@ -1,4 +1,6 @@
+ import weakref
++import six
++
+ try:
+     set
+ except NameError:
+@@ -11,7 +13,7 @@
+ 
+ def _make_id(target):
+     if hasattr(target, 'im_func'):
+-        return (id(target.im_self), id(target.im_func))
++        return (id(im_self(target)), id(im_func(target)))
+     return id(target)
+ 
+ 
+@@ -159,7 +161,7 @@ def send_robust(self, sender, **named):
+         for receiver in self._live_receivers(_make_id(sender)):
+             try:
+                 response = receiver(signal=self, sender=sender, **named)
+-            except Exception, err:
++            except Exception as err:
+                 responses.append((receiver, err))
+             else:
+                 responses.append((receiver, response))
+@@ -195,3 +197,17 @@ def _remove_receiver(self, receiver):
+             for idx, (r_key, _) in enumerate(self.receivers):
+                 if r_key == key:
+                     del self.receivers[idx]
++
++
++def im_self(func):
++    if six.PY2:
++        return func.im_self
++    elif six.PY3:
++        return func.__self__
++
++
++def im_func(func):
++    if six.PY2:
++        return func.im_func
++    elif six.PY3:
++        return func.__func__
+diff --git a/src/sugar3/dispatch/saferef.py b/src/sugar3/dispatch/saferef.py
+index df899cddb..581703c21 100644
+--- a/src/sugar3/dispatch/saferef.py
++++ b/src/sugar3/dispatch/saferef.py
+@@ -5,6 +5,7 @@
+ aren't handled by the core weakref module).
+ """
+ 
++import six
+ import weakref
+ import traceback
+ 
+@@ -21,7 +22,7 @@ def safeRef(target, onDelete=None):
+         weakref or a BoundMethodWeakref) as argument.
+     """
+     if hasattr(target, 'im_self'):
+-        if target.im_self is not None:
++        if im_self(target) is not None:
+             # Turn a bound method into a BoundMethodWeakref instance.
+             # Keep track of these instances for lookup by disconnect().
+             assert hasattr(target, 'im_func'), \
+@@ -123,18 +124,18 @@ def remove(weak, self=self):
+                 try:
+                     if callable(function):
+                         function(self)
+-                except Exception, e:
++                except Exception as e:
+                     try:
+                         traceback.print_exc()
+                     except AttributeError:
+-                        print "Exception during saferef %s cleanup "
+-                        "function %s: %s" % (self, function, e)
++                        print("Exception during saferef %s cleanup "
++                              "function %s: %s" % (self, function, e))
+         self.deletionMethods = [onDelete]
+         self.key = self.calculateKey(target)
+-        self.weakSelf = weakref.ref(target.im_self, remove)
+-        self.weakFunc = weakref.ref(target.im_func, remove)
+-        self.selfName = str(target.im_self)
+-        self.funcName = str(target.im_func.__name__)
++        self.weakSelf = weakref.ref(im_self(target), remove)
++        self.weakFunc = weakref.ref(im_func(target), remove)
++        self.selfName = str(im_self(target))
++        self.funcName = str(im_func(target).__name__)
+ 
+     def calculateKey(cls, target):
+         """Calculate the reference key for this reference
+@@ -142,7 +143,7 @@ def calculateKey(cls, target):
+         Currently this is a two-tuple of the id()'s of the
+         target object and the target function respectively.
+         """
+-        return (id(target.im_self), id(target.im_func))
++        return (id(im_self(target)), id(im_func(target)))
+     calculateKey = classmethod(calculateKey)
+ 
+     def __str__(self):
+@@ -155,15 +156,19 @@ def __str__(self):
+ 
+     __repr__ = __str__
+ 
+-    def __nonzero__(self):
++    def __bool__(self):
+         """Whether we are still a valid reference"""
+         return self() is not None
+ 
++    def __nonzero__(self):
++        """Python2 alternative for __bool__"""
++        return self() is not None
++
+     def __cmp__(self, other):
+         """Compare with another reference"""
+         if not isinstance(other, self.__class__):
+-            return cmp(self.__class__, type(other))
+-        return cmp(self.key, other.key)
++            return ((self.__class__ > type(other)) - (self.__class__ < type(other)))
++        return ((self.key > other.key) - (self.key < other.key))
+ 
+     def __call__(self):
+         """Return a strong reference to the bound method
+@@ -201,6 +206,7 @@ def foo(self): return "foo"
+     aren't descriptors (such as Jython) this implementation has the advantage
+     of working in the most cases.
+     """
++
+     def __init__(self, target, onDelete=None):
+         """Return a weak-reference-like instance for a bound method
+ 
+@@ -215,9 +221,9 @@ def __init__(self, target, onDelete=None):
+             collected).  Should take a single argument,
+             which will be passed a pointer to this object.
+         """
+-        assert getattr(target.im_self, target.__name__) == target, \
++        assert getattr(im_self(target), target.__name__) == target, \
+             ("method %s isn't available as the attribute %s of %s" %
+-                (target, target.__name__, target.im_self))
++                (target, target.__name__, im_self(target)))
+         super(BoundNonDescriptorMethodWeakref, self).__init__(target, onDelete)
+ 
+     def __call__(self):
+@@ -255,3 +261,17 @@ def get_bound_method_weakref(target, onDelete):
+         # no luck, use the alternative implementation:
+         return BoundNonDescriptorMethodWeakref(target=target,
+                                                onDelete=onDelete)
++
++
++def im_self(func):
++    if six.PY2:
++        return func.im_self
++    elif six.PY3:
++        return func.__self__
++
++
++def im_func(func):
++    if six.PY2:
++        return func.im_func
++    elif six.PY3:
++        return func.__func__
+diff --git a/src/sugar3/env.py b/src/sugar3/env.py
+index a18d5c93f..bbb1a9a18 100644
+--- a/src/sugar3/env.py
++++ b/src/sugar3/env.py
+@@ -36,9 +36,9 @@ def get_profile_path(path=None):
+     base = os.path.join(home_dir, profile_id)
+     if not os.path.isdir(base):
+         try:
+-            os.makedirs(base, 0770)
++            os.makedirs(base, 0o770)
+         except OSError:
+-            print 'Could not create user directory.'
++            print('Could not create user directory.')
+ 
+     if path is not None:
+         return os.path.join(base, path)
+diff --git a/src/sugar3/graphics/Makefile.am b/src/sugar3/graphics/Makefile.am
+index e5fdf09a1..0621750ef 100644
+--- a/src/sugar3/graphics/Makefile.am
++++ b/src/sugar3/graphics/Makefile.am
+@@ -16,7 +16,6 @@ sugar_PYTHON =              \
+ 	palettemenu.py          \
+ 	palettewindow.py        \
+ 	panel.py                \
+-	popwindow.py            \
+ 	radiopalette.py         \
+ 	radiotoolbutton.py      \
+ 	scrollingdetector.py    \
+diff --git a/src/sugar3/graphics/alert.py b/src/sugar3/graphics/alert.py
+index d262a209e..66cefd9df 100644
+--- a/src/sugar3/graphics/alert.py
++++ b/src/sugar3/graphics/alert.py
+@@ -61,7 +61,7 @@
+ from sugar3.graphics.icon import Icon
+ 
+ 
+-_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg)
++def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg)
+ 
+ 
+ if not hasattr(GObject.ParamFlags, 'READWRITE'):
+@@ -258,6 +258,8 @@ def _response(self, response_id):
+ 
+     def __button_clicked_cb(self, button, response_id):
+         self._response(response_id)
++
++
+ if hasattr(Alert, 'set_css_name'):
+     Alert.set_css_name('alert')
+ 
+@@ -294,9 +296,9 @@ def _alert_response_cb(self, alert, response_id):
+ 
+             # Check the response identifier.
+             if response_id is Gtk.ResponseType.OK:
+-                print 'Ok Button was clicked.'
++                print('Ok Button was clicked.')
+             elif response_id is Gtk.ResponseType.CANCEL:
+-                print 'Cancel Button was clicked.'
++                print('Cancel Button was clicked.')
+     """
+ 
+     def __init__(self, **kwargs):
+@@ -341,7 +343,7 @@ def _alert_response_cb(self, alert, response_id):
+ 
+             # Check the response identifier.
+             if response_id is Gtk.ResponseType.OK:
+-                print 'Ok Button was clicked.'
++                print('Ok Button was clicked.')
+     """
+ 
+     def __init__(self, **kwargs):
+@@ -390,6 +392,8 @@ def _draw(self, context):
+ 
+     def set_text(self, text):
+         self._text.set_markup('<b>%s</b>' % GLib.markup_escape_text(str(text)))
++
++
+ if hasattr(_TimeoutIcon, 'set_css_name'):
+     _TimeoutIcon.set_css_name('timeouticon')
+ 
+@@ -458,11 +462,11 @@ def __alert_response_cb(self, alert, response_id):
+ 
+             # Check the response identifier.
+             if response_id is Gtk.ResponseType.OK:
+-                print 'Continue Button was clicked.'
++                print('Continue Button was clicked.')
+             elif response_id is Gtk.ResponseType.CANCEL:
+-                print 'Cancel Button was clicked.'
++                print('Cancel Button was clicked.')
+             elif response_id == -1:
+-                print 'Timeout occurred'
++                print('Timeout occurred')
+     """
+ 
+     def __init__(self, timeout=5, **kwargs):
+@@ -508,9 +512,9 @@ def __alert_response_cb(self, alert, response_id):
+ 
+             # Check the response identifier.
+             if response_id is Gtk.ResponseType.OK:
+-                print 'Ok Button was clicked.'
++                print('Ok Button was clicked.')
+             elif response_id == -1:
+-                print 'Timeout occurred'
++                print('Timeout occurred')
+     """
+ 
+     def __init__(self, timeout=5, **kwargs):
+diff --git a/src/sugar3/graphics/colorbutton.py b/src/sugar3/graphics/colorbutton.py
+index 2ca6851c2..10c188ef6 100644
+--- a/src/sugar3/graphics/colorbutton.py
++++ b/src/sugar3/graphics/colorbutton.py
+@@ -29,7 +29,7 @@
+ from sugar3.graphics.palette import Palette, ToolInvoker, WidgetInvoker
+ 
+ 
+-_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg)
++def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg)
+ 
+ 
+ if not hasattr(GObject.ParamFlags, 'READWRITE'):
+@@ -38,8 +38,8 @@
+ 
+ 
+ def get_svg_color_string(color):
+-    return '#%.2X%.2X%.2X' % (color.red / 257, color.green / 257,
+-                              color.blue / 257)
++    return '#%.2X%.2X%.2X' % (color.red // 257, color.green // 257,
++                              color.blue // 257)
+ 
+ 
+ class _ColorButton(Gtk.Button):
+@@ -123,8 +123,8 @@ def _get_fg_style_color_str(self):
+         context = self.get_style_context()
+         fg_color = context.get_color(Gtk.StateType.NORMAL)
+         # the color components are stored as float values between 0.0 and 1.0
+-        return '#%.2X%.2X%.2X' % (fg_color.red * 255, fg_color.green * 255,
+-                                  fg_color.blue * 255)
++        return '#%.2X%.2X%.2X' % (int(fg_color.red * 255), int(fg_color.green * 255),
++                                  int(fg_color.blue * 255))
+ 
+     def set_color(self, color):
+         assert isinstance(color, Gdk.Color)
+@@ -149,11 +149,11 @@ def set_icon_name(self, icon_name):
+         '''
+         Sets the icon for the tool button from a named themed icon.
+         If it is none then no icon will be shown.
+-        
++
+         Args:
+             icon_name(string): The name for a themed icon.
+             It can be set as 'None' too.
+-        
++
+         Example:
+             set_icon_name('view-radial')
+         '''
+@@ -169,11 +169,11 @@ def get_icon_name(self):
+     icon_name = GObject.Property(type=str,
+                                  getter=get_icon_name, setter=set_icon_name)
+ 
+-    def set_icon_size(self, icon_size):
+-        self._preview.props.icon_size = icon_size
++    def set_icon_size(self, pixel_size):
++        self._preview.props.pixel_size = pixel_size
+ 
+     def get_icon_size(self):
+-        return self._preview.props.icon_size
++        return self._preview.props.pixel_size
+ 
+     icon_size = GObject.Property(type=int,
+                                  getter=get_icon_size, setter=set_icon_size)
+@@ -496,13 +496,13 @@ def __button_can_activate_accel_cb(self, button, signal_id):
+     def set_accelerator(self, accelerator):
+         '''
+         Sets keyboard shortcut that activates this button.
+-        
++
+         Args:
+             accelerator(string): accelerator to be set. Should be in
+             form <modifier>Letter
+             Find about format here :
+             https://developer.gnome.org/gtk3/stable/gtk3-Keyboard-Accelerators.html#gtk-accelerator-parse
+-        
++
+         Example:
+             set_accelerator(self, 'accel')
+         '''
+@@ -523,21 +523,21 @@ def create_palette(self):
+         The create_palette function is called when the palette needs to be
+         invoked.  For example, when the user has right clicked the icon or
+         the user has hovered over the icon for a long time.
+-        
++
+         The create_palette will only be called once or zero times.  The palette
+         returned will be stored and re-used if the user invokes the palette
+         multiple times.
+-        
++
+         Your create_palette implementation does not need to
+         :any:`Gtk.Widget.show` the palette, as this will be done by the
+         invoker.  However, you still need to show
+         the menu items, etc that you place in the palette.
+-        
++
+         Returns:
+-            
++
+             sugar3.graphics.palette.Palette, or None to indicate that you
+             do not want a palette shown
+-        
++
+         The default implementation returns None, to indicate no palette should
+         be shown.
+         '''
+@@ -595,11 +595,11 @@ def set_icon_name(self, icon_name):
+         '''
+         Sets the icon for the tool button from a named themed icon.
+         If it is none then no icon will be shown.
+-        
++
+         Args:
+             icon_name(string): The name for a themed icon.
+             It can be set as 'None' too.
+-        
++
+         Example:
+             set_icon_name('view-radial')
+         '''
+@@ -632,8 +632,8 @@ def get_icon_size(self):
+ 
+     def set_title(self, title):
+         '''
+-        The set_title() method sets the "title" property to the value of 
+-        title. The "title" property contains the string that is used to 
++        The set_title() method sets the "title" property to the value of
++        title. The "title" property contains the string that is used to
+         set the colorbutton title.
+         '''
+         self.get_child().props.title = title
+diff --git a/src/sugar3/graphics/icon.py b/src/sugar3/graphics/icon.py
+index 0666dc158..2343aee36 100644
+--- a/src/sugar3/graphics/icon.py
++++ b/src/sugar3/graphics/icon.py
+@@ -87,11 +87,13 @@
+ and 85.0% on the Y axis.
+ '''
+ 
++import six
+ import re
+ import math
+ import logging
+ import os
+-from ConfigParser import ConfigParser
++
++from six.moves.configparser import ConfigParser
+ 
+ import gi
+ gi.require_version('Rsvg', '2.0')
+@@ -127,8 +129,8 @@ def load(self, file_name, entities, cache):
+             if cache:
+                 self._cache[file_name] = icon
+ 
+-        for entity, value in entities.items():
+-            if isinstance(value, basestring):
++        for entity, value in list(entities.items()):
++            if isinstance(value, six.string_types):
+                 xml = '<!ENTITY %s "%s">' % (entity, value)
+                 icon = re.sub('<!ENTITY %s .*>' % entity, xml, icon)
+             else:
+@@ -207,7 +209,7 @@ def _get_attach_points(self, info, size_request):
+             # try read from the .icon file
+             icon_filename = info.get_filename().replace('.svg', '.icon')
+             if icon_filename != info.get_filename() and \
+-                os.path.exists(icon_filename):
++                            os.path.exists(icon_filename):
+ 
+                 try:
+                     with open(icon_filename) as config_file:
+@@ -470,7 +472,6 @@ class Icon(Gtk.Image):
+ 
+     __gtype_name__ = 'SugarIcon'
+ 
+-    # FIXME: deprecate icon_size
+     _MENU_SIZES = (Gtk.IconSize.MENU, Gtk.IconSize.DND,
+                    Gtk.IconSize.SMALL_TOOLBAR, Gtk.IconSize.BUTTON)
+ 
+@@ -483,7 +484,6 @@ def __init__(self, **kwargs):
+         self._alpha = 1.0
+         self._scale = 1.0
+ 
+-        # FIXME: deprecate icon_size
+         if 'icon_size' in kwargs:
+             logging.warning("icon_size is deprecated. Use pixel_size instead.")
+ 
+@@ -532,7 +532,6 @@ def _sync_image_properties(self):
+         if self._buffer.file_name != self.props.file:
+             self._buffer.file_name = self.props.file
+ 
+-        # FIXME: deprecate icon_size
+         pixel_size = None
+         if self.props.pixel_size == -1:
+             if self.props.icon_size in self._MENU_SIZES:
+@@ -549,7 +548,7 @@ def _sync_image_properties(self):
+             self._buffer.height = height
+ 
+     def _icon_size_changed_cb(self, image, pspec):
+-        self._buffer.icon_size = self.props.icon_size
++        self._buffer.icon_size = self.props.pixel_size
+ 
+     def _icon_name_changed_cb(self, image, pspec):
+         self._buffer.icon_name = self.props.icon_name
+@@ -805,7 +804,7 @@ def __init__(self, **kwargs):
+         # for example, after a touch palette invocation
+         self.connect_after('button-release-event',
+                            self.__button_release_event_cb)
+-        for key, value in kwargs.iteritems():
++        for key, value in six.iteritems(kwargs):
+             self.set_property(key, value)
+ 
+         from sugar3.graphics.palette import CursorInvoker
+@@ -1152,6 +1151,8 @@ def __palette_popup_cb(self, palette):
+ 
+     def __palette_popdown_cb(self, palette):
+         self.unset_state_flags(Gtk.StateFlags.PRELIGHT)
++
++
+ if hasattr(CanvasIcon, 'set_css_name'):
+     CanvasIcon.set_css_name('canvasicon')
+ 
+@@ -1447,6 +1448,6 @@ def get_surface(**kwargs):
+         cairo surface or None if image was not found
+     '''
+     icon = _IconBuffer()
+-    for key, value in kwargs.items():
++    for key, value in list(kwargs.items()):
+         icon.__setattr__(key, value)
+     return icon.get_surface()
+diff --git a/src/sugar3/graphics/iconentry.py b/src/sugar3/graphics/iconentry.py
+index 243807fa5..bc4746168 100644
+--- a/src/sugar3/graphics/iconentry.py
++++ b/src/sugar3/graphics/iconentry.py
+@@ -61,7 +61,7 @@ def set_icon_from_name(self, position, name):
+         self.set_icon(position, pixbuf)
+ 
+     def set_icon(self, position, pixbuf):
+-        if type(pixbuf) is not GdkPixbuf.Pixbuf:
++        if not isinstance(pixbuf, GdkPixbuf.Pixbuf):
+             raise ValueError('Argument must be a pixbuf, not %r.' % pixbuf)
+         self.set_icon_from_pixbuf(position, pixbuf)
+ 
+diff --git a/src/sugar3/graphics/objectchooser.py b/src/sugar3/graphics/objectchooser.py
+index e7fad8853..7cbe860f2 100644
+--- a/src/sugar3/graphics/objectchooser.py
++++ b/src/sugar3/graphics/objectchooser.py
+@@ -19,8 +19,8 @@
+ STABLE.
+ """
+ 
++import six
+ import logging
+-import StringIO
+ import cairo
+ 
+ from gi.repository import GObject
+@@ -59,13 +59,13 @@ def get_preview_pixbuf(preview_data, width=-1, height=-1):
+         None, if it could not be created
+ 
+     Example:
+-        pixbuf = get_preview_pixbuf(metadata.get('preview', ''))                
+-        has_preview = pixbuf is not None                                        
+-                                                                               
+-        if has_preview:                                                        
+-            im = Gtk.Image()                                                    
+-            im.set_from_pixbuf(pixbuf)                                          
+-            box.add(im)                                                        
++        pixbuf = get_preview_pixbuf(metadata.get('preview', ''))
++        has_preview = pixbuf is not None
++
++        if has_preview:
++            im = Gtk.Image()
++            im.set_from_pixbuf(pixbuf)
++            box.add(im)
+             im.show()
+     """
+     if width == -1:
+@@ -82,7 +82,7 @@ def get_preview_pixbuf(preview_data, width=-1, height=-1):
+             import base64
+             preview_data = base64.b64decode(preview_data)
+ 
+-        png_file = StringIO.StringIO(preview_data)
++        png_file = six.StringIO(preview_data)
+         try:
+             # Load image and scale to dimensions
+             surface = cairo.ImageSurface.create_from_png(png_file)
+@@ -126,7 +126,7 @@ class ObjectChooser(object):
+         what_filter (str): an activity bundle_id or a generic mime type as
+             defined in :mod:`sugar3.mime` used to determine which objects
+             will be presented in the object chooser
+-        
++
+         filter_type (str): should be one of [None, FILTER_TYPE_GENERIC_MIME,
+             FILTER_TYPE_ACTIVITY, FILTER_TYPE_MIME_BY_ACTIVITY]
+ 
+@@ -155,7 +155,7 @@ class ObjectChooser(object):
+ 
+     Examples:
+         chooser = ObjectChooser(self._activity, what_filter='Image')
+-        
++
+         chooser = ObjectChooser(parent=self,
+                                 what_filter=self.get_bundle_id(),
+                                 filter_type=FILTER_TYPE_ACTIVITY)
+@@ -192,7 +192,7 @@ def __init__(self, parent=None, what_filter=None, filter_type=None,
+     def run(self):
+         """
+         Runs the object chooser and displays it.
+-        
++
+         Returns:
+             Gtk.ResponseType constant, the response received from displaying the
+                 object chooser.
+diff --git a/src/sugar3/graphics/palettegroup.py b/src/sugar3/graphics/palettegroup.py
+index 98539feb2..9940e355b 100644
+--- a/src/sugar3/graphics/palettegroup.py
++++ b/src/sugar3/graphics/palettegroup.py
+@@ -36,7 +36,7 @@ def get_group(group_id):
+ 
+ 
+ def popdown_all():
+-    for group in _groups.values():
++    for group in list(_groups.values()):
+         group.popdown()
+ 
+ 
+diff --git a/src/sugar3/graphics/palettemenu.py b/src/sugar3/graphics/palettemenu.py
+index 231280716..ddc871164 100644
+--- a/src/sugar3/graphics/palettemenu.py
++++ b/src/sugar3/graphics/palettemenu.py
+@@ -56,7 +56,7 @@ def __init__(self):
+                 menu_item.show()
+ 
+             def __edit_cb(self, menu_item):
+-                print 'Edit...'
++                print('Edit...')
+ 
+         # Usually the Palette instance is returned in a create_palette function
+         p = ItemPalette()
+diff --git a/src/sugar3/graphics/palettewindow.py b/src/sugar3/graphics/palettewindow.py
+index 3253fd00a..1cadbb677 100644
+--- a/src/sugar3/graphics/palettewindow.py
++++ b/src/sugar3/graphics/palettewindow.py
+@@ -43,6 +43,7 @@
+ 
+ _pointer = None
+ 
++
+ def _get_pointer_position(widget):
+     global _pointer
+ 
+@@ -53,6 +54,7 @@ def _get_pointer_position(widget):
+     screen, x, y = _pointer.get_position()
+     return (x, y)
+ 
++
+ def _calculate_gap(a, b):
+     """Helper function to find the gap position and size of widget a"""
+     # Test for each side if the palette and invoker are
+@@ -420,6 +422,8 @@ def popdown(self):
+         self.disconnect_by_func(self.__enter_notify_event_cb)
+         self.disconnect_by_func(self.__leave_notify_event_cb)
+         self.hide()
++
++
+ if hasattr(_PaletteWindowWidget, 'set_css_name'):
+     _PaletteWindowWidget.set_css_name('palette')
+ 
+@@ -954,7 +958,7 @@ def get_alignment(self, palette_dim):
+             dright = screen_area.x + screen_area.width - rect.x - rect.width
+ 
+             ih = 0
+-            
++
+             if palette_dim.width == 0:
+                 ph = 0
+ 
+@@ -1315,7 +1319,7 @@ def attach(self, parent):
+         self._leave_hid = self._item.connect('leave-notify-event',
+                                              self.__leave_notify_event_cb)
+         self._release_hid = self._item.connect('button-release-event',
+-                                               self.__button_release_event_cb)
++                                             self.__button_release_event_cb)
+         self._long_pressed_hid = self._long_pressed_controller.connect(
+             'pressed',
+             self.__long_pressed_event_cb, self._item)
+@@ -1325,9 +1329,9 @@ def attach(self, parent):
+ 
+     def detach(self):
+         Invoker.detach(self)
+-        self._item.disconnect(self._enter_hid)
+-        self._item.disconnect(self._leave_hid)
+-        self._item.disconnect(self._release_hid)
++        self._item.disconnect_by_func(self.__enter_notify_event_cb)
++        self._item.disconnect_by_func(self.__leave_notify_event_cb)
++        self._item.disconnect_by_func(self.__button_release_event_cb)
+         self._long_pressed_controller.detach(self._item)
+         self._long_pressed_controller.disconnect(self._long_pressed_hid)
+ 
+diff --git a/src/sugar3/graphics/popwindow.py b/src/sugar3/graphics/popwindow.py
+deleted file mode 100644
+index 5a4a4d9ea..000000000
+--- a/src/sugar3/graphics/popwindow.py
++++ /dev/null
+@@ -1,202 +0,0 @@
+-# Copyright (C) 2016 Abhijit Patel
+-#
+-# 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; either version 2 of the License, or
+-# (at your option) any later version.
+-#
+-# 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+-
+-'''
+-Provide a PopWindow class for pop-up windows.
+-Making PopWindow containing Gtk.Toolbar which also contains Gtk.Label
+-and Toolbutton at the end of the Gtk.Toolbar.
+-
+-It is possible to change props like size and add more widgets PopWindow
+-and also to Gtk.Toolbar.
+-
+-Example:
+-    .. literalinclude: ..sugar/src/jarabe/view/viewsource.py
+-    .. literalinclude: ..sugar/src/jarabe/view/viewhelp.py
+-'''
+-from gettext import gettext as _
+-from gi.repository import Gtk
+-from gi.repository import Gdk
+-from gi.repository import GdkX11
+-from gi.repository import GObject
+-
+-from sugar3.graphics import style
+-from sugar3.graphics.toolbutton import ToolButton
+-
+-from jarabe.model import shell
+-
+-
+-class PopWindow(Gtk.Window):
+-    """
+-    UI interface for activity Pop-up Windows.
+-    PopWindows are the windows that open on the top of the current window.
+-    These pop-up windows don't cover the whole screen.
+-    They contain canvas content, alerts messages, a tray and a
+-    toolbar.
+-
+-    FULLSCREEN and HALF_WIDTH for setting size of the window.
+-
+-    Kwargs:
+-        size (int,int): size to be set of the window
+-        window_xid (xlib.Window): xid of the parent window
+-    """
+-    FULLSCREEN = (Gdk.Screen.width() - style.GRID_CELL_SIZE * 3,
+-                  Gdk.Screen.height() - style.GRID_CELL_SIZE * 2)
+-
+-    HALF_WIDTH = ((Gdk.Screen.height() - style.GRID_CELL_SIZE * 3)/2,
+-                  (Gdk.Screen.height() - style.GRID_CELL_SIZE * 2))
+-
+-    def __init__(self, window_xid=None, **kwargs):
+-        Gtk.Window.__init__(self, **kwargs)
+-        self._parent_window_xid = window_xid
+-
+-        self.set_decorated(False)
+-        self.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
+-        self.set_border_width(style.LINE_WIDTH)
+-        self.set_has_resize_grip(False)
+-        self.props.size = self.FULLSCREEN
+-
+-        self.connect('realize', self.__realize_cb)
+-        self.connect('key-press-event', self.__key_press_event_cb)
+-        self.connect('hide', self.__hide_cb)
+-
+-        self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+-        self.add(self._vbox)
+-        self._vbox.show()
+-
+-        self._title_box = TitleBox()
+-        self._title_box.close_button.connect(
+-            'clicked',
+-            self.__close_button_clicked_cb)
+-        self._title_box.set_size_request(-1, style.GRID_CELL_SIZE)
+-
+-        self._vbox.pack_start(self._title_box, False, True, 0)
+-        self._title_box.show()
+-
+-        # Note:
+-        # Not displaying the pop-up from here instead allowing
+-        # the child class to display the window after modifications
+-        # like chaninging window size, decorating, changing position.
+-
+-    def set_size(self, size):
+-        width, height = size
+-        self.set_size_request(width, height)
+-
+-    size = GObject.Property(type=None, setter=set_size)
+-
+-    def get_title_box(self):
+-        '''
+-        Getter method for title-box
+-
+-        Returns:
+-            self._title_box (): Title or Tool Box
+-        '''
+-        return self._title_box
+-
+-    title_box = GObject.Property(type=str, getter=get_title_box)
+-
+-    def get_vbox(self):
+-        '''
+-        Getter method for canvas
+-
+-        Returns:
+-            self._vbox (Gtk.Box): canvas
+-        '''
+-        return self._vbox
+-    vbox = GObject.Property(type=str, getter=get_vbox)
+-
+-    def __close_button_clicked_cb(self, button):
+-        self.destroy()
+-
+-    def __key_press_event_cb(self, window, event):
+-        keyname = Gdk.keyval_name(event.keyval)
+-        if keyname == 'Escape':
+-            self.destroy()
+-
+-    def __realize_cb(self, widget):
+-        self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
+-        window = self.get_window()
+-        window.set_accept_focus(True)
+-
+-        if self._parent_window_xid is not None:
+-            display = Gdk.Display.get_default()
+-            parent = GdkX11.X11Window.foreign_new_for_display(
+-                display, self._parent_window_xid)
+-            window.set_transient_for(parent)
+-            shell.get_model().push_modal()
+-
+-    def __hide_cb(self, widget):
+-        shell.get_model().pop_modal()
+-
+-    def add_view(self, widget, expand=True, fill=True, padding=0):
+-        '''
+-        Adds child to the vbox.
+-
+-        Args:
+-            widget (Gtk.Widget): widget to be added
+-
+-            expand (bool): True if child is to be given extra space allocated
+-                to vbox.
+-
+-            fill (bool): True if space given to child by the expand option is
+-                actually allocated to child, rather than just padding it.
+-
+-            padding (int): extra space in pixels to put between child and its
+-                neighbors, over and above the global amount specified
+-                by spacing in vbox.
+-
+-        Returns:
+-            None
+-        '''
+-        self._vbox.pack_start(widget, expand, fill, padding)
+-
+-
+-class TitleBox(Gtk.Toolbar):
+-    '''
+-    Title box at the top of the pop-up window.
+-    Title and close button are added to the box and as needed more widgets
+-    can be added using self.add_widget method.
+-    This box is optional as the inherited class can remove this block by
+-    setting the self._set_title_box to False.
+-    '''
+-
+-    def __init__(self):
+-        Gtk.Toolbar.__init__(self)
+-
+-        self.close_button = ToolButton(icon_name='dialog-cancel')
+-        self.close_button.set_tooltip(_('Close'))
+-        self.insert(self.close_button, -1)
+-        self.close_button.show()
+-
+-        self._label = Gtk.Label()
+-        self._label.set_alignment(0, 0.5)
+-
+-        tool_item = Gtk.ToolItem()
+-        tool_item.set_expand(True)
+-        tool_item.add(self._label)
+-        self._label.show()
+-        self.insert(tool_item, 0)
+-        tool_item.show()
+-
+-    def set_title(self, title):
+-        '''
+-        setter function for 'title' property.
+-        Args:
+-           title (str): title for the pop-up window
+-        '''
+-        self._label.set_markup('<b>%s</b>' % title)
+-        self._label.show()
+-
+-    title = GObject.Property(type=str, setter=set_title)
+diff --git a/src/sugar3/graphics/progressicon.py b/src/sugar3/graphics/progressicon.py
+index 327487102..d001efc7c 100644
+--- a/src/sugar3/graphics/progressicon.py
++++ b/src/sugar3/graphics/progressicon.py
+@@ -41,6 +41,7 @@ class ProgressIcon(Gtk.DrawingArea):
+       fill_color (string): The main (inside) color of progressicon
+          [e.g. fill_color=style.COLOR_BLUE.get_svg()
+     '''
++
+     def __init__(self, icon_name, pixel_size, stroke_color, fill_color,
+                  direction='vertical'):
+         Gtk.DrawingArea.__init__(self)
+diff --git a/src/sugar3/graphics/scrollingdetector.py b/src/sugar3/graphics/scrollingdetector.py
+index c0b483b27..a533aae04 100644
+--- a/src/sugar3/graphics/scrollingdetector.py
++++ b/src/sugar3/graphics/scrollingdetector.py
+@@ -32,18 +32,18 @@
+ 
+ class ScrollingDetector(GObject.GObject):
+     '''
+-    The scrolling detector sends signals when a scrolled window is scrolled and 
++    The scrolling detector sends signals when a scrolled window is scrolled and
+     when a scrolled window stops scrolling. Only one `scroll-start` signal will be
+-	emitted until scrolling stops.
+-    
++        emitted until scrolling stops.
++
+     The `scroll-start` signal is emitted when scrolling begins and
+     The `scroll-end` signal is emitted when scrolling ends
+     Neither of these two signals have any arguments
+-    
++
+     Args:
+         scrolled_window (Gtk.ScrolledWindow): A GTK scrolled window object for which
+             scrolling is to be detected
+-        
++
+         timeout (int): time in milliseconds to establish the interval for which
+             scrolling is detected
+     '''
+@@ -65,7 +65,7 @@ def connect_scrolled_window(self):
+         Connects scrolling detector to a scrolled window.
+         Detects scrolling when the vertical scrollbar
+         adjustment value is changed
+-        
++
+         Should be used to link an instance of a scrolling detector
+         to a Scrolled Window, after setting scrolled_window
+         '''
+diff --git a/src/sugar3/graphics/style.py b/src/sugar3/graphics/style.py
+index 1a5f47f66..8dafd5b7d 100644
+--- a/src/sugar3/graphics/style.py
++++ b/src/sugar3/graphics/style.py
+@@ -59,6 +59,7 @@ class Font(object):
+     Args:
+         desc (str): a description of the Font object
+     '''
++
+     def __init__(self, desc):
+         self._desc = desc
+ 
+@@ -84,6 +85,7 @@ class Color(object):
+ 
+         alpha (double):  transparency of color
+     '''
++
+     def __init__(self, color, alpha=1.0):
+         self._r, self._g, self._b = self._html_to_rgb(color)
+         self._a = alpha
+@@ -112,7 +114,8 @@ def get_html(self):
+         '''
+         Returns string in the standard html Color format (#FFFFFF)
+         '''
+-        return '#%02x%02x%02x' % (self._r * 255, self._g * 255, self._b * 255)
++        return '#%02x%02x%02x' % (
++            int(self._r * 255), int(self._g * 255), int(self._b * 255))
+ 
+     def _html_to_rgb(self, html_color):
+         '''
+diff --git a/src/sugar3/graphics/toolbarbox.py b/src/sugar3/graphics/toolbarbox.py
+index c813da065..804e2b24f 100644
+--- a/src/sugar3/graphics/toolbarbox.py
++++ b/src/sugar3/graphics/toolbarbox.py
+@@ -203,6 +203,8 @@ def __remove_cb(self, sender, button):
+         if button == self.expanded_button:
+             self.remove(button.page_widget)
+             self._expanded_button_index = -1
++
++
+ if hasattr(ToolbarBox, 'set_css_name'):
+     ToolbarBox.set_css_name('toolbarbox')
+ 
+diff --git a/src/sugar3/graphics/toolbox.py b/src/sugar3/graphics/toolbox.py
+index 5cd41f11d..23843b11b 100644
+--- a/src/sugar3/graphics/toolbox.py
++++ b/src/sugar3/graphics/toolbox.py
+@@ -32,7 +32,7 @@ class Toolbox(Gtk.VBox):
+     Class to represent the toolbox of an activity. Groups a
+     number of toolbars vertically, which can be accessed using their
+     indices. The current toolbar is the only one displayed.
+-    
++
+     Emits `current-toolbar-changed` signal when the
+     current toolbar is changed. This signal takes the current page index
+     as an argument.
+@@ -71,13 +71,13 @@ def _notify_page_cb(self, notebook, pspec):
+     def add_toolbar(self, name, toolbar):
+         '''
+         Adds a toolbar to this toolbox. Toolbar will be added
+-        to the end of this toolbox, and it's index will be 
++        to the end of this toolbox, and it's index will be
+         1 greater than the previously added index (index will be
+         0 if it is the first toolbar added).
+-        
++
+         Args:
+             name (string): name of toolbar to be added
+-            
++
+             toolbar (.. :class:`Gtk.Toolbar`): Gtk.Toolbar to be appended to this toolbox
+         '''
+         label = Gtk.Label(label=name)
+@@ -106,7 +106,7 @@ def add_toolbar(self, name, toolbar):
+     def remove_toolbar(self, index):
+         '''
+         Removes toolbar at the index specified.
+-        
++
+         Args:
+             index (int): index of the toolbar to be removed
+         '''
+@@ -118,9 +118,9 @@ def remove_toolbar(self, index):
+ 
+     def set_current_toolbar(self, index):
+         '''
+-        Sets the current toolbar to that of the index specified and 
++        Sets the current toolbar to that of the index specified and
+         displays it.
+-        
++
+         Args:
+             index (int): index of toolbar to be set as current toolbar
+         '''
+diff --git a/src/sugar3/graphics/toolbutton.py b/src/sugar3/graphics/toolbutton.py
+index e8f4e9ed2..8b0031f77 100644
+--- a/src/sugar3/graphics/toolbutton.py
++++ b/src/sugar3/graphics/toolbutton.py
+@@ -27,7 +27,7 @@
+         from sugar3.graphics.toolbutton import ToolButton
+ 
+         def __clicked_cb(button):
+-            print "tool button was clicked"
++            print("tool button was clicked")
+ 
+         w = Gtk.Window()
+         w.connect('destroy', Gtk.main_quit)
+diff --git a/src/sugar3/graphics/tray.py b/src/sugar3/graphics/tray.py
+index da8dda99c..55db31afe 100644
+--- a/src/sugar3/graphics/tray.py
++++ b/src/sugar3/graphics/tray.py
+@@ -330,6 +330,8 @@ def get_item_index(self, item):
+ 
+     def scroll_to_item(self, item):
+         self._viewport.scroll_to_item(item)
++
++
+ if hasattr(HTray, 'set_css_name'):
+     HTray.set_css_name('htray')
+ 
+@@ -424,6 +426,8 @@ def get_item_index(self, item):
+ 
+     def scroll_to_item(self, item):
+         self._viewport.scroll_to_item(item)
++
++
+ if hasattr(VTray, 'set_css_name'):
+     VTray.set_css_name('VTray')
+ 
+diff --git a/src/sugar3/graphics/xocolor.py b/src/sugar3/graphics/xocolor.py
+index 4c6e33a87..e5d7e4e85 100644
+--- a/src/sugar3/graphics/xocolor.py
++++ b/src/sugar3/graphics/xocolor.py
+@@ -20,6 +20,7 @@
+ Each pair of colors represents the fill color and the stroke color
+ '''
+ 
++import six
+ import random
+ import logging
+ 
+@@ -210,11 +211,11 @@
+ def _parse_string(color_string):
+     '''
+     Returns array of length 2 of two colors in standard html form of [stroke color, fill color]
+-    
++
+     Args:
+         color_string (string): two html format strings separated by a comma
+     '''
+-    if not isinstance(color_string, (str, unicode)):
++    if not isinstance(color_string, (six.text_type, six.binary_type)):
+         logging.error('Invalid color string: %r', color_string)
+         return None
+ 
+@@ -233,13 +234,14 @@ def _parse_string(color_string):
+ class XoColor:
+     '''
+     Defines color for XO
+-    
++
+     Args:
+-        color_string (string): two html format strings separated 
+-		    by a comma, "white", or "insensitive". If color_string 
+-		    is None, the user's color will be created. If parsed_color 
+-		    cannot be created, a random color will be used
++        color_string (string): two html format strings separated
++                    by a comma, "white", or "insensitive". If color_string
++                    is None, the user's color will be created. If parsed_color
++                    cannot be created, a random color will be used
+     '''
++
+     def __init__(self, color_string=None):
+         parsed_color = None
+ 
+@@ -261,7 +263,7 @@ def __cmp__(self, other):
+         '''
+         Compares two XO colors by their stroke and fill color
+         Returns 0 if they are equal and -1 if they are unequal
+-        
++
+         Args:
+             other (object): other XO color to compare
+         '''
+@@ -295,12 +297,12 @@ def to_string(self):
+ 
+     f = open(sys.argv[1], 'r')
+ 
+-    print 'colors = ['
++    print('colors = [')
+ 
+     for line in f.readlines():
+         match = re.match(r'fill: ([A-Z0-9]*) stroke: ([A-Z0-9]*)', line)
+-        print "['#%s', '#%s'], \\" % (match.group(2), match.group(1))
++        print("['#%s', '#%s'], \\" % (match.group(2), match.group(1)))
+ 
+-    print ']'
++    print(']')
+ 
+     f.close()
+diff --git a/src/sugar3/logger.py b/src/sugar3/logger.py
+index 2e01b1cb4..43ef5b56c 100644
+--- a/src/sugar3/logger.py
++++ b/src/sugar3/logger.py
+@@ -20,16 +20,17 @@
+ STABLE.
+ """
+ 
++import six
+ import array
+ import collections
+ import errno
+ import logging
+ import sys
+ import os
+-import repr as repr_
+ import decorator
+ import time
+ 
++from six.moves import reprlib as repr_
+ from sugar3 import env
+ 
+ # Let's keep this module self contained so that it can be easily
+@@ -104,8 +105,8 @@ def cleanup():
+             for f in os.listdir(root):
+                 os.remove(os.path.join(root, f))
+             os.rmdir(root)
+-        except OSError, e:
+-            print "Could not remove old logs files %s" % e
++        except OSError as e:
++            print("Could not remove old logs files %s" % e)
+ 
+     if len(backup_logs) > 0:
+         name = str(int(time.time()))
+@@ -116,7 +117,7 @@ def cleanup():
+                 source_path = os.path.join(logs_dir, log)
+                 dest_path = os.path.join(backup_dir, log)
+                 os.rename(source_path, dest_path)
+-        except OSError, e:
++        except OSError as e:
+             # gracefully deal w/ disk full
+             if e.errno != errno.ENOSPC:
+                 raise e
+@@ -145,7 +146,7 @@ def __init__(self, stream):
+         def write(self, s):
+             try:
+                 self._stream.write(s)
+-            except IOError, e:
++            except IOError as e:
+                 # gracefully deal w/ disk full
+                 if e.errno != errno.ENOSPC:
+                     raise e
+@@ -153,7 +154,7 @@ def write(self, s):
+         def flush(self):
+             try:
+                 self._stream.flush()
+-            except IOError, e:
++            except IOError as e:
+                 # gracefully deal w/ disk full
+                 if e.errno != errno.ENOSPC:
+                     raise e
+@@ -177,7 +178,7 @@ def flush(self):
+ 
+             sys.stdout = SafeLogWrapper(sys.stdout)
+             sys.stderr = SafeLogWrapper(sys.stderr)
+-        except OSError, e:
++        except OSError as e:
+             # if we're out of space, just continue
+             if e.errno != errno.ENOSPC:
+                 raise e
+@@ -188,13 +189,15 @@ def flush(self):
+ class TraceRepr(repr_.Repr):
+ 
+     # better handling of subclasses of basic types, e.g. for DBus
+-    _TYPES = [int, long, bool, tuple, list, array.array, set, frozenset,
++    _TYPES = [int, bool, tuple, list, array.array, set, frozenset,
+               collections.deque, dict, str]
++    if six.PY2:
++        _TYPES.append(long)
+ 
+     def repr1(self, x, level):
+         for t in self._TYPES:
+             if isinstance(x, t):
+-                return getattr(self, 'repr_'+t.__name__)(x, level)
++                return getattr(self, 'repr_' + t.__name__)(x, level)
+ 
+         return repr_.Repr.repr1(self, x, level)
+ 
+@@ -232,14 +235,14 @@ def _trace(f, *args, **kwargs):
+             [trace_repr.repr(a)
+                 for (idx, a) in enumerate(args) if idx not in skip_args] +
+             ['%s=%s' % (k, trace_repr.repr(v))
+-                for (k, v) in kwargs.items() if k not in skip_kwargs])
++                for (k, v) in list(kwargs.items()) if k not in skip_kwargs])
+ 
+         trace_logger.log(TRACE, "%s(%s) invoked", f.__name__,
+                          params_formatted)
+ 
+         try:
+             res = f(*args, **kwargs)
+-        except:
++        except BaseException:
+             trace_logger.exception("Exception occurred in %s" % f.__name__)
+             raise
+ 
+diff --git a/src/sugar3/mime.py b/src/sugar3/mime.py
+index cd30fa70d..a2797d0d0 100644
+--- a/src/sugar3/mime.py
++++ b/src/sugar3/mime.py
+@@ -32,7 +32,9 @@
+ from gi.repository import GdkPixbuf
+ from gi.repository import Gio
+ 
+-_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg)
++
++def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg)
++
+ 
+ GENERIC_TYPE_TEXT = 'Text'
+ GENERIC_TYPE_IMAGE = 'Image'
+@@ -185,14 +187,14 @@ def get_mime_parents(mime_type):
+             with open(subclasses_path) as parents_file:
+                 for line in parents_file:
+                     subclass, parent = line.split()
+-                    if subclass not in _subclasses.keys():
++                    if subclass not in list(_subclasses.keys()):
+                         _subclasses[subclass] = [parent]
+                     else:
+                         _subclasses[subclass].append(parent)
+ 
+         _subclasses_timestamps = timestamps
+ 
+-    if mime_type in _subclasses.keys():
++    if mime_type in list(_subclasses.keys()):
+         return _subclasses[mime_type]
+     else:
+         return []
+diff --git a/src/sugar3/network.py b/src/sugar3/network.py
+index 771a3caed..6afa4f8a8 100644
+--- a/src/sugar3/network.py
++++ b/src/sugar3/network.py
+@@ -21,14 +21,14 @@
+ 
+ import os
+ import threading
+-import urllib
++from six.moves import urllib
+ import fcntl
+ import tempfile
+ 
+ from gi.repository import GObject
+ from gi.repository import GLib
+-import SimpleHTTPServer
+-import SocketServer
++from six.moves import SimpleHTTPServer
++from six.moves import socketserver
+ 
+ 
+ __authinfos = {}
+@@ -46,7 +46,7 @@ def _del_authinfo():
+     del __authinfos[threading.currentThread()]
+ 
+ 
+-class GlibTCPServer(SocketServer.TCPServer):
++class GlibTCPServer(socketserver.TCPServer):
+     """GlibTCPServer
+ 
+     Integrate socket accept into glib mainloop.
+@@ -56,7 +56,7 @@ class GlibTCPServer(SocketServer.TCPServer):
+     request_queue_size = 20
+ 
+     def __init__(self, server_address, RequestHandlerClass):
+-        SocketServer.TCPServer.__init__(self, server_address,
++        socketserver.TCPServer.__init__(self, server_address,
+                                         RequestHandlerClass)
+         self.socket.setblocking(0)  # Set nonblocking
+ 
+@@ -212,7 +212,7 @@ def __init__(self, url, destdir=None):
+         GObject.GObject.__init__(self)
+ 
+     def start(self, destfile=None, destfd=None):
+-        self._info = urllib.urlopen(self._url)
++        self._info = urllib.request.urlopen(self._url)
+         self._outf = None
+         self._fname = None
+         if destfd and not destfile:
+@@ -226,14 +226,14 @@ def start(self, destfile=None, destfd=None):
+                 self._outf = destfd
+             else:
+                 self._outf = os.open(self._fname, os.O_RDWR |
+-                                     os.O_TRUNC | os.O_CREAT, 0644)
++                                     os.O_TRUNC | os.O_CREAT, 0o644)
+         else:
+             fname = self._get_filename_from_headers(self._info.headers)
+             self._suggested_fname = fname
+-            garbage_, path = urllib.splittype(self._url)
+-            garbage_, path = urllib.splithost(path or "")
+-            path, garbage_ = urllib.splitquery(path or "")
+-            path, garbage_ = urllib.splitattr(path or "")
++            garbage_, path = urllib.parse.splittype(self._url)
++            garbage_, path = urllib.parse.splithost(path or "")
++            path, garbage_ = urllib.parse.splitquery(path or "")
++            path, garbage_ = urllib.parse.splitattr(path or "")
+             suffix = os.path.splitext(path)[1]
+             (self._outf, self._fname) = tempfile.mkstemp(suffix=suffix,
+                                                          dir=self._destdir)
+@@ -291,7 +291,7 @@ def _read_next_chunk(self, source, condition):
+                 self.cleanup()
+                 self.emit('finished', self._fname, self._suggested_fname)
+                 return False
+-        except Exception, err:
++        except Exception as err:
+             self.cleanup(remove=True)
+             self.emit('error', 'Error downloading file: %r' % err)
+             return False
+diff --git a/src/sugar3/presence/activity.py b/src/sugar3/presence/activity.py
+index f07aab827..6097b8379 100644
+--- a/src/sugar3/presence/activity.py
++++ b/src/sugar3/presence/activity.py
+@@ -21,6 +21,7 @@
+ STABLE.
+ """
+ 
++import six
+ import logging
+ from functools import partial
+ 
+@@ -124,6 +125,11 @@ def __init__(self, account_path, connection, room_handle=None,
+ 
+     def _start_tracking_properties(self):
+         bus = dbus.SessionBus()
++        arg_dict = dict(reply_handler=self.__got_properties_cb,
++                        error_handler=self.__error_handler_cb)
++        if six.PY2:
++            arg_dict = arg_dict.update(utf8_strings=True)
++
+         self._get_properties_call = bus.call_async(
+             self.telepathy_conn.requested_bus_name,
+             self.telepathy_conn.object_path,
+@@ -131,9 +137,7 @@ def _start_tracking_properties(self):
+             'GetProperties',
+             'u',
+             (self.room_handle,),
+-            reply_handler=self.__got_properties_cb,
+-            error_handler=self.__error_handler_cb,
+-            utf8_strings=True)
++            arg_dict)
+ 
+         # As only one Activity instance is needed per activity process,
+         # we can afford listening to ActivityPropertiesChanged like this.
+@@ -144,7 +148,7 @@ def _start_tracking_properties(self):
+ 
+     def __activity_properties_changed_cb(self, room_handle, properties):
+         _logger.debug('%r: Activity properties changed to %r' % (self,
+-                      properties))
++                                                                 properties))
+         self._update_properties(properties)
+ 
+     def __got_properties_cb(self, properties):
+@@ -239,7 +243,7 @@ def get_joined_buddies(self):
+         returns list of presence Buddy objects that we can successfully
+         create from the buddy object paths that PS has for this activity.
+         """
+-        return self._joined_buddies.values()
++        return list(self._joined_buddies.values())
+ 
+     def get_buddy_by_handle(self, handle):
+         """Retrieve the Buddy object given a telepathy handle.
+@@ -300,8 +304,9 @@ def _start_tracking_channel(self):
+         channel.connect_to_signal('Closed', self.__text_channel_closed_cb)
+ 
+     def __get_all_members_cb(self, members, local_pending, remote_pending):
+-        _logger.debug('__get_all_members_cb %r %r' % (members,
+-                      self._text_channel_group_flags))
++        _logger.debug(
++            '__get_all_members_cb %r %r' %
++            (members, self._text_channel_group_flags))
+         if self._channel_self_handle in members:
+             members.remove(self._channel_self_handle)
+ 
+@@ -631,8 +636,9 @@ def _tubes_ready(self):
+         self._add_self_to_channel()
+ 
+     def __text_channel_group_flags_changed_cb(self, added, removed):
+-        _logger.debug('__text_channel_group_flags_changed_cb %r %r' % (added,
+-                      removed))
++        _logger.debug(
++            '__text_channel_group_flags_changed_cb %r %r' %
++            (added, removed))
+         self.text_channel_group_flags |= added
+         self.text_channel_group_flags &= ~removed
+ 
+diff --git a/src/sugar3/presence/buddy.py b/src/sugar3/presence/buddy.py
+index 6f85ae894..2e0190c00 100644
+--- a/src/sugar3/presence/buddy.py
++++ b/src/sugar3/presence/buddy.py
+@@ -22,7 +22,7 @@
+ """
+ 
+ import logging
+-
++import six
+ from gi.repository import GObject
+ import dbus
+ from telepathy.interfaces import CONNECTION, \
+@@ -103,7 +103,7 @@ def set_color(self, color):
+     def get_current_activity(self):
+         if self._current_activity is None:
+             return None
+-        for activity in self._activities.values():
++        for activity in list(self._activities.values()):
+             if activity.props.id == self._current_activity:
+                 return activity
+         return None
+@@ -164,6 +164,12 @@ def __init__(self, account_path, contact_id):
+                                      dbus_interface=CONNECTION)
+         self.contact_handle = handles[0]
+ 
++        arg_dict = dict(reply_handler=self.__got_properties_cb,
++                        error_handler=self.__error_handler_cb,
++                        byte_arrays = True)
++        if six.PY2:
++            arg_dict = arg_dict.update(utf8_strings=True)
++
+         self._get_properties_call = bus.call_async(
+             connection_name,
+             connection.object_path,
+@@ -171,10 +177,7 @@ def __init__(self, account_path, contact_id):
+             'GetProperties',
+             'u',
+             (self.contact_handle,),
+-            reply_handler=self.__got_properties_cb,
+-            error_handler=self.__error_handler_cb,
+-            utf8_strings=True,
+-            byte_arrays=True)
++            arg_dict)
+ 
+         self._get_attributes_call = bus.call_async(
+             connection_name,
+@@ -245,4 +248,3 @@ def __init__(self):
+ 
+         self.props.nick = get_nick_name()
+         self.props.color = get_color().to_string()
+-
+diff --git a/src/sugar3/presence/connectionmanager.py b/src/sugar3/presence/connectionmanager.py
+index 325c200cc..dbb957caf 100644
+--- a/src/sugar3/presence/connectionmanager.py
++++ b/src/sugar3/presence/connectionmanager.py
+@@ -92,7 +92,8 @@ def __status_changed_cb(self, account_path, status, reason):
+ 
+     def get_preferred_connection(self):
+         best_connection = None, None
+-        for account_path, connection in self._connections_per_account.items():
++        for account_path, connection in list(
++                self._connections_per_account.items()):
+             if 'salut' in account_path and connection.connected:
+                 best_connection = account_path, connection.connection
+             elif 'gabble' in account_path and connection.connected:
+@@ -107,7 +108,8 @@ def get_connections_per_account(self):
+         return self._connections_per_account
+ 
+     def get_account_for_connection(self, connection_path):
+-        for account_path, connection in self._connections_per_account.items():
++        for account_path, connection in list(
++                self._connections_per_account.items()):
+             if connection.connection.object_path == connection_path:
+                 return account_path
+         return None
+diff --git a/src/sugar3/presence/presenceservice.py b/src/sugar3/presence/presenceservice.py
+index 8b66b0e54..2f9f85104 100644
+--- a/src/sugar3/presence/presenceservice.py
++++ b/src/sugar3/presence/presenceservice.py
+@@ -78,7 +78,8 @@ def get_activity(self, activity_id, warn_if_none=True):
+             connection_manager = get_connection_manager()
+             connections_per_account = \
+                 connection_manager.get_connections_per_account()
+-            for account_path, connection in connections_per_account.items():
++            for account_path, connection in list(
++                    connections_per_account.items()):
+                 if not connection.connected:
+                     continue
+                 logging.debug('Calling GetActivity on %s' % account_path)
+@@ -86,13 +87,13 @@ def get_activity(self, activity_id, warn_if_none=True):
+                     room_handle = connection.connection.GetActivity(
+                         activity_id,
+                         dbus_interface=CONN_INTERFACE_ACTIVITY_PROPERTIES)
+-                except dbus.exceptions.DBusException, e:
++                except dbus.exceptions.DBusException as e:
+                     name = 'org.freedesktop.Telepathy.Error.NotAvailable'
+                     if e.get_dbus_name() == name:
+                         logging.debug("There's no shared activity with the id "
+                                       "%s" % activity_id)
+                     elif e.get_dbus_name() == \
+-                         'org.freedesktop.DBus.Error.UnknownMethod':
++                            'org.freedesktop.DBus.Error.UnknownMethod':
+                         logging.warning(
+                             'Telepathy Account %r does not support '
+                             'Sugar collaboration', account_path)
+diff --git a/src/sugar3/presence/tubeconn.py b/src/sugar3/presence/tubeconn.py
+index 9a496d984..8b1e2ff92 100644
+--- a/src/sugar3/presence/tubeconn.py
++++ b/src/sugar3/presence/tubeconn.py
+@@ -24,6 +24,7 @@
+ __docformat__ = 'reStructuredText'
+ 
+ 
++import six
+ import logging
+ 
+ from dbus.connection import Connection
+@@ -77,7 +78,9 @@ def _on_get_self_handle_error(self, e):
+ 
+     def close(self):
+         self._dbus_names_changed_match.remove()
+-        self._on_dbus_names_changed(self.tube_id, (), self.participants.keys())
++        self._on_dbus_names_changed(
++            self.tube_id, (), list(
++                self.participants.keys()))
+         super(TubeConnection, self).close()
+ 
+     def _on_get_dbus_names_reply(self, names):
+@@ -111,6 +114,6 @@ def watch_participants(self, callback):
+             # GetDBusNames already returned: fake a participant add event
+             # immediately
+             added = []
+-            for k, v in self.participants.iteritems():
++            for k, v in six.iteritems(self.participants):
+                 added.append((k, v))
+             callback(added, [])
+diff --git a/src/sugar3/profile.py b/src/sugar3/profile.py
+index 818afe05a..e46943fde 100644
+--- a/src/sugar3/profile.py
++++ b/src/sugar3/profile.py
+@@ -21,7 +21,8 @@
+ from gi.repository import Gio
+ import os
+ import logging
+-from ConfigParser import ConfigParser
++
++from six.moves.configparser import ConfigParser
+ 
+ from sugar3 import env
+ from sugar3 import util
+diff --git a/src/sugar3/speech.py b/src/sugar3/speech.py
+index fd2c5b5f7..19416043e 100644
+--- a/src/sugar3/speech.py
++++ b/src/sugar3/speech.py
+@@ -33,7 +33,7 @@
+     from gi.repository import Gst
+     Gst.init(None)
+     Gst.parse_launch('espeak')
+-except:
++except BaseException:
+     logging.error('Gst or the espeak plugin is not installed in the system.')
+     _HAS_GST = False
+ 
+@@ -268,7 +268,12 @@ def say_text(self, text, pitch=None, rate=None, lang_code=None):
+         else:
+             voice_name = self._player.get_all_voices()[lang_code]
+         if text:
+-            logging.error('PLAYING %r lang %r pitch %r rate %r', text, voice_name, pitch, rate)
++            logging.error(
++                'PLAYING %r lang %r pitch %r rate %r',
++                text,
++                voice_name,
++                pitch,
++                rate)
+             self._player.speak(pitch, rate, voice_name, text)
+ 
+     def say_selected_text(self):
+diff --git a/src/sugar3/test/Makefile.am b/src/sugar3/test/Makefile.am
+index 2ccac0223..6d77c2fc6 100644
+--- a/src/sugar3/test/Makefile.am
++++ b/src/sugar3/test/Makefile.am
+@@ -3,4 +3,4 @@ sugar_PYTHON = \
+ 	__init__.py \
+     discover.py \
+ 	uitree.py \
+-	unittest.py
++	_unittest.py
+diff --git a/src/sugar3/test/unittest.py b/src/sugar3/test/_unittest.py
+similarity index 93%
+rename from src/sugar3/test/unittest.py
+rename to src/sugar3/test/_unittest.py
+index d3e65ee24..6c51d7ee6 100644
+--- a/src/sugar3/test/unittest.py
++++ b/src/sugar3/test/_unittest.py
+@@ -18,8 +18,6 @@
+ UNSTABLE.
+ """
+ 
+-from __future__ import absolute_import
+-
+ import logging
+ import os
+ import unittest
+@@ -52,11 +50,11 @@ def tearDown(self):
+     @contextmanager
+     def run_view(self, name):
+         view_path = os.path.join("views", "%s.py" % name)
+-        process = subprocess.Popen(["python", view_path])
++        process = subprocess.Popen(["python3", view_path])
+ 
+         try:
+             yield
+-        except:
++        except BaseException:
+             logging.debug(uitree.get_root().dump())
+             raise
+         finally:
+@@ -77,12 +75,12 @@ def _run_activity(self, options=None):
+                 cmd += options
+             process = subprocess.Popen(cmd)
+         else:
+-            print "No bundle_id specified."
++            print("No bundle_id specified.")
+             return
+ 
+         try:
+             yield
+-        except:
++        except BaseException:
+             logging.debug(uitree.get_root().dump())
+             raise
+         finally:
+diff --git a/src/sugar3/test/discover.py b/src/sugar3/test/discover.py
+index 8de28f8c7..dbf898d1d 100644
+--- a/src/sugar3/test/discover.py
++++ b/src/sugar3/test/discover.py
+@@ -18,8 +18,6 @@
+ UNSTABLE.
+ """
+ 
+-from __future__ import absolute_import
+-
+ import argparse
+ import sys
+ import os
+diff --git a/src/sugar3/test/uitree.py b/src/sugar3/test/uitree.py
+index a4e8e7109..3da33c1c2 100644
+--- a/src/sugar3/test/uitree.py
++++ b/src/sugar3/test/uitree.py
+@@ -44,7 +44,7 @@ def wrapped(*args, **kwargs):
+ 
+             try:
+                 result = func(*args, **kwargs)
+-            except GLib.GError, e:
++            except GLib.GError as e:
+                 # The application is not responding, try again
+                 if e.code == Atspi.Error.IPC:
+                     continue
+diff --git a/src/sugar3/util.py b/src/sugar3/util.py
+index f44f250cb..45cbd3688 100644
+--- a/src/sugar3/util.py
++++ b/src/sugar3/util.py
+@@ -20,6 +20,7 @@
+ UNSTABLE. We have been adding helpers randomly to this module.
+ """
+ 
++import six
+ import os
+ import time
+ import hashlib
+@@ -31,20 +32,26 @@
+ import atexit
+ 
+ 
+-_ = lambda msg: gettext.dgettext('sugar-toolkit-gtk3', msg)
++def _(msg): return gettext.dgettext('sugar-toolkit-gtk3', msg)
+ 
+ 
+ def printable_hash(in_hash):
+     """Convert binary hash data into printable characters."""
+     printable = ""
+     for char in in_hash:
+-        printable = printable + binascii.b2a_hex(char)
++        if six.PY3:
++            char = bytes([char])
++            printable = printable + binascii.b2a_hex(char).decode()
++        else:
++            printable = printable + binascii.b2a_hex(char)
+     return printable
+ 
+ 
+ def sha_data(data):
+     """sha1 hash some bytes."""
+     sha_hash = hashlib.sha1()
++    if six.PY3:
++        data = data.encode('utf-8')
+     sha_hash.update(data)
+     return sha_hash.digest()
+ 
+@@ -81,7 +88,7 @@ def is_hex(s):
+ 
+ def validate_activity_id(actid):
+     """Validate an activity ID."""
+-    if not isinstance(actid, (str, unicode)):
++    if not isinstance(actid, (six.binary_type, six.text_type)):
+         return False
+     if len(actid) != ACTIVITY_ID_LEN:
+         return False
+@@ -204,7 +211,7 @@ def itervalues(self):
+             yield j
+ 
+     def keys(self):
+-        return self.d.keys()
++        return list(self.d.keys())
+ 
+ 
+ units = [['%d year', '%d years', 356 * 24 * 60 * 60],
+@@ -331,13 +338,14 @@ def __del__(self):
+ 
+ def _cleanup_temp_files():
+     logging.debug('_cleanup_temp_files')
+-    for path in _tracked_paths.keys():
++    for path in list(_tracked_paths.keys()):
+         try:
+             os.unlink(path)
+-        except:
++        except BaseException:
+             # pylint: disable=W0702
+             logging.exception('Exception occurred in _cleanup_temp_files')
+ 
++
+ atexit.register(_cleanup_temp_files)
+ 
+ 



More information about the arch-commits mailing list