comparison cagou/core/cagou_main.py @ 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 834d5c267219
children 5bd583d00594
comparison
equal deleted inserted replaced
321:a6eb154ba266 322:e2b51663d8b8
1 #!/usr//bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 2
4 # Cagou: desktop/mobile frontend for Salut à Toi XMPP client 3 # Cagou: desktop/mobile frontend for Salut à Toi XMPP client
5 # Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org) 4 # Copyright (C) 2016-2019 Jérôme Poisson (goffi@goffi.org)
6 5
7 # This program is free software: you can redistribute it and/or modify 6 # This program is free software: you can redistribute it and/or modify
24 from sat.core.i18n import _ 23 from sat.core.i18n import _
25 from . import kivy_hack 24 from . import kivy_hack
26 kivy_hack.do_hack() 25 kivy_hack.do_hack()
27 from .constants import Const as C 26 from .constants import Const as C
28 from sat.core import log as logging 27 from sat.core import log as logging
29 log = logging.getLogger(__name__)
30 from sat.core import exceptions 28 from sat.core import exceptions
31 from sat_frontends.quick_frontend.quick_app import QuickApp 29 from sat_frontends.quick_frontend.quick_app import QuickApp
32 from sat_frontends.quick_frontend import quick_widgets 30 from sat_frontends.quick_frontend import quick_widgets
33 from sat_frontends.quick_frontend import quick_chat 31 from sat_frontends.quick_frontend import quick_chat
34 from sat_frontends.quick_frontend import quick_utils 32 from sat_frontends.quick_frontend import quick_utils
35 from sat_frontends.tools import jid 33 from sat_frontends.tools import jid
36 from sat.tools import utils as sat_utils 34 from sat.tools import utils as sat_utils
37 from sat.tools import config 35 from sat.tools import config
38 from sat.tools.common import dynamic_import 36 from sat.tools.common import dynamic_import
39 import kivy 37 import kivy
40 kivy.require('1.10.0') 38 kivy.require('1.11.0')
41 import kivy.support 39 import kivy.support
42 main_config = config.parseMainConf() 40 main_config = config.parseMainConf()
43 bridge_name = config.getConfig(main_config, '', 'bridge', 'dbus') 41 bridge_name = config.getConfig(main_config, '', 'bridge', 'dbus')
44 # FIXME: event loop is choosen according to bridge_name, a better way should be used 42 # FIXME: event loop is choosen according to bridge_name, a better way should be used
45 if 'dbus' in bridge_name: 43 if 'dbus' in bridge_name:
63 from kivy.animation import Animation 61 from kivy.animation import Animation
64 from kivy.metrics import dp 62 from kivy.metrics import dp
65 from kivy import utils as kivy_utils 63 from kivy import utils as kivy_utils
66 from kivy.config import Config as KivyConfig 64 from kivy.config import Config as KivyConfig
67 from .cagou_widget import CagouWidget 65 from .cagou_widget import CagouWidget
66 from .share_widget import ShareWidget
68 from . import widgets_handler 67 from . import widgets_handler
69 from .common import IconButton 68 from .common import IconButton
70 from . import menu 69 from . import menu
71 from . import dialog 70 from . import dialog
72 from importlib import import_module 71 from importlib import import_module
73 import sat 72 import sat
74 import cagou 73 import cagou
75 import cagou.plugins 74 import cagou.plugins
76 import cagou.kv 75 import cagou.kv
76
77
78 log = logging.getLogger(__name__)
79
80
77 try: 81 try:
78 from plyer import notification 82 from plyer import notification
79 except ImportError: 83 except ImportError:
80 notification = None 84 notification = None
81 log.warning(_("Can't import plyer, some features disabled")) 85 log.warning(_("Can't import plyer, some features disabled"))
83 87
84 ## platform specific settings ## 88 ## platform specific settings ##
85 89
86 if kivy_utils.platform == "android": 90 if kivy_utils.platform == "android":
87 import socket 91 import socket
88 92 from .platform_ import android
89 # FIXME: move to separate android module 93 android.init_platform()
90 # sys.platform is "linux" on android by default 94 else:
91 # so we change it to allow backend to detect android 95 # we don't want multi-touch emulation with mouse
92 sys.platform = "android" 96
93 C.PLUGIN_EXT = 'pyc' 97 # this option doesn't make sense on Android and cause troubles, so we only activate
94 SOCKET_DIR = "/data/data/org.salutatoi.cagou/" 98 # it for other platforms (cf. https://github.com/kivy/kivy/issues/6229)
95 SOCKET_FILE = ".socket" 99 KivyConfig.set('input', 'mouse', 'mouse,disable_multitouch')
96 STATE_RUNNING = b"running"
97 STATE_PAUSED = b"paused"
98 STATE_STOPPED = b"stopped"
99 100
100 101
101 ## General Configuration ## 102 ## General Configuration ##
102 103
103 # we want white background by default 104 # we want white background by default
104 Window.clearcolor = (1, 1, 1, 1) 105 Window.clearcolor = (1, 1, 1, 1)
105 # we don't want multi-touch emulation with mouse
106 if sys.platform != 'android':
107 # this option doesn't make sense on Android and cause troubles
108 # cf. https://github.com/kivy/kivy/issues/6229
109 KivyConfig.set('input', 'mouse', 'mouse,disable_multitouch')
110 106
111 107
112 class NotifsIcon(IconButton): 108 class NotifsIcon(IconButton):
113 notifs = properties.ListProperty() 109 notifs = properties.ListProperty()
114 110
345 if sys.platform == "android": 341 if sys.platform == "android":
346 # XXX: we use a separated socket instead of bridge because if we 342 # XXX: we use a separated socket instead of bridge because if we
347 # try to call a bridge method in on_pause method, the call data 343 # try to call a bridge method in on_pause method, the call data
348 # is not written before the actual pause 344 # is not written before the actual pause
349 s = self._frontend_status_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 345 s = self._frontend_status_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
350 s.connect(os.path.join(SOCKET_DIR, SOCKET_FILE)) 346 s.connect(os.path.join(android.SOCKET_DIR, android.SOCKET_FILE))
351 s.sendall(STATE_RUNNING) 347 s.sendall(android.STATE_RUNNING)
352 348
353 def on_pause(self): 349 def on_pause(self):
354 self.host.sync = False 350 self.host.sync = False
355 self._frontend_status_socket.sendall(STATE_PAUSED) 351 self._frontend_status_socket.sendall(android.STATE_PAUSED)
356 return True 352 return True
357 353
358 def on_resume(self): 354 def on_resume(self):
359 self._frontend_status_socket.sendall(STATE_RUNNING) 355 self._frontend_status_socket.sendall(android.STATE_RUNNING)
360 self.host.sync = True 356 self.host.sync = True
361 357
362 def on_stop(self): 358 def on_stop(self):
363 if sys.platform == "android": 359 if sys.platform == "android":
364 self._frontend_status_socket.sendall(STATE_STOPPED) 360 self._frontend_status_socket.sendall(android.STATE_STOPPED)
365 self._frontend_status_socket.close() 361 self._frontend_status_socket.close()
366 362
367 def key_input(self, window, key, scancode, codepoint, modifier): 363 def key_input(self, window, key, scancode, codepoint, modifier):
368 if key == 27: 364 if key == 27:
369 # we disable [esc] handling, because default action is to quit app 365 # we disable [esc] handling, because default action is to quit app
370 return True 366 return True
371 elif key == 292: 367 elif key == 292:
372 # F11: full screen 368 # F11: full screen
373 if not Window.fullscreen: 369 if not Window.fullscreen:
374 window.fullscreen = 'auto' 370 Window.fullscreen = 'auto'
375 else: 371 else:
376 window.fullscreen = False 372 Window.fullscreen = False
377 return True 373 return True
378 elif key == 109 and modifier == ['alt']: 374 elif key == 109 and modifier == ['alt']:
379 # M-m we hide/show menu 375 # M-m we hide/show menu
380 menu = self.root.root_menus 376 menu = self.root.root_menus
381 if menu.height: 377 if menu.height:
401 397
402 def __init__(self): 398 def __init__(self):
403 if bridge_name == 'embedded': 399 if bridge_name == 'embedded':
404 from sat.core import sat_main 400 from sat.core import sat_main
405 self.sat = sat_main.SAT() 401 self.sat = sat_main.SAT()
406 if sys.platform == 'android':
407 from jnius import autoclass
408 service = autoclass('org.salutatoi.cagou.ServiceBackend')
409 mActivity = autoclass('org.kivy.android.PythonActivity').mActivity
410 argument = ''
411 service.start(mActivity, argument)
412 self.service = service
413 402
414 bridge_module = dynamic_import.bridge(bridge_name, 'sat_frontends.bridge') 403 bridge_module = dynamic_import.bridge(bridge_name, 'sat_frontends.bridge')
415 if bridge_module is None: 404 if bridge_module is None:
416 log.error(f"Can't import {bridge_name} bridge") 405 log.error(f"Can't import {bridge_name} bridge")
417 sys.exit(3) 406 sys.exit(3)
456 if not self.tls_validation: 445 if not self.tls_validation:
457 from cagou.core import patches 446 from cagou.core import patches
458 patches.apply() 447 patches.apply()
459 log.warning("SSL certificate validation is disabled, this is unsecure!") 448 log.warning("SSL certificate validation is disabled, this is unsecure!")
460 449
450 if sys.platform == 'android':
451 android.host_init(self)
452
461 @property 453 @property
462 def visible_widgets(self): 454 def visible_widgets(self):
463 for w_list in self._visible_widgets.values(): 455 for w_list in self._visible_widgets.values():
464 for w in w_list: 456 for w in w_list:
465 yield w 457 yield w
952 self.app.root.changeWidget(widget, "extra") 944 self.app.root.changeWidget(widget, "extra")
953 self.app.root.show("extra") 945 self.app.root.show("extra")
954 946
955 def closeUI(self): 947 def closeUI(self):
956 self.app.root.show() 948 self.app.root.show()
949 screen = self.app.root._manager.get_screen("extra")
950 screen.clear_widgets()
957 951
958 def getDefaultAvatar(self, entity=None): 952 def getDefaultAvatar(self, entity=None):
959 return self.app.default_avatar 953 return self.app.default_avatar
960 954
961 def _dialog_cb(self, cb, *args, **kwargs): 955 def _dialog_cb(self, cb, *args, **kwargs):
982 ) 976 )
983 self.addNotifWidget(wid) 977 self.addNotifWidget(wid)
984 else: 978 else:
985 log.warning(_("unknown dialog type: {dialog_type}").format(dialog_type=type)) 979 log.warning(_("unknown dialog type: {dialog_type}").format(dialog_type=type))
986 980
981 def share(self, media_type, data):
982 share_wid = ShareWidget(media_type=media_type, data=data)
983 try:
984 self.showExtraUI(share_wid)
985 except Exception as e:
986 log.error(e)
987 self.closeUI()
987 988
988 def desktop_notif(self, message, title='', duration=5000): 989 def desktop_notif(self, message, title='', duration=5000):
989 global notification 990 global notification
990 if notification is not None: 991 if notification is not None:
991 try: 992 try:
996 timeout = duration) 997 timeout = duration)
997 except Exception as e: 998 except Exception as e:
998 log.warning(_("Can't use notifications, disabling: {msg}").format( 999 log.warning(_("Can't use notifications, disabling: {msg}").format(
999 msg = e)) 1000 msg = e))
1000 notification = None 1001 notification = None
1002
1003 def getAncestorWidget(self, wid, cls):
1004 """Retrieve an ancestor of given class
1005
1006 @param wid(Widget): current widget
1007 @param cls(type): class of the ancestor to retrieve
1008 @return (Widget, None): found instance or None
1009 """
1010 parent = wid.parent
1011 while parent and parent.__class__ != cls:
1012 parent = parent.parent
1013 return parent