Mercurial > libervia-desktop-kivy
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 |