changeset 312:772c170b47a9

Python3 port: /!\ Cagou now runs with Python 3.6+ Port has been done in the same way as for backend (check backend commit b2d067339de3 message for details).
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:14:22 +0200
parents a0d978d3ce84
children ffb09ae72b88
files bin/cagou cagou/__init__.py cagou/core/cagou_main.py cagou/core/cagou_widget.py cagou/core/common.py cagou/core/common_widgets.py cagou/core/constants.py cagou/core/dialog.py cagou/core/image.py cagou/core/kivy_hack.py cagou/core/menu.py cagou/core/patches.py cagou/core/profile_manager.py cagou/core/simple_xhtml.py cagou/core/utils.py cagou/core/widgets_handler.py cagou/core/xmlui.py cagou/plugins/plugin_transfer_android_gallery.py cagou/plugins/plugin_transfer_android_photo.py cagou/plugins/plugin_transfer_android_video.py cagou/plugins/plugin_transfer_file.py cagou/plugins/plugin_transfer_voice.py cagou/plugins/plugin_wid_chat.kv cagou/plugins/plugin_wid_chat.py cagou/plugins/plugin_wid_contact_list.py cagou/plugins/plugin_wid_file_sharing.py cagou/plugins/plugin_wid_remote.py cagou/plugins/plugin_wid_settings.py cagou/plugins/plugin_wid_widget_selector.py setup.py
diffstat 30 files changed, 366 insertions(+), 371 deletions(-) [+]
line wrap: on
line diff
--- a/bin/cagou	Mon Aug 05 11:21:54 2019 +0200
+++ b/bin/cagou	Tue Aug 13 19:14:22 2019 +0200
@@ -1,4 +1,4 @@
-#!/usr//bin/env python2
+#!/usr//bin/env python3
 # -*- coding: utf-8 -*-
 
 # Cagou: desktop/mobile frontend for Salut à Toi XMPP client
--- a/cagou/__init__.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/__init__.py	Tue Aug 13 19:14:22 2019 +0200
@@ -31,7 +31,7 @@
 G = Global()
 
 # this import must be done after G is created
-from core import cagou_main
+from .core import cagou_main
 
 def run():
     host = G._host = cagou_main.Cagou()
--- a/cagou/core/cagou_main.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/cagou_main.py	Tue Aug 13 19:14:22 2019 +0200
@@ -24,7 +24,7 @@
 from sat.core.i18n import _
 from . import kivy_hack
 kivy_hack.do_hack()
-from constants import Const as C
+from .constants import Const as C
 from sat.core import log as logging
 log = logging.getLogger(__name__)
 from sat.core import exceptions
@@ -49,8 +49,8 @@
 from kivy.app import App
 from kivy.lang import Builder
 from kivy import properties
-import xmlui
-from profile_manager import ProfileManager
+from . import xmlui
+from .profile_manager import ProfileManager
 from kivy.clock import Clock
 from kivy.uix.label import Label
 from kivy.uix.boxlayout import BoxLayout
@@ -64,7 +64,7 @@
 from kivy.metrics import dp
 from kivy import utils as kivy_utils
 from kivy.config import Config as KivyConfig
-from cagou_widget import CagouWidget
+from .cagou_widget import CagouWidget
 from . import widgets_handler
 from .common import IconButton
 from . import menu
@@ -78,7 +78,7 @@
     from plyer import notification
 except ImportError:
     notification = None
-    log.warning(_(u"Can't import plyer, some features disabled"))
+    log.warning(_("Can't import plyer, some features disabled"))
 
 
 ## platform specific settings ##
@@ -153,14 +153,14 @@
         self.clear_widgets()
         for n in self.notes:
             kwargs = {
-                u'title': n.title,
-                u'message': n.message,
-                u'level': n.level
+                'title': n.title,
+                'message': n.message,
+                'level': n.level
             }
             if n.symbol is not None:
-                kwargs[u'symbol'] = n.symbol
+                kwargs['symbol'] = n.symbol
             if n.action is not None:
-                kwargs[u'action'] = n.action
+                kwargs['action'] = n.action
             self.add_widget(NoteDrop(title=n.title, message=n.message, level=n.level,
                                      symbol=n.symbol, action=n.action))
         self.add_widget(self.clear_btn)
@@ -194,14 +194,14 @@
 
     def addNote(self, title, message, level, symbol, action):
         kwargs = {
-            u'title': title,
-            u'message': message,
-            u'level': level
+            'title': title,
+            'message': message,
+            'level': level
         }
         if symbol is not None:
-            kwargs[u'symbol'] = symbol
+            kwargs['symbol'] = symbol
         if action is not None:
-            kwargs[u'action'] = action
+            kwargs['action'] = action
         note = Note(**kwargs)
         self.notes.append(note)
         if self.notes_event is None:
@@ -319,7 +319,7 @@
 
     def build(self):
         Window.bind(on_keyboard=self.key_input)
-        wid = CagouRootWidget(Label(text=u"Loading please wait"))
+        wid = CagouRootWidget(Label(text="Loading please wait"))
         if sys.platform == 'android':
             # we don't want menu on Android
             wid.root_menus.height = 0
@@ -413,10 +413,10 @@
 
         bridge_module = dynamic_import.bridge(bridge_name, 'sat_frontends.bridge')
         if bridge_module is None:
-            log.error(u"Can't import {} bridge".format(bridge_name))
+            log.error("Can't import {} bridge".format(bridge_name))
             sys.exit(3)
         else:
-            log.debug(u"Loading {} bridge".format(bridge_name))
+            log.debug("Loading {} bridge".format(bridge_name))
         super(Cagou, self).__init__(bridge_factory=bridge_module.Bridge,
                                     xmlui=xmlui,
                                     check_options=quick_utils.check_options,
@@ -432,7 +432,7 @@
             try:
                 os.makedirs(self.downloads_dir)
             except OSError as e:
-                log.warnings(_(u"Can't create downloads dir: {reason}").format(reason=e))
+                log.warnings(_("Can't create downloads dir: {reason}").format(reason=e))
         self.app.default_avatar = os.path.join(self.media_dir, "misc/default_avatar.png")
         self.app.icon = os.path.join(self.media_dir,
                                      "icons/muchoslava/png/cagou_profil_bleu_96.png")
@@ -456,11 +456,11 @@
         if not self.tls_validation:
             from cagou.core import patches
             patches.apply()
-            log.warning(u"SSL certificate validation is disabled, this is unsecure!")
+            log.warning("SSL certificate validation is disabled, this is unsecure!")
 
     @property
     def visible_widgets(self):
-        for w_list in self._visible_widgets.itervalues():
+        for w_list in self._visible_widgets.values():
             for w in w_list:
                 yield w
 
@@ -572,13 +572,13 @@
 
         for kv_file in kv_files:
             Builder.load_file(kv_file)
-            log.debug(u"kv file {} loaded".format(kv_file))
+            log.debug("kv file {} loaded".format(kv_file))
 
     def _import_plugins(self):
         """import all plugins"""
         self.default_wid = None
         plugins_path = os.path.dirname(cagou.plugins.__file__)
-        plugin_glob = u"plugin*." + C.PLUGIN_EXT
+        plugin_glob = "plugin*." + C.PLUGIN_EXT
         plug_lst = [os.path.splitext(p)[0] for p in
                     map(os.path.basename, glob.glob(os.path.join(plugins_path,
                                                                  plugin_glob)))]
@@ -591,10 +591,10 @@
 
             # we get type from plugin name
             suff = plug[7:]
-            if u'_' not in suff:
-                log.error(u"invalid plugin name: {}, skipping".format(plug))
+            if '_' not in suff:
+                log.error("invalid plugin name: {}, skipping".format(plug))
                 continue
-            plugin_type = suff[:suff.find(u'_')]
+            plugin_type = suff[:suff.find('_')]
 
             # and select the variable to use according to type
             if plugin_type == C.PLUG_TYPE_WID:
@@ -604,7 +604,7 @@
                 imported_names = imported_names_transfer
                 default_factory = self._defaultFactoryTransfer
             else:
-                log.error(u"unknown plugin type {type_} for plugin {file_}, skipping"
+                log.error("unknown plugin type {type_} for plugin {file_}, skipping"
                     .format(
                     type_ = plugin_type,
                     file_ = plug
@@ -623,7 +623,7 @@
 
             if 'platforms' in plugin_info:
                 if sys.platform not in plugin_info['platforms']:
-                    log.info(u"{plugin_file} is not used on this platform, skipping"
+                    log.info("{plugin_file} is not used on this platform, skipping"
                              .format(**plugin_info))
                     continue
 
@@ -631,13 +631,13 @@
             if 'import_name' not in plugin_info:
                 plugin_info['import_name'] = plug
             if plugin_info['import_name'] in imported_names:
-                log.warning(_(u"there is already a plugin named {}, "
-                              u"ignoring new one").format(plugin_info['import_name']))
+                log.warning(_("there is already a plugin named {}, "
+                              "ignoring new one").format(plugin_info['import_name']))
                 continue
             if plugin_info['import_name'] == C.WID_SELECTOR:
                 if plugin_type != C.PLUG_TYPE_WID:
-                    log.error(u"{import_name} import name can only be used with {type_} "
-                              u"type, skipping {name}".format(type_=C.PLUG_TYPE_WID,
+                    log.error("{import_name} import name can only be used with {type_} "
+                              "type, skipping {name}".format(type_=C.PLUG_TYPE_WID,
                                                               **plugin_info))
                     continue
                 # if WidgetSelector exists, it will be our default widget
@@ -651,10 +651,10 @@
 
             # we need to load the kv file
             if 'kv_file' not in plugin_info:
-                plugin_info['kv_file'] = u'{}.kv'.format(plug)
+                plugin_info['kv_file'] = '{}.kv'.format(plug)
             kv_path = os.path.join(plugins_path, plugin_info['kv_file'])
             if not os.path.exists(kv_path):
-                log.debug(u"no kv found for {plugin_file}".format(**plugin_info))
+                log.debug("no kv found for {plugin_file}".format(**plugin_info))
             else:
                 Builder.load_file(kv_path)
 
@@ -669,7 +669,7 @@
 
             # icons
             for size in ('small', 'medium'):
-                key = u'icon_{}'.format(size)
+                key = 'icon_{}'.format(size)
                 try:
                     path = plugin_info[key]
                 except KeyError:
@@ -682,7 +682,7 @@
 
             plugins_set.append(plugin_info)
         if not self._plg_wids:
-            log.error(_(u"no widget plugin found"))
+            log.error(_("no widget plugin found"))
             return
 
         # we want widgets sorted by names
@@ -699,7 +699,7 @@
         elif type_ == C.PLUG_TYPE_TRANSFER:
             return self._plg_wids_transfer
         else:
-            raise KeyError(u"{} plugin type is unknown".format(type_))
+            raise KeyError("{} plugin type is unknown".format(type_))
 
     def getPluggedWidgets(self, type_=C.PLUG_TYPE_WID, except_cls=None):
         """get available widgets plugin infos
@@ -727,7 +727,7 @@
         """
         plugins_set = self._getPluginsSet(type_)
         for plugin_info in plugins_set:
-            for k, w in kwargs.iteritems():
+            for k, w in kwargs.items():
                 try:
                     if plugin_info[k] != w:
                         continue
@@ -738,9 +738,9 @@
     ## widgets handling
 
     def newWidget(self, widget):
-        log.debug(u"new widget created: {}".format(widget))
+        log.debug("new widget created: {}".format(widget))
         if isinstance(widget, quick_chat.QuickChat) and widget.type == C.CHAT_GROUP:
-            self.addNote(u"", _(u"room {} has been joined").format(widget.target))
+            self.addNote("", _("room {} has been joined").format(widget.target))
 
     def switchWidget(self, old, new):
         """Replace old widget by new one
@@ -761,15 +761,15 @@
                     break
 
         if to_change is None:
-            raise exceptions.InternalError(u"no CagouWidget found when "
-                                           u"trying to switch widget")
+            raise exceptions.InternalError("no CagouWidget found when "
+                                           "trying to switch widget")
 
         wrapper = to_change.parent
         while wrapper is not None and not(isinstance(wrapper, widgets_handler.WHWrapper)):
             wrapper = wrapper.parent
 
         if wrapper is None:
-            raise exceptions.InternalError(u"no wrapper found")
+            raise exceptions.InternalError("no wrapper found")
 
         wrapper.changeWidget(new)
         self.selected_widget = new
@@ -818,7 +818,7 @@
                 if w.parent is None and w != widget:
                     to_delete.append(w)
             for w in to_delete:
-                log.debug(u"cleaning widget: {wid}".format(wid=w))
+                log.debug("cleaning widget: {wid}".format(wid=w))
                 self.widgets.deleteWidget(w)
 
     def getOrClone(self, widget):
@@ -873,7 +873,7 @@
             plg_infos = [p for p in self.getPluggedWidgets()
                          if action in p['import_name']][0]
         except IndexError:
-            log.warning(u"No plugin widget found to do {action}".format(action=action))
+            log.warning("No plugin widget found to do {action}".format(action=action))
         else:
             factory = plg_infos['factory']
             self.switchWidget(None,
@@ -885,7 +885,7 @@
         main_menu = self.app.root.root_menus
         self.menus.addMenus(backend_menus)
         self.menus.addMenu(C.MENU_GLOBAL,
-                           (_(u"Help"), _(u"About")),
+                           (_("Help"), _("About")),
                            callback=main_menu.onAbout)
         main_menu.update(C.MENU_GLOBAL)
 
@@ -902,9 +902,9 @@
                 widget.onOTRState(state, dest_jid, profile)
 
     def _debugHandler(self, action, parameters, profile):
-        if action == u"visible_widgets_dump":
+        if action == "visible_widgets_dump":
             from pprint import pformat
-            log.info(u"Visible widgets dump:\n{data}".format(
+            log.info("Visible widgets dump:\n{data}".format(
                 data=pformat(self._visible_widgets)))
         else:
             return super(Cagou, self)._debugHandler(action, parameters, profile)
@@ -916,11 +916,11 @@
         self.bridge.menusGet("", C.NO_SECURITY_LIMIT, callback=self._menusGetCb)
 
     def setPresenceStatus(self, show='', status=None, profile=C.PROF_KEY_NONE):
-        log.info(u"Profile presence status set to {show}/{status}".format(show=show,
+        log.info("Profile presence status set to {show}/{status}".format(show=show,
                                                                           status=status))
 
     def errback(self, failure_, title=_('error'),
-                message=_(u'error while processing: {msg}')):
+                message=_('error while processing: {msg}')):
         self.addNote(title, message.format(msg=failure_), level=C.XMLUI_DATA_LVL_WARNING)
 
     def addNote(self, title, message, level=C.XMLUI_DATA_LVL_INFO, symbol=None,
@@ -982,10 +982,11 @@
                                        )
             self.addNotifWidget(wid)
         else:
-            log.warning(_(u"unknown dialog type: {dialog_type}").format(dialog_type=type))
+            log.warning(_("unknown dialog type: {dialog_type}").format(dialog_type=type))
 
 
-    def desktop_notif(self, message, title=u'', duration=5000):
+    def desktop_notif(self, message, title='', duration=5000):
+        global notification
         if notification is not None:
             try:
                 notification.notify(title=title,
@@ -994,7 +995,6 @@
                                     app_icon=self.app.icon,
                                     timeout = duration)
             except Exception as e:
-                log.warning(_(u"Can't use notifications, disabling: {msg}").format(
+                log.warning(_("Can't use notifications, disabling: {msg}").format(
                     msg = e))
-                global notification
                 notification = None
--- a/cagou/core/cagou_widget.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/cagou_widget.py	Tue Aug 13 19:14:22 2019 +0200
@@ -76,7 +76,7 @@
         G.host.switchWidget(self, new_widget)
 
     def onHeaderInput(self):
-        log.info(u"header input text entered")
+        log.info("header input text entered")
 
     def onHeaderInputComplete(self, wid, text):
         return
--- a/cagou/core/common.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/common.py	Tue Aug 13 19:14:22 2019 +0200
@@ -33,7 +33,7 @@
 
 log = logging.getLogger(__name__)
 
-UNKNOWN_SYMBOL = u'Unknown symbol name'
+UNKNOWN_SYMBOL = 'Unknown symbol name'
 
 
 class IconButton(ButtonBehavior, Image):
@@ -96,9 +96,9 @@
         try:
             code = self.symbol_map[symbol]
         except KeyError:
-            log.warning(_(u"Invalid symbol {symbol}").format(symbol=symbol))
+            log.warning(_("Invalid symbol {symbol}").format(symbol=symbol))
         else:
-            self.text = unichr(code)
+            self.text = chr(code)
 
 
 class SymbolButton(ButtonBehavior, Symbol):
--- a/cagou/core/common_widgets.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/common_widgets.py	Tue Aug 13 19:14:22 2019 +0200
@@ -53,7 +53,7 @@
 
     @property
     def name(self):
-        return self.identities.values()[0].values()[0][0]
+        return list(self.identities.values())[0].values()[0][0]
 
 
 class ItemWidget(TouchMenuItemBehaviour, BoxLayout):
@@ -66,14 +66,14 @@
     def __init__(self, main_wid, entity_jid, identities, **kw):
         self.entity_jid = entity_jid
         self.identities = identities
-        own_jid = next(G.host.profiles.itervalues()).whoami
+        own_jid = next(iter(G.host.profiles.values())).whoami
         self.own_device = entity_jid.bare == own_jid
         if self.own_device:
             name = self.identities.name
         elif self.entity_jid.node:
             name = self.entity_jid.node
         elif self.entity_jid == own_jid.domain:
-            name = _(u"your server")
+            name = _("your server")
         else:
             name = entity_jid
 
--- a/cagou/core/constants.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/constants.py	Tue Aug 13 19:14:22 2019 +0200
@@ -22,19 +22,19 @@
 
 
 class Const(constants.Const):
-    APP_NAME = u"Cagou"
+    APP_NAME = "Cagou"
     APP_VERSION = cagou.__version__
     LOG_OPT_SECTION = APP_NAME.lower()
     CONFIG_SECTION = APP_NAME.lower()
-    WID_SELECTOR = u'selector'
-    ICON_SIZES = (u'small', u'medium')  # small = 32, medium = 44
-    DEFAULT_WIDGET_ICON = u'{media}/misc/black.png'
+    WID_SELECTOR = 'selector'
+    ICON_SIZES = ('small', 'medium')  # small = 32, medium = 44
+    DEFAULT_WIDGET_ICON = '{media}/misc/black.png'
 
-    PLUG_TYPE_WID = u'wid'
-    PLUG_TYPE_TRANSFER = u'transfer'
+    PLUG_TYPE_WID = 'wid'
+    PLUG_TYPE_TRANSFER = 'transfer'
 
-    TRANSFER_UPLOAD = u"upload"
-    TRANSFER_SEND = u"send"
+    TRANSFER_UPLOAD = "upload"
+    TRANSFER_SEND = "send"
 
     COLOR_PRIM = (0.98, 0.98, 0.98, 1)
     COLOR_PRIM_LIGHT = (1, 1, 1, 1)
--- a/cagou/core/dialog.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/dialog.py	Tue Aug 13 19:14:22 2019 +0200
@@ -37,7 +37,7 @@
 
 class ConfirmDialog(BoxLayout):
     title = properties.StringProperty()
-    message = properties.StringProperty(_(u"Are you sure?"))
+    message = properties.StringProperty(_("Are you sure?"))
     # callback for no/cancel
     no_cb = properties.ObjectProperty()
     # callback for yes/ok
--- a/cagou/core/image.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/image.py	Tue Aug 13 19:14:22 2019 +0200
@@ -58,14 +58,14 @@
                     # internal decompressed representation from pillow
                     # and im.save would need processing to handle format
                     data = io.BytesIO(open(filename, "rb").read())
-                    cache_filename = u"{}.{}".format(filename,ext) # needed for kivy's Image to use cache
+                    cache_filename = "{}.{}".format(filename,ext) # needed for kivy's Image to use cache
                     self._coreimage = ci = CoreImage(data, ext=ext,
                                                      filename=cache_filename, mipmap=mipmap,
                                                      anim_delay=self.anim_delay,
                                                      keep_data=self.keep_data,
                                                      nocache=self.nocache)
                 except Exception as e:
-                    log.warning(u"Can't load image: {}".format(e))
+                    log.warning("Can't load image: {}".format(e))
                     self._coreimage = ci = None
 
             if ci:
--- a/cagou/core/kivy_hack.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/kivy_hack.py	Tue Aug 13 19:14:22 2019 +0200
@@ -27,11 +27,11 @@
     import sys
     ori_argv = sys.argv[:]
     sys.argv = sys.argv[:1]
-    from constants import Const as C
+    from .constants import Const as C
     from sat.core import log_config
     log_config.satConfigure(C.LOG_BACKEND_STANDARD, C)
 
-    import config
+    from . import config
     kivy_level = config.getConfig(C.CONFIG_SECTION, CONF_KIVY_LEVEL, 'follow').upper()
 
     # kivy handles its own loggers, we don't want that!
@@ -51,7 +51,7 @@
         kivy_logger.setLevel(kivy_level)
         kivy_logger.setLevel = lambda level: None
     else:
-        raise ValueError(u"Unknown value for {name}: {value}".format(name=CONF_KIVY_LEVEL, value=kivy_level))
+        raise ValueError("Unknown value for {name}: {value}".format(name=CONF_KIVY_LEVEL, value=kivy_level))
 
     # during import kivy set its logging stuff
     import kivy
--- a/cagou/core/menu.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/menu.py	Tue Aug 13 19:14:22 2019 +0200
@@ -39,8 +39,8 @@
 
 log = logging.getLogger(__name__)
 
-ABOUT_TITLE = _(u"About {}".format(C.APP_NAME))
-ABOUT_CONTENT = _(u"""[b]Cagou (Salut à Toi)[/b]
+ABOUT_TITLE = _("About {}".format(C.APP_NAME))
+ABOUT_CONTENT = _("""[b]Cagou (Salut à Toi)[/b]
 
 [u]cagou version[/u]:
 {version}
@@ -99,7 +99,7 @@
                 try:
                     profile = list(G.host.profiles)[0]
                 except IndexError:
-                    log.warning(u"Can't find profile")
+                    log.warning("Can't find profile")
         self.item.call(selected, profile)
 
 
@@ -162,7 +162,7 @@
                 wid = MenuItem(item=child)
                 caller.add_widget(wid)
             else:
-                log.error(u"Unknown child type: {}".format(child))
+                log.error("Unknown child type: {}".format(child))
 
     def createMenus(self, caller):
         self.clear_widgets()
@@ -273,7 +273,7 @@
         G.host.closeUI()
 
     def do_callback(self, *args, **kwargs):
-        log.warning(u"callback not implemented")
+        log.warning("callback not implemented")
 
 
 class TransferMenu(SideMenu):
@@ -281,11 +281,11 @@
     # callback will be called with path to file to transfer
     # profiles if set will be sent to transfer widget, may be used to get specific files
     profiles = properties.ObjectProperty()
-    transfer_txt = _(u"Beware! The file will be sent to your server and stay unencrypted "
-                     u"there\nServer admin(s) can see the file, and they choose how, "
-                     u"when and if it will be deleted")
-    send_txt = _(u"The file will be sent unencrypted directly to your contact "
-                 u"(without transiting by the server), except in some cases")
+    transfer_txt = _("Beware! The file will be sent to your server and stay unencrypted "
+                     "there\nServer admin(s) can see the file, and they choose how, "
+                     "when and if it will be deleted")
+    send_txt = _("The file will be sent unencrypted directly to your contact "
+                 "(without transiting by the server), except in some cases")
     items_layout = properties.ObjectProperty()
     size_hint_close = (1, 0)
     size_hint_open = (1, 0.5)
@@ -303,7 +303,7 @@
     def do_callback(self, plug_info):
         self.parent.remove_widget(self)
         if self.callback is None:
-            log.warning(u"TransferMenu callback is not set")
+            log.warning("TransferMenu callback is not set")
         else:
             wid = None
             external = plug_info.get('external', False)
@@ -327,7 +327,7 @@
     """allow to select entities from roster"""
     profiles = properties.ObjectProperty()
     layout = properties.ObjectProperty()
-    instructions = properties.StringProperty(_(u"Please select entities"))
+    instructions = properties.StringProperty(_("Please select entities"))
     filter_input = properties.ObjectProperty()
     size_hint_close = (None, 1)
     size_hint_open = (None, 1)
--- a/cagou/core/patches.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/patches.py	Tue Aug 13 19:14:22 2019 +0200
@@ -17,7 +17,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-import urllib2
+import urllib.request, urllib.error, urllib.parse
 import ssl
 
 
@@ -27,14 +27,14 @@
     ctx_no_verify.check_hostname = False
     ctx_no_verify.verify_mode = ssl.CERT_NONE
 
-    class HTTPSHandler(urllib2.HTTPSHandler):
+    class HTTPSHandler(urllib.request.HTTPSHandler):
         no_certificate_check = False
 
         def __init__(self, *args, **kwargs):
-            urllib2._HTTPSHandler_ori.__init__(self, *args, **kwargs)
+            urllib.request._HTTPSHandler_ori.__init__(self, *args, **kwargs)
             if self.no_certificate_check:
                 self._context = ctx_no_verify
 
-    urllib2._HTTPSHandler_ori = urllib2.HTTPSHandler
-    urllib2.HTTPSHandler = HTTPSHandler
-    urllib2.HTTPSHandler.no_certificate_check = True
+    urllib.request._HTTPSHandler_ori = urllib.request.HTTPSHandler
+    urllib.request.HTTPSHandler = HTTPSHandler
+    urllib.request.HTTPSHandler.no_certificate_check = True
--- a/cagou/core/profile_manager.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/profile_manager.py	Tue Aug 13 19:14:22 2019 +0200
@@ -42,12 +42,12 @@
     error_msg = properties.StringProperty('')
 
     def __init__(self, pm):
-        super(NewProfileScreen, self).__init__(name=u'new_profile')
+        super(NewProfileScreen, self).__init__(name='new_profile')
         self.pm = pm
 
     def onCreationFailure(self, failure):
-        msg = [l for l in unicode(failure).split('\n') if l][-1]
-        self.error_msg = unicode(msg)
+        msg = [l for l in str(failure).split('\n') if l][-1]
+        self.error_msg = str(msg)
 
     def onCreationSuccess(self, profile):
         self.pm.profiles_screen.reload()
@@ -68,7 +68,7 @@
         # XXX: we use XMPP password for profile password to simplify
         #      if user want to change profile password, he can do it in preferences
         G.host.bridge.profileCreate(
-            name, self.password.text, u'',
+            name, self.password.text, '',
             callback=lambda: self.onCreationSuccess(name),
             errback=self.onCreationFailure)
 
@@ -77,7 +77,7 @@
 
     def __init__(self, pm):
         self.pm = pm
-        super(DeleteProfilesScreen, self).__init__(name=u'delete_profiles')
+        super(DeleteProfilesScreen, self).__init__(name='delete_profiles')
 
     def doDelete(self):
         """This method will delete *ALL* selected profiles"""
@@ -92,7 +92,7 @@
                 self.pm.screen_manager.current = 'profiles'
 
         for profile in to_delete:
-            log.info(u"Deleteing profile [{}]".format(profile))
+            log.info("Deleteing profile [{}]".format(profile))
             G.host.bridge.asyncDeleteProfile(
                 profile, callback=deleteInc, errback=deleteInc)
 
@@ -103,7 +103,7 @@
 
     def __init__(self, pm):
         self.pm = pm
-        super(ProfilesScreen, self).__init__(name=u'profiles')
+        super(ProfilesScreen, self).__init__(name='profiles')
         self.reload()
 
     def _profilesListGetCb(self, profiles):
@@ -134,7 +134,7 @@
         self.profiles_screen = ProfilesScreen(self)
         self.new_profile_screen = NewProfileScreen(self)
         self.delete_profiles_screen = DeleteProfilesScreen(self)
-        self.xmlui_screen = Screen(name=u'xmlui')
+        self.xmlui_screen = Screen(name='xmlui')
         self.screen_manager.add_widget(self.profiles_screen)
         self.screen_manager.add_widget(self.xmlui_screen)
         self.screen_manager.add_widget(self.new_profile_screen)
--- a/cagou/core/simple_xhtml.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/simple_xhtml.py	Tue Aug 13 19:14:22 2019 +0200
@@ -31,12 +31,9 @@
 import webbrowser
 
 
-class Escape(unicode):
+class Escape(str):
     """Class used to mark that a message need to be escaped"""
 
-    def __init__(self, text):
-        super(Escape, self).__init__(text)
-
 
 class SimpleXHTMLWidgetEscapedText(Label):
 
@@ -52,9 +49,9 @@
             m = sat_strings.RE_URL.search(text[idx:])
             if m is not None:
                 text_elts.append(escape_markup(m.string[0:m.start()]))
-                link_key = u'link_' + unicode(links)
+                link_key = 'link_' + str(links)
                 url = m.group()
-                text_elts.append(u'[color=5500ff][ref={link}]{url}[/ref][/color]'.format(
+                text_elts.append('[color=5500ff][ref={link}]{url}[/ref][/color]'.format(
                     link = link_key,
                     url = url
                     ))
@@ -68,7 +65,7 @@
                 if links:
                     text_elts.append(escape_markup(text[idx:]))
                     self.markup = True
-                    self.text = u''.join(text_elts)
+                    self.text = ''.join(text_elts)
                 break
 
     def on_text(self, instance, text):
@@ -107,7 +104,7 @@
         while parent and not isinstance(parent, SimpleXHTMLWidget):
             parent = parent.parent
         if parent is None:
-            log.error(u"no SimpleXHTMLWidget parent found")
+            log.error("no SimpleXHTMLWidget parent found")
         return parent
 
     def _on_source_load(self, value):
@@ -208,12 +205,12 @@
             self.splitted
         except AttributeError:
             # XXX: to make things easier, we split labels in words
-            log.debug(u"split start")
+            log.debug("split start")
             children = self.children[::-1]
             self.clear_widgets()
             for child in children:
                 if isinstance(child, Label):
-                    log.debug(u"label before split: {}".format(child.text))
+                    log.debug("label before split: {}".format(child.text))
                     styles = []
                     tag = False
                     new_text = []
@@ -228,9 +225,9 @@
                     for c in child.text:
                         if tag:
                             # we are parsing a markup tag
-                            if c == u']':
-                                current_tag_s = u''.join(current_tag)
-                                current_style = (current_tag_s, u''.join(current_value))
+                            if c == ']':
+                                current_tag_s = ''.join(current_tag)
+                                current_style = (current_tag_s, ''.join(current_value))
                                 if close:
                                     for idx, s in enumerate(reversed(styles)):
                                         if s[0] == current_tag_s:
@@ -243,9 +240,9 @@
                                 tag = False
                                 value = False
                                 close = False
-                            elif c == u'/':
+                            elif c == '/':
                                 close = True
-                            elif c == u'=':
+                            elif c == '=':
                                 value = True
                             elif value:
                                 current_value.append(c)
@@ -254,38 +251,38 @@
                             new_text.append(c)
                         else:
                             # we are parsing regular text
-                            if c == u'[':
+                            if c == '[':
                                 new_text.append(c)
                                 tag = True
-                            elif c == u' ':
+                            elif c == ' ':
                                 # new word, we do a new widget
-                                new_text.append(u' ')
+                                new_text.append(' ')
                                 for t, v in reversed(styles):
-                                    new_text.append(u'[/{}]'.format(t))
-                                current_wid.text = u''.join(new_text)
+                                    new_text.append('[/{}]'.format(t))
+                                current_wid.text = ''.join(new_text)
                                 new_text = []
                                 self.add_widget(current_wid)
-                                log.debug(u"new widget: {}".format(current_wid.text))
+                                log.debug("new widget: {}".format(current_wid.text))
                                 current_wid = self._createText()
                                 for t, v in styles:
-                                    new_text.append(u'[{tag}{value}]'.format(
+                                    new_text.append('[{tag}{value}]'.format(
                                         tag = t,
-                                        value = u'={}'.format(v) if v else u''))
+                                        value = '={}'.format(v) if v else ''))
                             else:
                                 new_text.append(c)
                     if current_wid.text:
                         # we may have a remaining widget after the parsing
                         close_styles = []
                         for t, v in reversed(styles):
-                            close_styles.append(u'[/{}]'.format(t))
-                        current_wid.text = u''.join(close_styles)
+                            close_styles.append('[/{}]'.format(t))
+                        current_wid.text = ''.join(close_styles)
                         self.add_widget(current_wid)
-                        log.debug(u"new widget: {}".format(current_wid.text))
+                        log.debug("new widget: {}".format(current_wid.text))
                 else:
                     # non Label widgets, we just add them
                     self.add_widget(child)
             self.splitted = True
-            log.debug(u"split OK")
+            log.debug("split OK")
 
         # we now set the content width
         # FIXME: for now we just use the full width
@@ -303,7 +300,7 @@
         try:
             method = getattr(self, "xhtml_{}".format(e.tag))
         except AttributeError:
-            log.warning(u"Unhandled XHTML tag: {}".format(e.tag))
+            log.warning("Unhandled XHTML tag: {}".format(e.tag))
             method = self.xhtml_generic
         method(e)
 
@@ -317,9 +314,9 @@
             should most probably be set to True
         """
         label = self._getLabel()
-        label.text += u'[{tag}{value}]'.format(
+        label.text += '[{tag}{value}]'.format(
             tag = tag,
-            value = u'={}'.format(value) if value else ''
+            value = '={}'.format(value) if value else ''
             )
         if append_to_list:
             self.styles.append((tag, value))
@@ -332,7 +329,7 @@
             should most probably be set to True
         """
         label = self._getLabel()
-        label.text += u'[/{tag}]'.format(
+        label.text += '[/{tag}]'.format(
             tag = tag
             )
         if remove_from_list:
@@ -382,19 +379,19 @@
         @param e(ET.Element): element which may have a "style" attribute
         """
         styles_limit = len(self.styles)
-        styles = e.attrib['style'].split(u';')
+        styles = e.attrib['style'].split(';')
         for style in styles:
             try:
-                prop, value = style.split(u':')
+                prop, value = style.split(':')
             except ValueError:
-                log.warning(u"can't parse style: {}".format(style))
+                log.warning("can't parse style: {}".format(style))
                 continue
-            prop = prop.strip().replace(u'-', '_')
+            prop = prop.strip().replace('-', '_')
             value = value.strip()
             try:
                 method = getattr(self, "css_{}".format(prop))
             except AttributeError:
-                log.warning(u"Unhandled CSS: {}".format(prop))
+                log.warning("Unhandled CSS: {}".format(prop))
             else:
                 method(e, value)
         self._css_styles = self.styles[styles_limit:]
@@ -419,7 +416,7 @@
         """
         # we first add markup and CSS style
         if markup is not None:
-            if isinstance(markup, basestring):
+            if isinstance(markup, str):
                 tag, value = markup, None
             else:
                 tag, value = markup
@@ -460,17 +457,17 @@
         try:
             src = elem.attrib['src']
         except KeyError:
-            log.warning(u"<img> element without src: {}".format(ET.tostring(elem)))
+            log.warning("<img> element without src: {}".format(ET.tostring(elem)))
             return
         try:
-            target_height = int(elem.get(u'height', 0))
+            target_height = int(elem.get('height', 0))
         except ValueError:
-            log.warning(u"Can't parse image height: {}".format(elem.get(u'height')))
+            log.warning("Can't parse image height: {}".format(elem.get('height')))
             target_height = 0
         try:
-            target_width = int(elem.get(u'width', 0))
+            target_width = int(elem.get('width', 0))
         except ValueError:
-            log.warning(u"Can't parse image width: {}".format(elem.get(u'width')))
+            log.warning("Can't parse image width: {}".format(elem.get('width')))
             target_width = 0
 
         img = SimpleXHTMLWidgetImage(source=src, target_height=target_height, target_width=target_width)
@@ -491,12 +488,12 @@
     # methods handling CSS properties
 
     def css_color(self, elem, value):
-        self._addStyle(u"color", css_color.parse(value))
+        self._addStyle("color", css_color.parse(value))
 
     def css_text_decoration(self, elem, value):
-        if value == u'underline':
+        if value == 'underline':
             self._addStyle('u')
-        elif value == u'line-through':
+        elif value == 'line-through':
             self._addStyle('s')
         else:
-            log.warning(u"unhandled text decoration: {}".format(value))
+            log.warning("unhandled text decoration: {}".format(value))
--- a/cagou/core/utils.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/utils.py	Tue Aug 13 19:14:22 2019 +0200
@@ -28,7 +28,7 @@
 
     def __init__(self, *args, **kwargs):
         super(FilterBehavior, self).__init__(*args, **kwargs)
-        self._filter_last = u''
+        self._filter_last = ''
         self._filter_anim = Animation(width = 0,
                                       height = 0,
                                       opacity = 0,
--- a/cagou/core/widgets_handler.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/widgets_handler.py	Tue Aug 13 19:14:22 2019 +0200
@@ -357,9 +357,9 @@
         @return: a value which will be used for sorting
         """
         try:
-            return unicode(widget.target).lower()
+            return str(widget.target).lower()
         except AttributeError:
-            return unicode(list(widget.targets)[0]).lower()
+            return str(list(widget.targets)[0]).lower()
 
     def updateHiddenSlides(self):
         """adjust carousel slides according to visible widgets"""
--- a/cagou/core/xmlui.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/core/xmlui.py	Tue Aug 13 19:14:22 2019 +0200
@@ -316,7 +316,7 @@
             while parent is not None and not isinstance(parent, AdvancedListContainer):
                 parent = parent.parent
             if parent is None:
-                log.error(u"Can't find parent AdvancedListContainer")
+                log.error("Can't find parent AdvancedListContainer")
             else:
                 if parent.selectable:
                     self.selected = parent._xmluiToggleSelected(self)
@@ -353,7 +353,7 @@
 
     def _xmluiAppend(self, widget):
         if self._current_row is None:
-            log.error(u"No row set, ignoring append")
+            log.error("No row set, ignoring append")
             return
         self._current_row.add_widget(widget)
 
@@ -589,15 +589,15 @@
             submit_btn.bind(on_press=self.onFormSubmitted)
             self.layout.add_widget(submit_btn)
             if not 'NO_CANCEL' in self.flags:
-                cancel_btn = CancelButton(text=_(u"Cancel"))
+                cancel_btn = CancelButton(text=_("Cancel"))
                 cancel_btn.bind(on_press=self.onFormCancelled)
                 self.layout.add_widget(cancel_btn)
         elif self.type == 'param':
-            self.save_btn = SaveButton(text=_(u"Save"), disabled=True)
+            self.save_btn = SaveButton(text=_("Save"), disabled=True)
             self.save_btn.bind(on_press=self._saveButtonCb)
             self.layout.add_widget(self.save_btn)
         elif self.type == 'window':
-            cancel_btn = CancelButton(text=_(u"Cancel"))
+            cancel_btn = CancelButton(text=_("Cancel"))
             cancel_btn.bind(
                 on_press=partial(self._xmluiClose, reason=C.XMLUI_DATA_CANCELLED))
             self.layout.add_widget(cancel_btn)
--- a/cagou/plugins/plugin_transfer_android_gallery.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_transfer_android_gallery.py	Tue Aug 13 19:14:22 2019 +0200
@@ -37,12 +37,12 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"gallery"),
+    "name": _("gallery"),
     "main": "AndroidGallery",
     "platforms": ('android',),
     "external": True,
-    "description": _(u"upload a photo from photo gallery"),
-    "icon_medium": u"{media}/icons/muchoslava/png/gallery_50.png",
+    "description": _("upload a photo from photo gallery"),
+    "icon_medium": "{media}/icons/muchoslava/png/gallery_50.png",
 }
 
 
@@ -61,7 +61,7 @@
         # TODO: move file dump to a thread or use async callbacks during file writting
         if requestCode == PHOTO_GALLERY and resultCode == RESULT_OK:
             if data is None:
-                log.warning(u"No data found in activity result")
+                log.warning("No data found in activity result")
                 self.cancel_cb(self, None)
                 return
             uri = data.getData()
@@ -80,7 +80,7 @@
             def cleaning():
                 os.unlink(tmp_file)
                 os.rmdir(tmp_dir)
-                log.debug(u'temporary file cleaned')
+                log.debug('temporary file cleaned')
             buff = bytearray(4096)
             with open(tmp_file, 'wb') as f:
                 while True:
--- a/cagou/plugins/plugin_transfer_android_photo.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_transfer_android_photo.py	Tue Aug 13 19:14:22 2019 +0200
@@ -34,12 +34,12 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"take photo"),
+    "name": _("take photo"),
     "main": "AndroidPhoto",
     "platforms": ('android',),
     "external": True,
-    "description": _(u"upload a photo from photo application"),
-    "icon_medium": u"{media}/icons/muchoslava/png/camera_off_50.png",
+    "description": _("upload a photo from photo application"),
+    "icon_medium": "{media}/icons/muchoslava/png/camera_off_50.png",
 }
 
 
@@ -51,7 +51,7 @@
         filename = time.strftime("%Y-%m-%d_%H:%M:%S.jpg", time.gmtime())
         tmp_dir = self.getTmpDir()
         tmp_file = os.path.join(tmp_dir, filename)
-        log.debug(u"Picture will be saved to {}".format(tmp_file))
+        log.debug("Picture will be saved to {}".format(tmp_file))
         camera.take_picture(tmp_file, self.callback)
         # we don't delete the file, as it is nice to keep it locally
 
--- a/cagou/plugins/plugin_transfer_android_video.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_transfer_android_video.py	Tue Aug 13 19:14:22 2019 +0200
@@ -34,12 +34,12 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"take video"),
+    "name": _("take video"),
     "main": "AndroidVideo",
     "platforms": ('android',),
     "external": True,
-    "description": _(u"upload a video from video application"),
-    "icon_medium": u"{media}/icons/muchoslava/png/film_camera_off_50.png",
+    "description": _("upload a video from video application"),
+    "icon_medium": "{media}/icons/muchoslava/png/film_camera_off_50.png",
 }
 
 
@@ -51,7 +51,7 @@
         filename = time.strftime("%Y-%m-%d_%H:%M:%S.mpg", time.gmtime())
         tmp_dir = self.getTmpDir()
         tmp_file = os.path.join(tmp_dir, filename)
-        log.debug(u"Video will be saved to {}".format(tmp_file))
+        log.debug("Video will be saved to {}".format(tmp_file))
         camera.take_video(tmp_file, self.callback)
         # we don't delete the file, as it is nice to keep it locally
 
--- a/cagou/plugins/plugin_transfer_file.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_transfer_file.py	Tue Aug 13 19:14:22 2019 +0200
@@ -31,10 +31,10 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"file"),
+    "name": _("file"),
     "main": "FileTransmitter",
-    "description": _(u"transmit a local file"),
-    "icon_medium": u"{media}/icons/muchoslava/png/fichier_50.png",
+    "description": _("transmit a local file"),
+    "icon_medium": "{media}/icons/muchoslava/png/fichier_50.png",
 }
 
 
@@ -66,7 +66,7 @@
                                            cancel_cb=partial(self.cancel_cb, self)))
 
     def _nativeFileChooser(self, *args, **kwargs):
-        title=_(u"Please select a file to upload")
+        title=_("Please select a file to upload")
         files = filechooser.open_file(title=title,
                                       path=self.default_path,
                                       multiple=False,
--- a/cagou/plugins/plugin_transfer_voice.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_transfer_voice.py	Tue Aug 13 19:14:22 2019 +0200
@@ -31,11 +31,11 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"voice"),
+    "name": _("voice"),
     "main": "VoiceRecorder",
     "platforms": ["android"],
-    "description": _(u"transmit a voice record"),
-    "icon_medium": u"{media}/icons/muchoslava/png/micro_off_50.png",
+    "description": _("transmit a voice record"),
+    "icon_medium": "{media}/icons/muchoslava/png/micro_off_50.png",
 }
 
 
@@ -67,7 +67,7 @@
             except Exception as e:
                 # an exception can happen if record is pressed
                 # repeatedly in a short time (not a normal use)
-                log.warning(u"Exception on stop: {}".format(e))
+                log.warning("Exception on stop: {}".format(e))
             self._counter_timer.cancel()
             self.time = self.time + 1
         else:
@@ -99,7 +99,7 @@
             except Exception as e:
                 # an exception can happen in the same situation
                 # as for audio.stop() above (i.e. bad record)
-                log.warning(u"Exception on play: {}".format(e))
+                log.warning("Exception on play: {}".format(e))
                 self.time = 0
                 return
 
--- a/cagou/plugins/plugin_wid_chat.kv	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_wid_chat.kv	Tue Aug 13 19:14:22 2019 +0200
@@ -185,7 +185,7 @@
         size_hint: 1, 1
         padding: dp(5), dp(10)
         color: 0, 0, 0, 1
-        bold: True
+        bold: root.bold
         background_normal: app.expand('{media}/misc/borders/border_filled_black.png')
         background_color: app.c_sec if root.selected else app.c_prim_dark
         on_release: root.dispatch("on_release")
--- a/cagou/plugins/plugin_wid_chat.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_wid_chat.py	Tue Aug 13 19:14:22 2019 +0200
@@ -46,10 +46,10 @@
 log = logging.getLogger(__name__)
 
 PLUGIN_INFO = {
-    "name": _(u"chat"),
+    "name": _("chat"),
     "main": "Chat",
-    "description": _(u"instant messaging with one person or a group"),
-    "icon_symbol": u"chat",
+    "description": _("instant messaging with one person or a group"),
+    "icon_symbol": "chat",
 }
 
 # FIXME: OTR specific code is legacy, and only used nowadays for lock color
@@ -99,9 +99,9 @@
         return self.mess_data.main_message
 
     def _set_message(self, message):
-        if message == self.mess_data.message.get(u""):
+        if message == self.mess_data.message.get(""):
             return False
-        self.mess_data.message = {u"": message}
+        self.mess_data.message = {"": message}
         return True
 
     message = properties.AliasProperty(_get_message, _set_message)
@@ -135,7 +135,7 @@
             self.avatar.source = update_dict['avatar']
         if 'status' in update_dict:
             status = update_dict['status']
-            self.delivery.text =  u'\u2714' if status == 'delivered' else u''
+            self.delivery.text =  '\u2714' if status == 'delivered' else ''
 
 
 class SendButton(SymbolButton):
@@ -191,13 +191,13 @@
 
     def on_select(self, menu):
         if menu == 'bookmark':
-            G.host.bridge.menuLaunch(C.MENU_GLOBAL, (u"groups", u"bookmarks"),
+            G.host.bridge.menuLaunch(C.MENU_GLOBAL, ("groups", "bookmarks"),
                                      {}, C.NO_SECURITY_LIMIT, self.chat.profile,
                                      callback=partial(
                                         G.host.actionManager, profile=self.chat.profile),
                                      errback=G.host.errback)
         else:
-            raise exceptions.InternalError(u"Unknown menu: {}".format(menu))
+            raise exceptions.InternalError("Unknown menu: {}".format(menu))
 
 
 class ExtraButton(SymbolButton):
@@ -253,11 +253,12 @@
     text = properties.StringProperty()
     trust_button = properties.BooleanProperty(False)
     best_width = properties.NumericProperty(0)
+    bold = properties.BooleanProperty(True)
 
     def __init__(self, **kwargs):
+        super(EncryptionButton, self).__init__(**kwargs)
         self.register_event_type('on_release')
         self.register_event_type('on_trust_release')
-        super(EncryptionButton, self).__init__(**kwargs)
         if self.trust_button:
             self.add_widget(TrustManagementButton())
 
@@ -279,7 +280,7 @@
         self.chat = chat
         super(EncryptionMenu, self).__init__(**kwargs)
         btn = EncryptionButton(
-            text=_(u"unencrypted (plain text)"),
+            text=_("unencrypted (plain text)"),
             on_release=self.unencrypted,
             selected=True,
             bold=False,
@@ -287,7 +288,7 @@
         self.add_widget(btn)
         for plugin in G.host.encryption_plugins:
             btn = EncryptionButton(
-                text=plugin[u'name'],
+                text=plugin['name'],
                 on_release=partial(self.startEncryption, plugin=plugin),
                 on_trust_release=partial(self.getTrustUI, plugin=plugin),
                 trust_button=True,
@@ -296,35 +297,35 @@
             log.info("added encryption: {}".format(plugin['name']))
 
     def messageEncryptionStopCb(self):
-        log.info(_(u"Session with {destinee} is now in plain text").format(
+        log.info(_("Session with {destinee} is now in plain text").format(
             destinee = self.chat.target))
 
     def messageEncryptionStopEb(self, failure_):
-        msg = _(u"Error while stopping encryption with {destinee}: {reason}").format(
+        msg = _("Error while stopping encryption with {destinee}: {reason}").format(
             destinee = self.chat.target,
             reason = failure_)
         log.warning(msg)
-        G.host.addNote(_(u"encryption problem"), msg, C.XMLUI_DATA_LVL_ERROR)
+        G.host.addNote(_("encryption problem"), msg, C.XMLUI_DATA_LVL_ERROR)
 
     def unencrypted(self, button):
         self.dismiss()
         G.host.bridge.messageEncryptionStop(
-            unicode(self.chat.target),
+            str(self.chat.target),
             self.chat.profile,
             callback=self.messageEncryptionStopCb,
             errback=self.messageEncryptionStopEb)
 
     def messageEncryptionStartCb(self, plugin):
-        log.info(_(u"Session with {destinee} is now encrypted with {encr_name}").format(
+        log.info(_("Session with {destinee} is now encrypted with {encr_name}").format(
             destinee = self.chat.target,
             encr_name = plugin['name']))
 
     def messageEncryptionStartEb(self, failure_):
-        msg = _(u"Session can't be encrypted with {destinee}: {reason}").format(
+        msg = _("Session can't be encrypted with {destinee}: {reason}").format(
             destinee = self.chat.target,
             reason = failure_)
         log.warning(msg)
-        G.host.addNote(_(u"encryption problem"), msg, C.XMLUI_DATA_LVL_ERROR)
+        G.host.addNote(_("encryption problem"), msg, C.XMLUI_DATA_LVL_ERROR)
 
     def startEncryption(self, button, plugin):
         """Request encryption with given plugin for this session
@@ -334,7 +335,7 @@
         """
         self.dismiss()
         G.host.bridge.messageEncryptionStart(
-            unicode(self.chat.target),
+            str(self.chat.target),
             plugin['namespace'],
             True,
             self.chat.profile,
@@ -347,10 +348,10 @@
         xml_ui.show()
 
     def encryptionTrustUIGetEb(self, failure_):
-        msg = _(u"Trust manager interface can't be retrieved: {reason}").format(
+        msg = _("Trust manager interface can't be retrieved: {reason}").format(
             reason = failure_)
         log.warning(msg)
-        G.host.addNote(_(u"encryption trust management problem"), msg,
+        G.host.addNote(_("encryption trust management problem"), msg,
                        C.XMLUI_DATA_LVL_ERROR)
 
     def getTrustUI(self, button, plugin):
@@ -361,7 +362,7 @@
         """
         self.dismiss()
         G.host.bridge.encryptionTrustUIGet(
-            unicode(self.chat.target),
+            str(self.chat.target),
             plugin['namespace'],
             self.chat.profile,
             callback=self.encryptionTrustUIGetCb,
@@ -371,8 +372,8 @@
         self.dismiss()
         G.host.launchMenu(
             C.MENU_SINGLE,
-            (u"otr", u"start/refresh"),
-            {u'jid': unicode(self.chat.target)},
+            ("otr", "start/refresh"),
+            {'jid': str(self.chat.target)},
             None,
             C.NO_SECURITY_LIMIT,
             self.chat.profile
@@ -382,8 +383,8 @@
         self.dismiss()
         G.host.launchMenu(
             C.MENU_SINGLE,
-            (u"otr", u"end session"),
-            {u'jid': unicode(self.chat.target)},
+            ("otr", "end session"),
+            {'jid': str(self.chat.target)},
             None,
             C.NO_SECURITY_LIMIT,
             self.chat.profile
@@ -393,8 +394,8 @@
         self.dismiss()
         G.host.launchMenu(
             C.MENU_SINGLE,
-            (u"otr", u"authenticate"),
-            {u'jid': unicode(self.chat.target)},
+            ("otr", "authenticate"),
+            {'jid': str(self.chat.target)},
             None,
             C.NO_SECURITY_LIMIT,
             self.chat.profile
@@ -425,14 +426,11 @@
         self.extra_menu = ExtraMenu(chat=self)
         extra_btn = ExtraButton(chat=self)
         self.headerInputAddExtra(extra_btn)
-        self.header_input.hint_text = u"{}".format(target)
+        self.header_input.hint_text = "{}".format(target)
         self.postInit()
 
-    def __unicode__(self):
-        return u"Chat({})".format(self.target)
-
     def __str__(self):
-        return self.__unicode__().encode('utf-8')
+        return "Chat({})".format(self.target)
 
     def __repr__(self):
         return self.__str__()
@@ -441,7 +439,7 @@
     def factory(cls, plugin_info, target, profiles):
         profiles = list(profiles)
         if len(profiles) > 1:
-            raise NotImplementedError(u"Multi-profiles is not available yet for chat")
+            raise NotImplementedError("Multi-profiles is not available yet for chat")
         if target is None:
             target = G.host.profiles[profiles[0]].whoami
         return G.host.widgets.getOrCreateWidget(cls, target, on_new_widget=None,
@@ -467,23 +465,23 @@
     def onHeaderInput(self):
         text = self.header_input.text.strip()
         try:
-            if text.count(u'@') != 1 or text.count(u' '):
+            if text.count('@') != 1 or text.count(' '):
                 raise ValueError
             jid_ = jid.JID(text)
         except ValueError:
-            log.info(u"entered text is not a jid")
+            log.info("entered text is not a jid")
             return
 
         def discoCb(disco):
             # TODO: check if plugin XEP-0045 is activated
             if "conference" in [i[0] for i in disco[1]]:
-                G.host.bridge.mucJoin(unicode(jid_), "", "", self.profile,
+                G.host.bridge.mucJoin(str(jid_), "", "", self.profile,
                                       callback=self._mucJoinCb, errback=self._mucJoinEb)
             else:
                 self.changeWidget(jid_)
 
         def discoEb(failure):
-            log.warning(u"Disco failure, ignore this text: {}".format(failure))
+            log.warning("Disco failure, ignore this text: {}".format(failure))
 
         G.host.bridge.discoInfos(jid_.domain, self.profile, callback=discoCb,
                                  errback=discoEb)
@@ -526,7 +524,7 @@
             dropdown.clear_widgets()
 
             for jid_, jid_data in comp_data:
-                nick = jid_data.get(u'nick', u'')
+                nick = jid_data.get('nick', '')
                 if text in jid_.bare or text in nick.lower():
                     btn = JidButton(
                         jid = jid_.bare,
@@ -554,7 +552,7 @@
     def _onHistoryPrinted(self):
         """Refresh or scroll down the focus after the history is printed"""
         # self.adapter.data = self.messages
-        for mess_data in self.messages.itervalues():
+        for mess_data in self.messages.values():
             self.appendMessage(mess_data)
         super(Chat, self)._onHistoryPrinted()
 
@@ -572,7 +570,7 @@
         self.notify(mess_data)
 
     def _get_notif_msg(self, mess_data):
-        return _(u"{nick}: {message}").format(
+        return _("{nick}: {message}").format(
             nick=mess_data.nick,
             message=mess_data.main_message)
 
@@ -597,14 +595,14 @@
                 notif_msg = self._get_notif_msg(mess_data)
                 G.host.desktop_notif(
                     notif_msg,
-                    title=_(u"private message"))
+                    title=_("private message"))
                 if not is_visible:
                     G.host.addNote(
-                        _(u"private message"),
+                        _("private message"),
                         notif_msg,
-                        symbol = u"chat",
+                        symbol = "chat",
                         action = {
-                            "action": u'chat',
+                            "action": 'chat',
                             "target": self.target,
                             "profiles": self.profiles}
                         )
@@ -612,18 +610,18 @@
             if mess_data.mention and not mess_data.history:
                 notif_msg = self._get_notif_msg(mess_data)
                 G.host.addNote(
-                    _(u"mention"),
+                    _("mention"),
                     notif_msg,
-                    symbol = u"chat",
+                    symbol = "chat",
                     action = {
-                        "action": u'chat',
+                        "action": 'chat',
                         "target": self.target,
                         "profiles": self.profiles}
                     )
                 if not Window.focus:
                     G.host.desktop_notif(
                         notif_msg,
-                        title=_(u"mention ({room_jid})").format(
+                        title=_("mention ({room_jid})").format(
                             room_jid=self.target)
                         )
 
@@ -640,9 +638,9 @@
     def fileTransferEb(self, err_msg, cleaning_cb, profile):
         if cleaning_cb is not None:
             cleaning_cb()
-        msg = _(u"can't transfer file: {reason}").format(reason=err_msg)
+        msg = _("can't transfer file: {reason}").format(reason=err_msg)
         log.warning(msg)
-        G.host.addNote(_(u"File transfer error"),
+        G.host.addNote(_("File transfer error"),
                        msg,
                        level=C.XMLUI_DATA_LVL_WARNING)
 
@@ -653,9 +651,9 @@
         # FIXME: Q&D way of getting file type, upload plugins shouls give it
         mime_type = mimetypes.guess_type(metadata['url'])[0]
         if mime_type is not None:
-            if mime_type.split(u'/')[0] == 'image':
+            if mime_type.split('/')[0] == 'image':
                 # we generate url ourselves, so this formatting is safe
-                extra['xhtml'] = u"<img src='{url}' />".format(**metadata)
+                extra['xhtml'] = "<img src='{url}' />".format(**metadata)
 
         G.host.messageSend(
             self.target,
@@ -682,28 +680,28 @@
                     profile = self.profile,
                     ),
                 errback = partial(G.host.errback,
-                                  message=_(u"can't upload file: {msg}"))
+                                  message=_("can't upload file: {msg}"))
             )
         elif transfer_type == C.TRANSFER_SEND:
             if self.type == C.CHAT_GROUP:
-                log.warning(u"P2P transfer is not possible for group chat")
+                log.warning("P2P transfer is not possible for group chat")
                 # TODO: show an error dialog to user, or better hide the send button for
                 #       MUC
             else:
                 jid_ = self.target
                 if not jid_.resource:
                     jid_ = G.host.contact_lists[self.profile].getFullJid(jid_)
-                G.host.bridge.fileSend(unicode(jid_), file_path, "", "", {},
+                G.host.bridge.fileSend(str(jid_), file_path, "", "", {},
                                        profile=self.profile)
                 # TODO: notification of sending/failing
         else:
-            raise log.error(u"transfer of type {} are not handled".format(transfer_type))
+            raise log.error("transfer of type {} are not handled".format(transfer_type))
 
     def messageEncryptionStarted(self, plugin_data):
         quick_chat.QuickChat.messageEncryptionStarted(self, plugin_data)
         self.encryption_btn.symbol = SYMBOL_ENCRYPTED
         self.encryption_btn.color = COLOR_ENCRYPTED
-        self.encryption_btn.selectAlgo(plugin_data[u'name'])
+        self.encryption_btn.selectAlgo(plugin_data['name'])
 
     def messageEncryptionStopped(self, plugin_data):
         quick_chat.QuickChat.messageEncryptionStopped(self, plugin_data)
@@ -718,7 +716,7 @@
         self.changeWidget(jid_)
 
     def _mucJoinEb(self, failure):
-        log.warning(u"Can't join room: {}".format(failure))
+        log.warning("Can't join room: {}".format(failure))
 
     def onOTRState(self, state, dest_jid, profile):
         assert profile in self.profiles
@@ -727,7 +725,7 @@
         elif state in OTR_STATE_TRUST:
             self.otr_state_trust = state
         else:
-            log.error(_(u"Unknown OTR state received: {}".format(state)))
+            log.error(_("Unknown OTR state received: {}".format(state)))
             return
         self.encryption_btn.symbol = self.encryption_btn.getSymbol()
         self.encryption_btn.color = self.encryption_btn.getColor()
--- a/cagou/plugins/plugin_wid_contact_list.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_wid_contact_list.py	Tue Aug 13 19:14:22 2019 +0200
@@ -38,10 +38,10 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"contacts"),
+    "name": _("contacts"),
     "main": "ContactList",
-    "description": _(u"list of contacts"),
-    "icon_medium": u"{media}/icons/muchoslava/png/contact_list_no_border_blue_44.png"
+    "description": _("list of contacts"),
+    "icon_medium": "{media}/icons/muchoslava/png/contact_list_no_border_blue_44.png"
 }
 
 
@@ -53,7 +53,7 @@
     def __init__(self, **kwargs):
         super(AddContactMenu, self).__init__(**kwargs)
         if self.profile is None:
-            log.warning(_(u"profile not set in AddContactMenu"))
+            log.warning(_("profile not set in AddContactMenu"))
             self.profile = next(iter(G.host.profiles))
 
     def addContact(self, contact_jid):
@@ -67,15 +67,15 @@
         if not contact_jid or not re.match(r"[^@ ]+@[^@ ]+", contact_jid):
             return
         contact_jid = jid.JID(contact_jid).bare
-        G.host.bridge.addContact(unicode(contact_jid),
+        G.host.bridge.addContact(str(contact_jid),
             self.profile,
             callback=lambda: G.host.addNote(
-                _(u"contact request"),
-                _(u"a contact request has been sent to {contact_jid}").format(
+                _("contact request"),
+                _("a contact request has been sent to {contact_jid}").format(
                     contact_jid=contact_jid)),
             errback=partial(G.host.errback,
-                title=_(u"can't add contact"),
-                message=_(u"error while trying to add contact: {msg}")))
+                title=_("can't add contact"),
+                message=_("error while trying to add contact: {msg}")))
 
 
 class DelContactMenu(SideMenu):
@@ -88,15 +88,15 @@
 
     def do_delete_contact(self):
         self.hide()
-        G.host.bridge.delContact(unicode(self.contact_item.jid.bare),
+        G.host.bridge.delContact(str(self.contact_item.jid.bare),
         self.contact_item.profile,
         callback=lambda: G.host.addNote(
-            _(u"contact removed"),
-            _(u"{contact_jid} has been removed from your contacts list").format(
+            _("contact removed"),
+            _("{contact_jid} has been removed from your contacts list").format(
                 contact_jid=self.contact_item.jid.bare)),
         errback=partial(G.host.errback,
-            title=_(u"can't remove contact"),
-            message=_(u"error while trying to remove contact: {msg}")))
+            title=_("can't remove contact"),
+            message=_("error while trying to remove contact: {msg}")))
 
 
 
@@ -117,11 +117,11 @@
         assert self.profile
         # XXX: for now clicking on an item launch the corresponding Chat widget
         #      behaviour should change in the future
-        G.host.doAction(u'chat', jid.JID(self.jid), [self.profile])
+        G.host.doAction('chat', jid.JID(self.jid), [self.profile])
 
     def getMenuChoices(self):
         choices = []
-        choices.append(dict(text=_(u'delete'),
+        choices.append(dict(text=_('delete'),
                             index=len(choices)+1,
                             callback=self.main_wid.removeContact))
         return choices
@@ -180,7 +180,7 @@
         if type_ == None or type_ == C.UPDATE_STRUCTURE:
             log.debug("full contact list update")
             self.layout.clear_widgets()
-            for bare_jid, data in self.items_sorted.iteritems():
+            for bare_jid, data in self.items_sorted.items():
                 wid = ContactItem(profile=profile, data=data, jid=bare_jid, main_wid=self)
                 self.layout.add_widget(wid)
                 self._wid_map[(profile, bare_jid)] = wid
--- a/cagou/plugins/plugin_wid_file_sharing.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_wid_file_sharing.py	Tue Aug 13 19:14:22 2019 +0200
@@ -42,22 +42,22 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"file sharing"),
+    "name": _("file sharing"),
     "main": "FileSharing",
-    "description": _(u"share/transfer files between devices"),
-    "icon_symbol": u"exchange",
+    "description": _("share/transfer files between devices"),
+    "icon_symbol": "exchange",
 }
-MODE_VIEW = u"view"
-MODE_LOCAL = u"local"
-SELECT_INSTRUCTIONS = _(u"Please select entities to share with")
+MODE_VIEW = "view"
+MODE_LOCAL = "local"
+SELECT_INSTRUCTIONS = _("Please select entities to share with")
 
 if kivy_utils.platform == "android":
     from jnius import autoclass
     Environment = autoclass("android.os.Environment")
     base_dir = Environment.getExternalStorageDirectory().getAbsolutePath()
     def expanduser(path):
-        if path == u'~' or path.startswith(u'~/'):
-            return path.replace(u'~', base_dir, 1)
+        if path == '~' or path.startswith('~/'):
+            return path.replace('~', base_dir, 1)
         return path
 else:
     expanduser = os.path.expanduser
@@ -72,11 +72,11 @@
 
     def on_mode(self, parent, new_mode):
         if new_mode == MODE_VIEW:
-            self.text = _(u"view shared files")
+            self.text = _("view shared files")
         elif new_mode == MODE_LOCAL:
-            self.text = _(u"share local files")
+            self.text = _("share local files")
         else:
-            exceptions.InternalError(u"Unknown mode: {mode}".format(mode=new_mode))
+            exceptions.InternalError("Unknown mode: {mode}".format(mode=new_mode))
 
 
 class PathWidget(ItemWidget):
@@ -84,8 +84,8 @@
     def __init__(self, filepath, main_wid, **kw):
         name = os.path.basename(filepath)
         self.filepath = os.path.normpath(filepath)
-        if self.filepath == u'.':
-            self.filepath = u''
+        if self.filepath == '.':
+            self.filepath = ''
         super(PathWidget, self).__init__(name=name, main_wid=main_wid, **kw)
 
     @property
@@ -97,7 +97,7 @@
             self.main_wid.current_dir = self.filepath
 
     def open_menu(self, touch, dt):
-        log.debug(_(u"opening menu for {path}").format(path=self.filepath))
+        log.debug(_("opening menu for {path}").format(path=self.filepath))
         super(PathWidget, self).open_menu(touch, dt)
 
 
@@ -110,11 +110,11 @@
     def getMenuChoices(self):
         choices = []
         if self.shared:
-            choices.append(dict(text=_(u'unshare'),
+            choices.append(dict(text=_('unshare'),
                                 index=len(choices)+1,
                                 callback=self.main_wid.unshare))
         else:
-            choices.append(dict(text=_(u'share'),
+            choices.append(dict(text=_('share'),
                                 index=len(choices)+1,
                                 callback=self.main_wid.share))
         return choices
@@ -132,8 +132,8 @@
 
     def do_item_action(self, touch):
         if self.is_dir:
-            if self.filepath == u'..':
-                self.main_wid.remote_entity = u''
+            if self.filepath == '..':
+                self.main_wid.remote_entity = ''
             else:
                 super(RemotePathWidget, self).do_item_action(touch)
         else:
@@ -144,7 +144,7 @@
 
     def do_item_action(self, touch):
         self.main_wid.remote_entity = self.entity_jid
-        self.main_wid.remote_dir = u''
+        self.main_wid.remote_dir = ''
 
 
 class FileSharing(quick_widgets.QuickWidget, cagou_widget.CagouWidget, FilterBehavior,
@@ -152,9 +152,9 @@
     SINGLE=False
     layout = properties.ObjectProperty()
     mode = properties.OptionProperty(MODE_VIEW, options=[MODE_VIEW, MODE_LOCAL])
-    local_dir = properties.StringProperty(expanduser(u'~'))
-    remote_dir = properties.StringProperty(u'')
-    remote_entity = properties.StringProperty(u'')
+    local_dir = properties.StringProperty(expanduser('~'))
+    remote_dir = properties.StringProperty('')
+    remote_entity = properties.StringProperty('')
     shared_paths = properties.ListProperty()
     signals_registered = False
 
@@ -208,12 +208,12 @@
         self.update_view(None, self.local_dir)
 
     def onHeaderInput(self):
-        if u'/' in self.header_input.text or self.header_input.text == u'~':
+        if '/' in self.header_input.text or self.header_input.text == '~':
             self.current_dir = expanduser(self.header_input.text)
 
     def onHeaderInputComplete(self, wid, text, **kwargs):
         """we filter items when text is entered in input box"""
-        if u'/' in text:
+        if '/' in text:
             return
         self.do_filter(self.layout.children,
                        text,
@@ -221,7 +221,7 @@
                        width_cb=lambda c: c.base_width,
                        height_cb=lambda c: c.minimum_height,
                        continue_tests=[lambda c: not isinstance(c, ItemWidget),
-                                       lambda c: c.name == u'..'])
+                                       lambda c: c.name == '..'])
 
 
     ## remote sharing callback ##
@@ -229,14 +229,14 @@
     def _discoFindByFeaturesCb(self, data):
         entities_services, entities_own, entities_roster = data
         for entities_map, title in ((entities_services,
-                                     _(u'services')),
+                                     _('services')),
                                     (entities_own,
-                                     _(u'your devices')),
+                                     _('your devices')),
                                     (entities_roster,
-                                     _(u'your contacts devices'))):
+                                     _('your contacts devices'))):
             if entities_map:
                 self.layout.add_widget(CategorySeparator(text=title))
-                for entity_str, entity_ids in entities_map.iteritems():
+                for entity_str, entity_ids in entities_map.items():
                     entity_jid = jid.JID(entity_str)
                     item = SharingDeviceWidget(
                         self, entity_jid, Identities(entity_ids))
@@ -246,39 +246,39 @@
                 size_hint=(1, 1),
                 halign='center',
                 text_size=self.size,
-                text=_(u"No sharing device found")))
+                text=_("No sharing device found")))
 
     def discover_devices(self):
         """Looks for devices handling file "File Information Sharing" and display them"""
         try:
             namespace = self.host.ns_map['fis']
         except KeyError:
-            msg = _(u"can't find file information sharing namespace, "
-                    u"is the plugin running?")
+            msg = _("can't find file information sharing namespace, "
+                    "is the plugin running?")
             log.warning(msg)
-            G.host.addNote(_(u"missing plugin"), msg, C.XMLUI_DATA_LVL_ERROR)
+            G.host.addNote(_("missing plugin"), msg, C.XMLUI_DATA_LVL_ERROR)
             return
         self.host.bridge.discoFindByFeatures(
             [namespace], [], False, True, True, True, False, self.profile,
             callback=self._discoFindByFeaturesCb,
             errback=partial(G.host.errback,
-                title=_(u"shared folder error"),
-                message=_(u"can't check sharing devices: {msg}")))
+                title=_("shared folder error"),
+                message=_("can't check sharing devices: {msg}")))
 
     def FISListCb(self, files_data):
         for file_data in files_data:
-            filepath = os.path.join(self.current_dir, file_data[u'name'])
+            filepath = os.path.join(self.current_dir, file_data['name'])
             item = RemotePathWidget(
                 filepath=filepath,
                 main_wid=self,
-                type_=file_data[u'type'])
+                type_=file_data['type'])
             self.layout.add_widget(item)
 
     def FISListEb(self, failure_):
-        self.remote_dir = u''
+        self.remote_dir = ''
         G.host.addNote(
-            _(u"shared folder error"),
-            _(u"can't list files for {remote_entity}: {msg}").format(
+            _("shared folder error"),
+            _("can't list files for {remote_entity}: {msg}").format(
                 remote_entity=self.remote_entity,
                 msg=failure_),
             level=C.XMLUI_DATA_LVL_WARNING)
@@ -287,25 +287,25 @@
 
     def update_view(self, *args):
         """update items according to current mode, entity and dir"""
-        log.debug(u'updating {}, {}'.format(self.current_dir, args))
+        log.debug('updating {}, {}'.format(self.current_dir, args))
         self.layout.clear_widgets()
-        self.header_input.text = u''
+        self.header_input.text = ''
         self.header_input.hint_text = self.current_dir
 
         if self.mode == MODE_LOCAL:
-            filepath = os.path.join(self.local_dir, u'..')
+            filepath = os.path.join(self.local_dir, '..')
             self.layout.add_widget(LocalPathWidget(filepath=filepath, main_wid=self))
             try:
                 files = sorted(os.listdir(self.local_dir))
             except OSError as e:
-                msg = _(u"can't list files in \"{local_dir}\": {msg}").format(
+                msg = _("can't list files in \"{local_dir}\": {msg}").format(
                     local_dir=self.local_dir,
                     msg=e)
                 G.host.addNote(
-                    _(u"shared folder error"),
+                    _("shared folder error"),
                     msg,
                     level=C.XMLUI_DATA_LVL_WARNING)
-                self.local_dir = expanduser(u'~')
+                self.local_dir = expanduser('~')
                 return
             for f in files:
                 filepath = os.path.join(self.local_dir, f)
@@ -317,14 +317,14 @@
             else:
                 # we always a way to go back
                 # so user can return to previous list even in case of error
-                parent_path = os.path.join(self.remote_dir, u'..')
+                parent_path = os.path.join(self.remote_dir, '..')
                 item = RemotePathWidget(
                     filepath = parent_path,
                     main_wid=self,
                     type_ = C.FILE_TYPE_DIRECTORY)
                 self.layout.add_widget(item)
                 self.host.bridge.FISList(
-                    unicode(self.remote_entity),
+                    str(self.remote_entity),
                     self.remote_dir,
                     {},
                     self.profile,
@@ -335,8 +335,8 @@
 
     def do_share(self, entities_jids, item):
         if entities_jids:
-            access = {u'read': {u'type': 'whitelist',
-                                u'jids': entities_jids}}
+            access = {'read': {'type': 'whitelist',
+                                'jids': entities_jids}}
         else:
             access = {}
 
@@ -346,11 +346,11 @@
             json.dumps(access, ensure_ascii=False),
             self.profile,
             callback=lambda name: G.host.addNote(
-                _(u"sharing folder"),
-                _(u"{name} is now shared").format(name=name)),
+                _("sharing folder"),
+                _("{name} is now shared").format(name=name)),
             errback=partial(G.host.errback,
-                title=_(u"sharing folder"),
-                message=_(u"can't share folder: {msg}")))
+                title=_("sharing folder"),
+                message=_("can't share folder: {msg}")))
 
     def share(self, menu):
         item = self.menu_item
@@ -365,16 +365,16 @@
             item.filepath,
             self.profile,
             callback=lambda: G.host.addNote(
-                _(u"sharing folder"),
-                _(u"{name} is not shared anymore").format(name=item.name)),
+                _("sharing folder"),
+                _("{name} is not shared anymore").format(name=item.name)),
             errback=partial(G.host.errback,
-                title=_(u"sharing folder"),
-                message=_(u"can't unshare folder: {msg}")))
+                title=_("sharing folder"),
+                message=_("can't unshare folder: {msg}")))
 
     def fileJingleRequestCb(self, progress_id, item, dest_path):
         G.host.addNote(
-            _(u"file request"),
-            _(u"{name} download started at {dest_path}").format(
+            _("file request"),
+            _("{name} download started at {dest_path}").format(
                 name = item.name,
                 dest_path = dest_path))
 
@@ -388,19 +388,19 @@
         assert self.remote_entity
         extra = {'path': path}
         dest_path = files_utils.get_unique_name(os.path.join(G.host.downloads_dir, name))
-        G.host.bridge.fileJingleRequest(unicode(self.remote_entity),
+        G.host.bridge.fileJingleRequest(str(self.remote_entity),
                                         dest_path,
                                         name,
-                                        u'',
-                                        u'',
+                                        '',
+                                        '',
                                         extra,
                                         self.profile,
                                         callback=partial(self.fileJingleRequestCb,
                                             item=item,
                                             dest_path=dest_path),
                                         errback=partial(G.host.errback,
-                                            title = _(u"file request error"),
-                                            message = _(u"can't request file: {msg}")))
+                                            title = _("file request error"),
+                                            message = _("can't request file: {msg}")))
 
     @classmethod
     def shared_path_new(cls, shared_path, name, profile):
@@ -414,5 +414,5 @@
             if shared_path in wid.shared_paths:
                 wid.shared_paths.remove(shared_path)
             else:
-                log.warning(_(u"shared path {path} not found in {widget}".format(
+                log.warning(_("shared path {path} not found in {widget}".format(
                     path = shared_path, widget = wid)))
--- a/cagou/plugins/plugin_wid_remote.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_wid_remote.py	Tue Aug 13 19:14:22 2019 +0200
@@ -41,13 +41,13 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"remote control"),
+    "name": _("remote control"),
     "main": "RemoteControl",
-    "description": _(u"universal remote control"),
-    "icon_symbol": u"signal",
+    "description": _("universal remote control"),
+    "icon_symbol": "signal",
 }
 
-NOTE_TITLE = _(u"Media Player Remote Control")
+NOTE_TITLE = _("Media Player Remote Control")
 
 
 class RemoteItemWidget(ItemWidget):
@@ -66,7 +66,7 @@
 class MediaPlayerControlWidget(BoxLayout):
     main_wid = properties.ObjectProperty()
     remote_item = properties.ObjectProperty()
-    status = properties.OptionProperty(u"play", options=(u"play", u"pause", u"stop"))
+    status = properties.OptionProperty("play", options=("play", "pause", "stop"))
     title = properties.StringProperty()
     identity = properties.StringProperty()
     command = properties.DictProperty()
@@ -84,34 +84,34 @@
             try:
                 setattr(self, prop.lower(), ui_tpl.widgets[prop].value)
             except KeyError:
-                log.warning(_(u"Missing field: {name}").format(name=prop))
+                log.warning(_("Missing field: {name}").format(name=prop))
         playback_status = self.ui_tpl.widgets['PlaybackStatus'].value
-        if playback_status == u"Playing":
-            self.status = u"pause"
-        elif playback_status == u"Paused":
-            self.status = u"play"
-        elif playback_status == u"Stopped":
-            self.status = u"play"
+        if playback_status == "Playing":
+            self.status = "pause"
+        elif playback_status == "Paused":
+            self.status = "play"
+        elif playback_status == "Stopped":
+            self.status = "play"
         else:
             G.host.addNote(
                 title=NOTE_TITLE,
-                message=_(u"Unknown playback status: playback_status")
+                message=_("Unknown playback status: playback_status")
                           .format(playback_status=playback_status),
                 level=C.XMLUI_DATA_LVL_WARNING)
         self.commands = {v:k for k,v in ui_tpl.widgets['command'].options}
 
     def adHocRunCb(self, xmlui_raw):
         ui_tpl = template_xmlui.create(G.host, xmlui_raw)
-        data = {xmlui.XMLUIPanel.escape(u"media_player"): self.remote_item.node,
-                u"session_id": ui_tpl.session_id}
+        data = {xmlui.XMLUIPanel.escape("media_player"): self.remote_item.node,
+                "session_id": ui_tpl.session_id}
         G.host.bridge.launchAction(
             ui_tpl.submit_id, data, self.profile,
             callback=self.updateUI,
             errback=self.main_wid.errback)
 
     def on_remote_item(self, __, remote):
-        NS_MEDIA_PLAYER = G.host.ns_map[u"mediaplayer"]
-        G.host.bridge.adHocRun(unicode(remote.device_jid), NS_MEDIA_PLAYER, self.profile,
+        NS_MEDIA_PLAYER = G.host.ns_map["mediaplayer"]
+        G.host.bridge.adHocRun(str(remote.device_jid), NS_MEDIA_PLAYER, self.profile,
                                callback=self.adHocRunCb,
                                errback=self.main_wid.errback)
 
@@ -121,15 +121,15 @@
         except KeyError:
             G.host.addNote(
                 title=NOTE_TITLE,
-                message=_(u"{command} command is not managed").format(command=command),
+                message=_("{command} command is not managed").format(command=command),
                 level=C.XMLUI_DATA_LVL_WARNING)
         else:
-            data = {xmlui.XMLUIPanel.escape(u"command"): cmd_value,
-                    u"session_id": self.ui_tpl.session_id}
+            data = {xmlui.XMLUIPanel.escape("command"): cmd_value,
+                    "session_id": self.ui_tpl.session_id}
             # hidden values are normally transparently managed by XMLUIPanel
             # but here we have to add them by hand
             hidden = {xmlui.XMLUIPanel.escape(k):v
-                      for k,v in self.ui_tpl.hidden.iteritems()}
+                      for k,v in self.ui_tpl.hidden.items()}
             data.update(hidden)
             G.host.bridge.launchAction(
                 self.ui_tpl.submit_id, data, self.profile,
@@ -140,9 +140,9 @@
 class RemoteDeviceWidget(DeviceWidget):
 
     def xmluiCb(self, data, cb_id, profile):
-        if u'xmlui' in data:
+        if 'xmlui' in data:
             xml_ui = xmlui.create(
-                G.host, data[u'xmlui'], callback=self.xmluiCb, profile=profile)
+                G.host, data['xmlui'], callback=self.xmluiCb, profile=profile)
             if isinstance(xml_ui, xmlui.XMLUIDialog):
                 self.main_wid.showRootWidget()
                 xml_ui.show()
@@ -151,7 +151,7 @@
                 self.main_wid.layout.add_widget(xml_ui)
         else:
             if data:
-                log.warning(_(u"Unhandled data: {data}").format(data=data))
+                log.warning(_("Unhandled data: {data}").format(data=data))
             self.main_wid.showRootWidget()
 
     def onClose(self, __, reason):
@@ -167,7 +167,7 @@
 
     def do_item_action(self, touch):
         self.main_wid.layout.clear_widgets()
-        G.host.bridge.adHocRun(unicode(self.entity_jid), u'', self.profile,
+        G.host.bridge.adHocRun(str(self.entity_jid), '', self.profile,
             callback=self.adHocRunCb, errback=self.main_wid.errback)
 
 
@@ -194,7 +194,7 @@
         """Generic errback which add a warning note and go back to root widget"""
         G.host.addNote(
             title=NOTE_TITLE,
-            message=_(u"Can't use remote control: {reason}").format(reason=failure_),
+            message=_("Can't use remote control: {reason}").format(reason=failure_),
             level=C.XMLUI_DATA_LVL_WARNING)
         self.showRootWidget()
 
@@ -218,8 +218,8 @@
             self.show_devices(found)
 
     def adHocRemotesGetEb(self, failure_, found):
-        G.host.errback(failure_, title=_(u"discovery error"),
-                       message=_(u"can't check remote controllers: {msg}"))
+        G.host.errback(failure_, title=_("discovery error"),
+                       message=_("can't check remote controllers: {msg}"))
         found.insert(0, [])
         if len(found) == 2:
             self.show_devices(found)
@@ -236,8 +236,8 @@
             self.show_devices(found)
 
     def _discoFindByFeaturesEb(self, failure_, found):
-        G.host.errback(failure_, title=_(u"discovery error"),
-                       message=_(u"can't check devices: {msg}"))
+        G.host.errback(failure_, title=_("discovery error"),
+                       message=_("can't check devices: {msg}"))
         found.append(({}, {}, {}))
         if len(found) == 2:
             self.show_devices(found)
@@ -247,9 +247,9 @@
         try:
             namespace = self.host.ns_map['commands']
         except KeyError:
-            msg = _(u"can't find ad-hoc commands namespace, is the plugin running?")
+            msg = _("can't find ad-hoc commands namespace, is the plugin running?")
             log.warning(msg)
-            G.host.addNote(_(u"missing plugin"), msg, C.XMLUI_DATA_LVL_ERROR)
+            G.host.addNote(_("missing plugin"), msg, C.XMLUI_DATA_LVL_ERROR)
             return
         self.host.bridge.discoFindByFeatures(
             [namespace], [], False, True, True, True, False, self.profile,
@@ -259,7 +259,7 @@
     def show_devices(self, found):
         remotes_data, (entities_services, entities_own, entities_roster) = found
         if remotes_data:
-            title = _(u"media players remote controls")
+            title = _("media players remote controls")
             self.stack_layout.add_widget(CategorySeparator(text=title))
 
         for remote_data in remotes_data:
@@ -268,14 +268,14 @@
             self.stack_layout.add_widget(wid)
 
         for entities_map, title in ((entities_services,
-                                     _(u'services')),
+                                     _('services')),
                                     (entities_own,
-                                     _(u'your devices')),
+                                     _('your devices')),
                                     (entities_roster,
-                                     _(u'your contacts devices'))):
+                                     _('your contacts devices'))):
             if entities_map:
                 self.stack_layout.add_widget(CategorySeparator(text=title))
-                for entity_str, entity_ids in entities_map.iteritems():
+                for entity_str, entity_ids in entities_map.items():
                     entity_jid = jid.JID(entity_str)
                     item = RemoteDeviceWidget(
                         self, entity_jid, Identities(entity_ids))
@@ -286,4 +286,4 @@
                 size_hint=(1, 1),
                 halign='center',
                 text_size=self.size,
-                text=_(u"No sharing device found")))
+                text=_("No sharing device found")))
--- a/cagou/plugins/plugin_wid_settings.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_wid_settings.py	Tue Aug 13 19:14:22 2019 +0200
@@ -30,10 +30,10 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"settings"),
+    "name": _("settings"),
     "main": "CagouSettings",
-    "description": _(u"Cagou/SàT settings"),
-    "icon_symbol": u"wrench",
+    "description": _("Cagou/SàT settings"),
+    "icon_symbol": "wrench",
 }
 
 
@@ -60,7 +60,7 @@
 
     def getParamsUIEb(self, failure):
         self.changeWidget(Label(
-            text=_(u"Can't load parameters!"),
+            text=_("Can't load parameters!"),
             bold=True,
             color=(1,0,0,1)))
-        G.host.showDialog(u"Can't load params UI", failure, "error")
+        G.host.showDialog("Can't load params UI", failure, "error")
--- a/cagou/plugins/plugin_wid_widget_selector.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/cagou/plugins/plugin_wid_widget_selector.py	Tue Aug 13 19:14:22 2019 +0200
@@ -31,11 +31,11 @@
 
 
 PLUGIN_INFO = {
-    "name": _(u"widget selector"),
+    "name": _("widget selector"),
     "import_name": C.WID_SELECTOR,
     "main": "WidgetSelector",
-    "description": _(u"show available widgets and allow to select one"),
-    "icon_medium": u"{media}/icons/muchoslava/png/selector_no_border_blue_44.png"
+    "description": _("show available widgets and allow to select one"),
+    "icon_medium": "{media}/icons/muchoslava/png/selector_no_border_blue_44.png"
 }
 
 
@@ -44,7 +44,7 @@
     item = properties.ObjectProperty()
 
     def on_release(self, *args):
-        log.debug(u"widget selection: {}".format(self.plugin_info["name"]))
+        log.debug("widget selection: {}".format(self.plugin_info["name"]))
         factory = self.plugin_info["factory"]
         G.host.switchWidget(self, factory(self.plugin_info, None, profiles=iter(G.host.profiles)))
 
--- a/setup.py	Mon Aug 05 11:21:54 2019 +0200
+++ b/setup.py	Tue Aug 13 19:14:22 2019 +0200
@@ -52,7 +52,7 @@
 
 setup(name=NAME,
       version=VERSION,
-      description=u'Desktop/Android frontend for Salut à Toi XMPP client',
+      description='Desktop/Android frontend for Salut à Toi XMPP client',
       long_description=textwrap.dedent("""\
           Cagou is a desktop/Android frontend for Salut à Toi.
           It provides native graphical interface with a modern user interface,