# HG changeset patch # User Goffi # Date 1578151497 -3600 # Node ID 89799148f894614e9fd9e2dfd9471b69c0b79af8 # Parent 89b17a841c2f25738dedab5445883f2e6d1f506a core: use classes and factory to handle platform specific behaviours in a generic way diff -r 89b17a841c2f -r 89799148f894 cagou/__init__.py --- a/cagou/__init__.py Fri Jan 03 15:48:59 2020 +0100 +++ b/cagou/__init__.py Sat Jan 04 16:24:57 2020 +0100 @@ -35,4 +35,5 @@ def run(): host = G._host = cagou_main.Cagou() + G.local_platform = cagou_main.local_platform host.run() diff -r 89b17a841c2f -r 89799148f894 cagou/core/cagou_main.py --- a/cagou/core/cagou_main.py Fri Jan 03 15:48:59 2020 +0100 +++ b/cagou/core/cagou_main.py Sat Jan 04 16:24:57 2020 +0100 @@ -62,8 +62,6 @@ from kivy.core.window import Window from kivy.animation import Animation 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 .share_widget import ShareWidget from . import widgets_handler @@ -89,16 +87,9 @@ ## platform specific settings ## -if kivy_utils.platform == "android": - import socket - from .platform_ import android - android.init_platform() -else: - # we don't want multi-touch emulation with mouse - - # 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') +from . import platform_ +local_platform = platform_.create() +local_platform.init_platform() ## General Configuration ## @@ -318,9 +309,7 @@ def build(self): Window.bind(on_keyboard=self.key_input) wid = CagouRootWidget(Label(text="Loading please wait")) - if sys.platform == 'android': - # we don't want menu on Android - wid.root_menus.height = 0 + local_platform.on_app_build(wid) return wid def showWidget(self): @@ -340,27 +329,16 @@ def initFrontendState(self): """Init state to handle paused/stopped/running on mobile OSes""" - if sys.platform == "android": - # XXX: we use a separated socket instead of bridge because if we - # 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(android.SOCKET_DIR, android.SOCKET_FILE)) - s.sendall(android.STATE_RUNNING) + local_platform.on_initFrontendState() def on_pause(self): - self.host.sync = False - self._frontend_status_socket.sendall(android.STATE_PAUSED) - return True + return local_platform.on_pause() def on_resume(self): - self._frontend_status_socket.sendall(android.STATE_RUNNING) - self.host.sync = True + return local_platform.on_resume() def on_stop(self): - if sys.platform == "android": - self._frontend_status_socket.sendall(android.STATE_STOPPED) - self._frontend_status_socket.close() + return local_platform.on_stop() def key_input(self, window, key, scancode, codepoint, modifier): if key == 27: @@ -449,8 +427,7 @@ patches.apply() log.warning("SSL certificate validation is disabled, this is unsecure!") - if sys.platform == 'android': - android.host_init(self) + local_platform.on_host_init(self) @property def visible_widgets(self): diff -r 89b17a841c2f -r 89799148f894 cagou/core/platform_/__init__.py --- a/cagou/core/platform_/__init__.py Fri Jan 03 15:48:59 2020 +0100 +++ b/cagou/core/platform_/__init__.py Sat Jan 04 16:24:57 2020 +0100 @@ -0,0 +1,29 @@ +#!/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 . + +from kivy import utils as kivy_utils + + +def create(): + """Factory method to create the platform instance adapted to running one""" + if kivy_utils.platform == "android": + from .android import Platform + return Platform() + else: + from .base import Platform + return Platform() diff -r 89b17a841c2f -r 89799148f894 cagou/core/platform_/android.py --- a/cagou/core/platform_/android.py Fri Jan 03 15:48:59 2020 +0100 +++ b/cagou/core/platform_/android.py Sat Jan 04 16:24:57 2020 +0100 @@ -17,12 +17,15 @@ # along with this program. If not, see . import sys +import os +import socket 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 +from .base import Platform as BasePlatform log = logging.getLogger(__name__) @@ -41,84 +44,108 @@ SOCKET_DIR = "/data/data/org.salutatoi.cagou/" SOCKET_FILE = ".socket" -# cache for callbacks to run when profile is plugged -cache = [] + +class Platform(BasePlatform): + + def __init__(self): + super().__init__() + # cache for callbacks to run when profile is plugged + self.cache = [] + + def init_platform(self): + # 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 on_app_build(self, wid): + # we don't want menu on Android + wid.root_menus.height = 0 + + def on_host_init(self, host): + argument = '' + service.start(mActivity, argument) + activity.bind(on_new_intent=self.on_new_intent) + self.cache.append((self.on_new_intent, mActivity.getIntent())) + host.addListener('profilePlugged', self.onProfilePlugged) + + def on_initFrontendState(self): + # XXX: we use a separated socket instead of bridge because if we + # 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) + + def onProfilePlugged(self, profile): + log.debug("ANDROID profilePlugged") + for method, *args in self.cache: + method(*args) + del self.cache + G.host.removeListener("profilePlugged", self.onProfilePlugged) + + def on_pause(self): + G.host.sync = False + self._frontend_status_socket.sendall(STATE_PAUSED) + return True -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 + def on_resume(self): + self._frontend_status_socket.sendall(STATE_RUNNING) + G.host.sync = True + + def on_stop(self): + self._frontend_status_socket.sendall(STATE_STOPPED) + self._frontend_status_socket.close() + + def getPathFromUri(self, 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) + + def on_new_intent(self, 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 = self.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 - 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) - + 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}") -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) + log.debug(msg) diff -r 89b17a841c2f -r 89799148f894 cagou/core/platform_/base.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cagou/core/platform_/base.py Sat Jan 04 16:24:57 2020 +0100 @@ -0,0 +1,48 @@ +#!/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 . + +from kivy.config import Config as KivyConfig + + +class Platform: + """Base class to handle platform specific behaviours""" + + def init_platform(self): + # we don't want multi-touch emulation with mouse + + # 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') + + def on_app_build(self, wid): + pass + + def on_host_init(self, host): + pass + + def on_initFrontendState(self): + pass + + def on_pause(self): + pass + + def on_resume(self): + pass + + def on_stop(self): + pass