[arch-commits] Commit in sugar-toolkit-gtk3/trunk (PKGBUILD six.patch)
Balló György
bgyorgy at archlinux.org
Fri Mar 29 10:01:09 UTC 2019
Date: Friday, March 29, 2019 @ 10:01:08
Author: bgyorgy
Revision: 445976
upgpkg: sugar-toolkit-gtk3 0.113-2
Update to new version
Added:
sugar-toolkit-gtk3/trunk/six.patch
Modified:
sugar-toolkit-gtk3/trunk/PKGBUILD
-----------+
PKGBUILD | 17
six.patch | 2987 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 3000 insertions(+), 4 deletions(-)
Modified: PKGBUILD
===================================================================
--- PKGBUILD 2019-03-29 09:44:24 UTC (rev 445975)
+++ PKGBUILD 2019-03-29 10:01:08 UTC (rev 445976)
@@ -2,18 +2,27 @@
pkgname=sugar-toolkit-gtk3
pkgver=0.113
-pkgrel=1
+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-six'
+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)
-sha256sums=('4062d981d551f1567d073389385ffc28793b75f0ed2485e8f9250a145239cba7')
+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
Added: six.patch
===================================================================
--- six.patch (rev 0)
+++ six.patch 2019-03-29 10:01:08 UTC (rev 445976)
@@ -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