Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Service-based remote shell #3

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**.pyc
.vagrant/**
3 changes: 3 additions & 0 deletions android.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
title=Kivy Remote Shell
author=tito
orientation=portrait
Binary file modified libs/garden/garden.navigationdrawer/__init__.pyc
Binary file not shown.
61 changes: 40 additions & 21 deletions main.py
Original file line number Diff line number Diff line change
@@ -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']
Expand All @@ -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()
37 changes: 37 additions & 0 deletions service/__init__.py
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions service/main.py
Original file line number Diff line number Diff line change
@@ -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)
85 changes: 85 additions & 0 deletions shell.py
Original file line number Diff line number Diff line change
@@ -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()