diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25e1180 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**.pyc +.vagrant/** diff --git a/android.txt b/android.txt new file mode 100644 index 0000000..da1c648 --- /dev/null +++ b/android.txt @@ -0,0 +1,3 @@ +title=Kivy Remote Shell +author=tito +orientation=portrait diff --git a/libs/garden/garden.navigationdrawer/__init__.pyc b/libs/garden/garden.navigationdrawer/__init__.pyc index 1ba01f1..ed96d26 100644 Binary files a/libs/garden/garden.navigationdrawer/__init__.pyc and b/libs/garden/garden.navigationdrawer/__init__.pyc differ diff --git a/main.py b/main.py index b7c3798..fa2c79b 100644 --- a/main.py +++ b/main.py @@ -1,44 +1,42 @@ __version__ = '0.1' # install_twisted_rector must be called before importing and using the reactor -from kivy.support import install_twisted_reactor -install_twisted_reactor() +#from kivy.support import install_twisted_reactor +#install_twisted_reactor() import socket import fcntl import struct -from twisted.internet import reactor -from twisted.cred import portal, checkers -from twisted.conch import manhole, manhole_ssh from kivy.lang import Builder from kivy.uix.floatlayout import FloatLayout -from kivy.properties import StringProperty +from kivy.properties import StringProperty, BooleanProperty from kivy.app import App +from kivy.clock import Clock +import kivy +kivy.require('1.8.0') from kivy.garden import navigationdrawer from kivy.uix.screenmanager import Screen -app = None +from service import ServiceAppMixin +import shell + +app = None -def getManholeFactory(namespace, **passwords): - realm = manhole_ssh.TerminalRealm() - def getManhole(_): - return manhole.ColoredManhole(namespace) - realm.chainedProtocolFactory.protocolFactory = getManhole - p = portal.Portal(realm) - p.registerChecker( - checkers.InMemoryUsernamePasswordDatabaseDontUse(**passwords)) - f = manhole_ssh.ConchFactory(p) - return f class MainScreen(Screen): lan_ip = StringProperty('127.0.0.1') + use_service = BooleanProperty() def __init__(self, **kwargs): super(MainScreen, self).__init__(**kwargs) - ip = socket.gethostbyname(socket.gethostname()) + try: + ip = socket.gethostbyname(socket.gethostname()) + except socket.gaierror: + ip = socket.gethostbyname(socket.gethostname() + '.local') + if ip.startswith('127.'): interfaces = ['eth0', 'eth1', 'eth2', 'wlan0', 'wlan1', 'wifi0', 'tiwlan0', 'tiwlan1', 'ath0', 'ath1', 'ppp0'] @@ -58,16 +56,37 @@ def get_interface_ip(self, ifname): struct.pack('256s', ifname[:15]) )[20:24]) + def on_use_service(self, instance, value): + app.start_shell(service=value) + -class RemoteKivyApp(App): +class RemoteKivyApp(App, ServiceAppMixin): def build(self): global app app = self - self.connection = reactor.listenTCP(8000, - getManholeFactory(globals(), admin='kivy')) def on_pause(self): return True + def on_resume(self): + return + + def on_stop(self): + if hasattr(self, 'service'): + self.stop_service() + + def start_shell(self, service=False): + if service: # For now on, use the Service! + if hasattr(self, '_twisted_connection'): + shell.uninstall_shell(service=False, connections=[self._twisted_connection]) + del self._twisted_connection + self.start_service() + + else: # For now on, use the Activity! + if hasattr(self, 'service'): + self.stop_service() + self._twisted_connection = shell.install_shell(context=globals(), service=False) + + if __name__ == '__main__': RemoteKivyApp().run() diff --git a/service/__init__.py b/service/__init__.py new file mode 100644 index 0000000..bbb898f --- /dev/null +++ b/service/__init__.py @@ -0,0 +1,37 @@ +import kivy.utils +from kivy import platform as PLATFORM + +try: # hack for Kivy pre-1.8 + PLATFORM = PLATFORM() +except TypeError: + pass + +if PLATFORM == 'android': + import android +else: + android = None + +class ServiceAppMixin(object): + def start_service(self, arg=''): + if PLATFORM == 'android': + self.service = android.AndroidService(title='Kivy Remote Shell', description='Twisted reactor running') + self.service.start(arg) # accepts an argument, handled to PYTHON_SERVICE_ARGUMENT + return True + else: + from multiprocessing import Process + def _run_service(arg=arg): + import os + os.environ['PYTHON_SERVICE_ARGUMENT'] = arg + import service.main + self.service = Process(target=_run_service) + self.service.start() + return False + + def stop_service(self): + if PLATFORM == 'android': + self.service.stop() + return True + else: + import os, signal + os.kill(self.service.pid, signal.SIGKILL) + return False diff --git a/service/main.py b/service/main.py new file mode 100644 index 0000000..80e6f2d --- /dev/null +++ b/service/main.py @@ -0,0 +1,10 @@ +import os +# Hack to allow import from main app dir: +_parentdir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +os.sys.path.insert(0, _parentdir) + +# get the argument passed +arg = os.getenv('PYTHON_SERVICE_ARGUMENT') + +import shell +shell.install_shell(context=globals(), service=True) diff --git a/shell.py b/shell.py new file mode 100644 index 0000000..aaee0c1 --- /dev/null +++ b/shell.py @@ -0,0 +1,85 @@ +#coding: utf-8 +from threading import Lock +from kivy.logger import Logger + +INSTALL_SHELL_LOCK = Lock() + + +def _block_on(d, timeout=None): + '''Blocks waiting for a deferred to return something, or fail''' + from Queue import Queue, Empty + from twisted.python.failure import Failure + + q = Queue() + d.addBoth(q.put) + try: + ret = q.get(True, timeout) + except Empty: + raise Empty + if isinstance(ret, Failure): + ret.raiseException() + else: + return ret + + +def getManholeFactory(namespace, **passwords): + from twisted.cred import portal, checkers + from twisted.conch import manhole, manhole_ssh + + realm = manhole_ssh.TerminalRealm() + def getManhole(_): + return manhole.ColoredManhole(namespace) + realm.chainedProtocolFactory.protocolFactory = getManhole + p = portal.Portal(realm) + p.registerChecker( + checkers.InMemoryUsernamePasswordDatabaseDontUse(**passwords)) + f = manhole_ssh.ConchFactory(p) + return f + + +def install_shell(context={}, service=False): + with INSTALL_SHELL_LOCK: + if service: + from twisted.internet import default + default.install() + else: + # install_twisted_rector must be called before importing and using the reactor + from kivy.support import install_twisted_reactor + install_twisted_reactor() + + from twisted.internet import reactor + + Logger.debug('Shell: Creating twisted reactor. Service: %s', service) + connection = reactor.listenTCP(8000, + getManholeFactory(context, admin='kivy') + ) + + if service: + # service-based have no implicit reactor running. + Logger.debug('Shell: Twisted reactor starting') + reactor.run() + Logger.debug('Shell: Twisted reactor stopped') + else: + return connection + +def uninstall_shell(service=False, connections=[]): + if service: + raise NotImplementedError() + + defers = [] + for c in connections: + defers.append(c.stopListening()) + + from Queue import Empty + import kivy.support + while True: + kivy.support._twisted_reactor_work() + try: + for d in defers: + _block_on(d, timeout=1) + except Empty: + continue + break + + from kivy.support import uninstall_twisted_reactor + uninstall_twisted_reactor()