changeset 322:e2b51663d8b8

core, android: new share widget + added Cagou to "share" menu: - new intent filter to add Cagou to share menu for all media types - minimum Kivy version is now 1.11.0 - new "Share" widget to display data to share via SàT and select the target - new core.platform_ module (the suffix "_" avoid trouble with standard "platform" module), for platform specific code. - Android intent are now checked on startup and "on_new_intent" events - if a android.intent.action.SEND action is received (i.e. some data is shared), the "Share" widget is shown - new Cagou.share method to share data using "Share" widget - new Cagou.getAncestorWidget method to easily retrieve an instance of a specific class in a widget's ancestors - ContactList's Avatar and ContactItem widgets have been moved to core.common
author Goffi <goffi@goffi.org>
date Fri, 06 Dec 2019 13:23:03 +0100
parents a6eb154ba266
children 5bd583d00594
files .p4a .p4a_blacklist android_intents/share.xml cagou/core/cagou_main.py cagou/core/common.py cagou/core/constants.py cagou/core/menu.py cagou/core/platform_/__init__.py cagou/core/platform_/android.py cagou/kv/common.kv cagou/kv/share_widget.kv cagou/plugins/plugin_wid_chat.kv cagou/plugins/plugin_wid_chat.py cagou/plugins/plugin_wid_contact_list.kv cagou/plugins/plugin_wid_contact_list.py cagou/plugins/plugin_wid_remote.py
diffstat 15 files changed, 352 insertions(+), 82 deletions(-) [+]
line wrap: on
line diff
--- a/.p4a	Fri Dec 06 13:23:03 2019 +0100
+++ b/.p4a	Fri Dec 06 13:23:03 2019 +0100
@@ -21,4 +21,5 @@
 --permission RECORD_AUDIO
 --permission READ_EXTERNAL_STORAGE
 --permission WRITE_EXTERNAL_STORAGE
+--intent-filters /home/goffi/dev/cagou/android_intents/share.xml
 --ignore-setup-py
--- a/.p4a_blacklist	Fri Dec 06 13:23:03 2019 +0100
+++ b/.p4a_blacklist	Fri Dec 06 13:23:03 2019 +0100
@@ -3,3 +3,4 @@
 *_old
 *_old/*
 *.tar.bz2
+android_intents
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/android_intents/share.xml	Fri Dec 06 13:23:03 2019 +0100
@@ -0,0 +1,5 @@
+<intent-filter>
+    <action android:name="android.intent.action.SEND" />
+    <category android:name="android.intent.category.DEFAULT" />
+    <data android:mimeType="*/*" />
+</intent-filter>
--- a/cagou/core/cagou_main.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/core/cagou_main.py	Fri Dec 06 13:23:03 2019 +0100
@@ -1,5 +1,4 @@
-#!/usr//bin/env python2
-# -*- coding: utf-8 -*-
+#!/usr/bin/env python3
 
 # Cagou: desktop/mobile frontend for Salut à Toi XMPP client
 # Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org)
@@ -26,7 +25,6 @@
 kivy_hack.do_hack()
 from .constants import Const as C
 from sat.core import log as logging
-log = logging.getLogger(__name__)
 from sat.core import exceptions
 from sat_frontends.quick_frontend.quick_app import QuickApp
 from sat_frontends.quick_frontend import quick_widgets
@@ -37,7 +35,7 @@
 from sat.tools import config
 from sat.tools.common import dynamic_import
 import kivy
-kivy.require('1.10.0')
+kivy.require('1.11.0')
 import kivy.support
 main_config = config.parseMainConf()
 bridge_name = config.getConfig(main_config, '', 'bridge', 'dbus')
@@ -65,6 +63,7 @@
 from kivy import utils as kivy_utils
 from kivy.config import Config as KivyConfig
 from .cagou_widget import CagouWidget
+from .share_widget import ShareWidget
 from . import widgets_handler
 from .common import IconButton
 from . import menu
@@ -74,6 +73,11 @@
 import cagou
 import cagou.plugins
 import cagou.kv
+
+
+log = logging.getLogger(__name__)
+
+
 try:
     from plyer import notification
 except ImportError:
@@ -85,28 +89,20 @@
 
 if kivy_utils.platform == "android":
     import socket
+    from .platform_ import android
+    android.init_platform()
+else:
+    # we don't want multi-touch emulation with mouse
 
-    # FIXME: move to separate android module
-    # sys.platform is "linux" on android by default
-    # so we change it to allow backend to detect android
-    sys.platform = "android"
-    C.PLUGIN_EXT = 'pyc'
-    SOCKET_DIR = "/data/data/org.salutatoi.cagou/"
-    SOCKET_FILE = ".socket"
-    STATE_RUNNING = b"running"
-    STATE_PAUSED = b"paused"
-    STATE_STOPPED = b"stopped"
+    # this option doesn't make sense on Android and cause troubles, so we only activate
+    # it for other platforms (cf. https://github.com/kivy/kivy/issues/6229)
+    KivyConfig.set('input', 'mouse', 'mouse,disable_multitouch')
 
 
 ## General Configuration ##
 
 # we want white background by default
 Window.clearcolor = (1, 1, 1, 1)
-# we don't want multi-touch emulation with mouse
-if sys.platform != 'android':
-    # this option doesn't make sense on Android and cause troubles
-    # cf. https://github.com/kivy/kivy/issues/6229
-    KivyConfig.set('input', 'mouse', 'mouse,disable_multitouch')
 
 
 class NotifsIcon(IconButton):
@@ -347,21 +343,21 @@
             #      try to call a bridge method in on_pause method, the call data
             #      is not written before the actual pause
             s = self._frontend_status_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
-            s.connect(os.path.join(SOCKET_DIR, SOCKET_FILE))
-            s.sendall(STATE_RUNNING)
+            s.connect(os.path.join(android.SOCKET_DIR, android.SOCKET_FILE))
+            s.sendall(android.STATE_RUNNING)
 
     def on_pause(self):
         self.host.sync = False
-        self._frontend_status_socket.sendall(STATE_PAUSED)
+        self._frontend_status_socket.sendall(android.STATE_PAUSED)
         return True
 
     def on_resume(self):
-        self._frontend_status_socket.sendall(STATE_RUNNING)
+        self._frontend_status_socket.sendall(android.STATE_RUNNING)
         self.host.sync = True
 
     def on_stop(self):
         if sys.platform == "android":
-            self._frontend_status_socket.sendall(STATE_STOPPED)
+            self._frontend_status_socket.sendall(android.STATE_STOPPED)
             self._frontend_status_socket.close()
 
     def key_input(self, window, key, scancode, codepoint, modifier):
@@ -371,9 +367,9 @@
         elif key == 292:
             # F11: full screen
             if not Window.fullscreen:
-                window.fullscreen = 'auto'
+                Window.fullscreen = 'auto'
             else:
-                window.fullscreen = False
+                Window.fullscreen = False
             return True
         elif key == 109 and modifier == ['alt']:
             # M-m we hide/show menu
@@ -403,13 +399,6 @@
         if bridge_name == 'embedded':
             from sat.core import sat_main
             self.sat = sat_main.SAT()
-        if sys.platform == 'android':
-            from jnius import autoclass
-            service = autoclass('org.salutatoi.cagou.ServiceBackend')
-            mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
-            argument = ''
-            service.start(mActivity, argument)
-            self.service = service
 
         bridge_module = dynamic_import.bridge(bridge_name, 'sat_frontends.bridge')
         if bridge_module is None:
@@ -458,6 +447,9 @@
             patches.apply()
             log.warning("SSL certificate validation is disabled, this is unsecure!")
 
+        if sys.platform == 'android':
+            android.host_init(self)
+
     @property
     def visible_widgets(self):
         for w_list in self._visible_widgets.values():
@@ -954,6 +946,8 @@
 
     def closeUI(self):
         self.app.root.show()
+        screen = self.app.root._manager.get_screen("extra")
+        screen.clear_widgets()
 
     def getDefaultAvatar(self, entity=None):
         return self.app.default_avatar
@@ -984,6 +978,13 @@
         else:
             log.warning(_("unknown dialog type: {dialog_type}").format(dialog_type=type))
 
+    def share(self, media_type, data):
+        share_wid = ShareWidget(media_type=media_type, data=data)
+        try:
+            self.showExtraUI(share_wid)
+        except Exception as e:
+            log.error(e)
+            self.closeUI()
 
     def desktop_notif(self, message, title='', duration=5000):
         global notification
@@ -998,3 +999,15 @@
                 log.warning(_("Can't use notifications, disabling: {msg}").format(
                     msg = e))
                 notification = None
+
+    def getAncestorWidget(self, wid, cls):
+        """Retrieve an ancestor of given class
+
+        @param wid(Widget): current widget
+        @param cls(type): class of the ancestor to retrieve
+        @return (Widget, None): found instance or None
+        """
+        parent = wid.parent
+        while parent and parent.__class__ != cls:
+            parent = parent.parent
+        return parent
--- a/cagou/core/common.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/core/common.py	Fri Dec 06 13:23:03 2019 +0100
@@ -25,6 +25,7 @@
 from kivy.uix.behaviors import ButtonBehavior
 from kivy.uix.behaviors import ToggleButtonBehavior
 from kivy.uix.boxlayout import BoxLayout
+from kivy.metrics import dp
 from cagou.core.constants import Const as C
 from kivy import properties
 from cagou import G
@@ -40,6 +41,17 @@
     pass
 
 
+class Avatar(Image):
+    pass
+
+
+class ContactItem(BoxLayout):
+    base_width = dp(150)
+    profile = properties.StringProperty()
+    data = properties.DictProperty()
+    jid = properties.StringProperty('')
+
+
 class JidItem(BoxLayout):
     bg_color = properties.ListProperty([0.2, 0.2, 0.2, 1])
     color = properties.ListProperty([1, 1, 1, 1])
--- a/cagou/core/constants.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/core/constants.py	Fri Dec 06 13:23:03 2019 +0100
@@ -20,6 +20,8 @@
 from sat_frontends.quick_frontend import constants
 import cagou
 
+# Kivy must not be imported here due to log hijacking see core/kivy_hack.py
+
 
 class Const(constants.Const):
     APP_NAME = "Cagou"
@@ -30,6 +32,8 @@
     ICON_SIZES = ('small', 'medium')  # small = 32, medium = 44
     DEFAULT_WIDGET_ICON = '{media}/misc/black.png'
 
+    BTN_HEIGHT = '35dp'
+
     PLUG_TYPE_WID = 'wid'
     PLUG_TYPE_TRANSFER = 'transfer'
 
--- a/cagou/core/menu.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/core/menu.py	Fri Dec 06 13:23:03 2019 +0100
@@ -312,9 +312,10 @@
                     self._closeUI(wid)
                 self.callback(
                     file_path,
-                    cleaning_cb,
                     transfer_type = (C.TRANSFER_UPLOAD
-                        if self.ids['upload_btn'].state == "down" else C.TRANSFER_SEND))
+                        if self.ids['upload_btn'].state == "down" else C.TRANSFER_SEND),
+                    cleaning_cb=cleaning_cb,
+                )
             wid = plug_info['factory'](plug_info,
                                        onTransferCb,
                                        self.cancel_cb,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cagou/core/platform_/android.py	Fri Dec 06 13:23:03 2019 +0100
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+
+# Cagou: desktop/mobile frontend for Salut à Toi XMPP client
+# Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# 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 sys
+from jnius import autoclass, cast
+from android import activity
+from sat.core import log as logging
+from cagou.core.constants import Const as C
+from cagou import G
+from kivy.clock import Clock
+
+
+log = logging.getLogger(__name__)
+
+service = autoclass('org.salutatoi.cagou.ServiceBackend')
+mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
+ImagesMedia = autoclass('android.provider.MediaStore$Images$Media')
+AudioMedia = autoclass('android.provider.MediaStore$Audio$Media')
+VideoMedia = autoclass('android.provider.MediaStore$Video$Media')
+DATA = '_data'
+
+
+STATE_RUNNING = b"running"
+STATE_PAUSED = b"paused"
+STATE_STOPPED = b"stopped"
+SOCKET_DIR = "/data/data/org.salutatoi.cagou/"
+SOCKET_FILE = ".socket"
+
+# cache for callbacks to run when profile is plugged
+cache = []
+
+
+def on_new_intent(intent):
+    log.debug("on_new_intent")
+    Intent = autoclass('android.content.Intent')
+    action = intent.getAction();
+    intent_type = intent.getType();
+    if action == "android.intent.action.SEND":
+        # we have receiving data to share, we parse the intent data
+        # and show the share widget
+        data = {}
+        text = intent.getStringExtra(Intent.EXTRA_TEXT)
+        if text is not None:
+            data['text'] = text
+        item = intent.getParcelableExtra(Intent.EXTRA_STREAM)
+        if item is not None:
+            uri = cast('android.net.Uri', item)
+            data['uri'] = uri.toString()
+            path = getPathFromUri(uri)
+            if path is not None:
+                data['path'] = path
+        else:
+            uri = None
+            path = None
+
+        Clock.schedule_once(lambda *args: G.host.share(intent_type, data), 0)
+    else:
+        text = None
+        uri = None
+        path = None
+
+    msg = (f"NEW INTENT RECEIVED\n"
+           f"type: {intent_type}\n"
+           f"action: {action}\n"
+           f"text: {text}\n"
+           f"uri: {uri}\n"
+           f"path: {path}")
+
+    log.debug(msg)
+
+
+def onProfilePlugged(profile):
+    log.debug("ANDROID profilePlugged")
+    global cache
+    for method, *args in cache:
+        method(*args)
+    del cache
+    G.host.removeListener("profilePlugged", onProfilePlugged)
+
+
+def init_platform():
+    # sys.platform is "linux" on android by default
+    # so we change it to allow backend to detect android
+    sys.platform = "android"
+    C.PLUGIN_EXT = 'pyc'
+
+
+def host_init(host):
+    argument = ''
+    service.start(mActivity, argument)
+
+    activity.bind(on_new_intent=on_new_intent)
+    cache.append((on_new_intent, mActivity.getIntent()))
+    host.addListener('profilePlugged', onProfilePlugged)
+
+
+def getPathFromUri(uri):
+    cursor = mActivity.getContentResolver().query(uri, None, None, None, None)
+    if cursor is None:
+        return uri.getPath()
+    else:
+        cursor.moveToFirst()
+        # FIXME: using DATA is not recommended (and DATA is deprecated)
+        # we should read directly the file with
+        # ContentResolver#openFileDescriptor(Uri, String)
+        col_idx = cursor.getColumnIndex(DATA);
+        if col_idx == -1:
+            return uri.getPath()
+        return cursor.getString(col_idx)
--- a/cagou/kv/common.kv	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/kv/common.kv	Fri Dec 06 13:23:03 2019 +0100
@@ -15,6 +15,28 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
+<ContactItem>:
+    size_hint: None, None
+    width: self.base_width
+    height: self.minimum_height
+    orientation: 'vertical'
+    Avatar:
+        id: avatar
+        size_hint: 1, None
+        height: dp(60)
+        source: root.data.get('avatar') or app.default_avatar
+        allow_stretch: True
+    Label:
+        id: jid_label
+        size_hint: None, None
+        text_size: root.base_width, None
+        size: self.texture_size
+        text: root.jid
+        bold: True
+        valign: 'middle'
+        halign: 'center'
+
+
 <JidItem>:
     size_hint: 1, None
     height: dp(68)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cagou/kv/share_widget.kv	Fri Dec 06 13:23:03 2019 +0100
@@ -0,0 +1,118 @@
+# Cagou: desktop/mobile frontend for Salut à Toi XMPP client
+# Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# 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 _ sat.core.i18n._
+#:import C cagou.core.constants.Const
+
+
+<ShareWidget>:
+    preview_box: preview_box
+    layout: layout
+    orientation: 'vertical'
+    Label:
+        size_hint: 1, None
+        text_size: self.size
+        halign: 'center'
+        text: _("share")
+        height: self.font_size + dp(5)
+        bold: True
+        font_size: '35sp'
+    BoxLayout:
+        id: preview_box
+        size_hint: 1, None
+        height: self.minimum_height
+        orientation: 'vertical'
+        text: str(root.data)
+    Label:
+        size_hint: 1, None
+        text_size: self.size
+        halign: 'center'
+        text: _("with")
+        height: self.font_size + dp(5)
+        bold: True
+        font_size: '25sp'
+    ScrollView:
+        StackLayout:
+            id: layout
+            size_hint: 1, None
+            height: self.minimum_height
+            spacing: 0
+    Button:
+        size_hint: 1, None
+        height: C.BTN_HEIGHT
+        text: _("cancel")
+        on_press: app.host.closeUI()
+
+
+<TextPreview>:
+    size_hint: 1, None
+    height: min(data.height, dp(100))
+    ScrollView
+        Label:
+            id: data
+            size_hint: 1, None
+            text: root.text
+            text_size: self.width, None
+            size: self.texture_size
+            font_size: sp(20)
+            padding_x: dp(10)
+            padding_y: dp(5)
+            halign: 'center'
+            canvas.before:
+                Color:
+                    rgba: 0.95, 0.95, 0.95, 1
+                Rectangle:
+                    pos: self.pos
+                    size: self.size
+
+<ImagePreview>:
+    reduce_layout: reduce_layout
+    reduce_checkbox: reduce_checkbox
+    size_hint: 1, None
+    height: dp(120)
+    orientation: "vertical"
+    Image:
+        source: root.path
+    BoxLayout
+        id: reduce_layout
+        size_hint: 1, None
+        padding_y: None
+        opacity: 0
+        height: 0
+        Widget:
+        CheckBox:
+            id: reduce_checkbox
+            size_hint: None, 1
+            width: dp(20)
+            active: True
+        Label:
+            size_hint: None, None
+            text: _("reduce image size")
+            text_size: None, None
+            size: self.texture_size
+            padding_x: dp(10)
+            font_size: sp(15)
+        Widget:
+
+<GenericPreview>:
+    size_hint: 1, None
+    height: dp(100)
+    Widget:
+    SymbolLabel:
+        symbol: "doc"
+        text: root.path
+    Widget:
+
--- a/cagou/plugins/plugin_wid_chat.kv	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/plugins/plugin_wid_chat.kv	Fri Dec 06 13:23:03 2019 +0100
@@ -103,6 +103,7 @@
 
 <Chat>:
     messages_widget: messages_widget
+    message_input: message_input
     ScrollView:
         scroll_y: 0
         do_scroll_x: False
--- a/cagou/plugins/plugin_wid_chat.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/plugins/plugin_wid_chat.py	Fri Dec 06 13:23:03 2019 +0100
@@ -183,7 +183,7 @@
     chat = properties.ObjectProperty()
 
     def on_release(self, *args):
-        menu.TransferMenu(callback=self.chat.onTransferOK).show(self)
+        menu.TransferMenu(callback=self.chat.transferFile).show(self)
 
 
 class ExtraMenu(DropDown):
@@ -664,11 +664,14 @@
             profile_key=profile
             )
 
-    def onTransferOK(self, file_path, cleaning_cb, transfer_type):
-        if transfer_type == C.TRANSFER_UPLOAD:
+        if cleaning_cb is not None:
+            cleaning_cb()
+
 
+    def transferFile(self, file_path, transfer_type=C.TRANSFER_UPLOAD, cleaning_cb=None):
+        if transfer_type == C.TRANSFER_UPLOAD:
             G.host.bridge.fileUpload(
-                file_path,
+                str(file_path),
                 "",
                 "",
                 {"ignore_tls_errors": C.boolConst(not G.host.tls_validation)},
@@ -691,7 +694,7 @@
                 jid_ = self.target
                 if not jid_.resource:
                     jid_ = G.host.contact_lists[self.profile].getFullJid(jid_)
-                G.host.bridge.fileSend(str(jid_), file_path, "", "", {},
+                G.host.bridge.fileSend(str(jid_), str(file_path), "", "", {},
                                        profile=self.profile)
                 # TODO: notification of sending/failing
         else:
--- a/cagou/plugins/plugin_wid_contact_list.kv	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/plugins/plugin_wid_contact_list.kv	Fri Dec 06 13:23:03 2019 +0100
@@ -74,28 +74,6 @@
     Widget:
 
 
-<ContactItem>:
-    size_hint: None, None
-    width: self.base_width
-    height: self.minimum_height
-    orientation: 'vertical'
-    Avatar:
-        id: avatar
-        size_hint: 1, None
-        height: dp(60)
-        source: root.data.get('avatar', app.default_avatar)
-        allow_stretch: True
-    Label:
-        id: jid_label
-        size_hint: None, None
-        text_size: root.base_width, None
-        size: self.texture_size
-        text: root.jid
-        bold: True
-        valign: 'middle'
-        halign: 'center'
-
-
 <ContactList>:
     float_layout: float_layout
     layout: layout
--- a/cagou/plugins/plugin_wid_contact_list.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/plugins/plugin_wid_contact_list.py	Fri Dec 06 13:23:03 2019 +0100
@@ -21,16 +21,14 @@
 from sat.core import log as logging
 log = logging.getLogger(__name__)
 from cagou.core.constants import Const as C
+from ..core.common import ContactItem
 from sat.core.i18n import _
 from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
 from sat_frontends.tools import jid
-from kivy.uix.boxlayout import BoxLayout
 from cagou.core.utils import FilterBehavior
 from cagou.core.menu import SideMenu, TouchMenuBehaviour, TouchMenuItemBehaviour
-from kivy.metrics import dp
 from kivy import properties
 from cagou.core import cagou_widget
-from cagou.core import image
 from cagou import G
 from functools import partial
 import bisect
@@ -99,19 +97,7 @@
             message=_("error while trying to remove contact: {msg}")))
 
 
-
-class Avatar(image.Image):
-    pass
-
-
-class ContactItem(TouchMenuItemBehaviour, BoxLayout):
-    base_width = dp(150)
-    profile = properties.StringProperty()
-    data = properties.DictProperty()
-    jid = properties.StringProperty('')
-
-    def __init__(self, **kwargs):
-        super(ContactItem, self).__init__(**kwargs)
+class CLContactItem(TouchMenuItemBehaviour, ContactItem):
 
     def do_item_action(self, touch):
         assert self.profile
@@ -162,14 +148,14 @@
                        )
 
     def _addContactItem(self, bare_jid, profile):
-        """Create a new ContactItem instance, and add it
+        """Create a new CLContactItem instance, and add it
 
         item will be added in a sorted position
         @param bare_jid(jid.JID): entity bare JID
         @param profile(unicode): profile where the contact is
         """
         data = G.host.contact_lists[profile].getItem(bare_jid)
-        wid = ContactItem(profile=profile, data=data, jid=bare_jid, main_wid=self)
+        wid = CLContactItem(profile=profile, data=data, jid=bare_jid, main_wid=self)
         child_jids = [c.jid for c in reversed(self.layout.children)]
         idx = bisect.bisect_right(child_jids, bare_jid)
         self.layout.add_widget(wid, -idx)
@@ -181,7 +167,7 @@
             log.debug("full contact list update")
             self.layout.clear_widgets()
             for bare_jid, data in self.items_sorted.items():
-                wid = ContactItem(profile=profile, data=data, jid=bare_jid, main_wid=self)
+                wid = CLContactItem(profile=profile, data=data, jid=bare_jid, main_wid=self)
                 self.layout.add_widget(wid)
                 self._wid_map[(profile, bare_jid)] = wid
         elif type_ == C.UPDATE_MODIFY:
--- a/cagou/plugins/plugin_wid_remote.py	Fri Dec 06 13:23:03 2019 +0100
+++ b/cagou/plugins/plugin_wid_remote.py	Fri Dec 06 13:23:03 2019 +0100
@@ -19,7 +19,6 @@
 
 
 from sat.core import log as logging
-log = logging.getLogger(__name__)
 from sat.core.i18n import _
 from sat_frontends.quick_frontend import quick_widgets
 from cagou.core import cagou_widget
@@ -40,6 +39,8 @@
 from functools import partial
 
 
+log = logging.getLogger(__name__)
+
 PLUGIN_INFO = {
     "name": _("remote control"),
     "main": "RemoteControl",