From 5f039ae7d51614e9df3801dfeaf8eb58cdccf1dc Mon Sep 17 00:00:00 2001 From: Akshay Arora Date: Tue, 25 Feb 2025 20:34:50 +0530 Subject: [PATCH 1/5] Python REPL, kivy_console, local cryptography recipe --- .github/workflows/android.yml | 57 +- .gitignore | 2 + libs/garden/garden.navigationdrawer/LICENSE | 19 - libs/garden/garden.navigationdrawer/README.md | 128 --- .../garden.navigationdrawer/__init__.pyc | Bin 23935 -> 0 bytes .../navigationdrawer_closed.png | Bin 27528 -> 0 bytes .../navigationdrawer_open_example1.png | Bin 24303 -> 0 bytes .../navigationdrawer_open_example2.png | Bin 24768 -> 0 bytes .../data/background.png | Bin ham_icon.png => remoteshell/data/ham_icon.png | Bin icon.png => remoteshell/data/icon.png | Bin remoteshell/libs/kivy_console.py | 904 ++++++++++++++++++ remoteshell/libs/kivy_python_console.py | 311 ++++++ .../libs/navigationdrawer}/__init__.py | 2 +- remoteshell/libs/navigationdrawer/_version.py | 1 + .../navigationdrawer_gradient_ltor.png | Bin .../navigationdrawer_gradient_rtol.png | Bin .../navigationdrawer/tests/test_import.py | 6 + main.py => remoteshell/main.py | 9 +- .../plyer_command_list.rst | 30 +- remotekivy.kv => remoteshell/remotekivy.kv | 38 +- test.py => tests/test.py | 0 tools/build/build-android.sh | 8 + buildozer.spec => tools/build/buildozer.spec | 14 +- .../p4a_recipes/cryptography/__init__.py | 22 + .../build/requirements.txt | 2 + 26 files changed, 1331 insertions(+), 222 deletions(-) delete mode 100644 libs/garden/garden.navigationdrawer/LICENSE delete mode 100644 libs/garden/garden.navigationdrawer/README.md delete mode 100644 libs/garden/garden.navigationdrawer/__init__.pyc delete mode 100644 libs/garden/garden.navigationdrawer/navigationdrawer_closed.png delete mode 100644 libs/garden/garden.navigationdrawer/navigationdrawer_open_example1.png delete mode 100644 libs/garden/garden.navigationdrawer/navigationdrawer_open_example2.png rename background.png => remoteshell/data/background.png (100%) rename ham_icon.png => remoteshell/data/ham_icon.png (100%) rename icon.png => remoteshell/data/icon.png (100%) create mode 100644 remoteshell/libs/kivy_console.py create mode 100644 remoteshell/libs/kivy_python_console.py rename {libs/garden/garden.navigationdrawer => remoteshell/libs/navigationdrawer}/__init__.py (99%) create mode 100644 remoteshell/libs/navigationdrawer/_version.py rename {libs/garden/garden.navigationdrawer => remoteshell/libs/navigationdrawer}/navigationdrawer_gradient_ltor.png (100%) rename {libs/garden/garden.navigationdrawer => remoteshell/libs/navigationdrawer}/navigationdrawer_gradient_rtol.png (100%) create mode 100644 remoteshell/libs/navigationdrawer/tests/test_import.py rename main.py => remoteshell/main.py (95%) rename plyer_command_list.rst => remoteshell/plyer_command_list.rst (81%) rename remotekivy.kv => remoteshell/remotekivy.kv (56%) rename test.py => tests/test.py (100%) create mode 100644 tools/build/build-android.sh rename buildozer.spec => tools/build/buildozer.spec (72%) create mode 100644 tools/build/p4a_recipes/cryptography/__init__.py rename requirements.txt => tools/build/requirements.txt (79%) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index a1baae6..cc22739 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -5,38 +5,31 @@ jobs: strategy: matrix: os: - - 'ubuntu-latest' + - "ubuntu-latest" runs-on: ${{ matrix.os }} steps: - - name: Setup python - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - uses: actions/checkout@v2 - - uses: actions/cache@v2 - with: - path: | - ~/.buildozer - .buildozer - key: ${{ hashFiles('buildozer.spec') }} - - - name: Setup environment - run: | - pip install buildozer - pip install Cython - - run: buildozer --help - - name: SDK, NDK and p4a download - run: | - sed -i.bak "s/# android.accept_sdk_license = False/android.accept_sdk_license = True/" buildozer.spec - buildozer android p4a -- --help - - name: Install Linux dependencies - if: matrix.os == 'ubuntu-latest' - run: sudo apt -y install automake - - name: buildozer android debug - run: | - touch main.py - buildozer android debug - - uses: actions/upload-artifact@v2 - with: - path: bin/*.apk + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - uses: actions/checkout@v2 + - uses: actions/cache@v4 + with: + path: | + ~/.buildozer + .buildozer + key: ${{ hashFiles('tools/build/buildozer.spec') }} + - name: Setup environment + run: | + pip install buildozer + pip install Cython + - name: Install Linux dependencies + if: matrix.os == 'ubuntu-latest' + run: sudo apt -y install automake + - name: buildozer android debug + run: | + sh tools/build/build-android.sh + - uses: actions/upload-artifact@v4.6.1 + with: + path: .buildozer/bin/*.apk diff --git a/.gitignore b/.gitignore index f9a9ead..0843f61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .buildozer +*.pyc +__pycache__/ bin diff --git a/libs/garden/garden.navigationdrawer/LICENSE b/libs/garden/garden.navigationdrawer/LICENSE deleted file mode 100644 index 3094688..0000000 --- a/libs/garden/garden.navigationdrawer/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2013 Alexander Taylor - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/libs/garden/garden.navigationdrawer/README.md b/libs/garden/garden.navigationdrawer/README.md deleted file mode 100644 index 0ff33d1..0000000 --- a/libs/garden/garden.navigationdrawer/README.md +++ /dev/null @@ -1,128 +0,0 @@ -# NavigationDrawer - -The NavigationDrawer widget provides a hidden panel view designed to -duplicate the popular Android layout. The user views one main widget -but can slide from the left of the screen to view a second, previously -hidden widget. The transition between open/closed is smoothly -animated, with the parameters (anim time, panel width, touch -detection) all user configurable. If the panel is released without -being fully open or closed, it animates to an appropriate -configuration. - -NavigationDrawer supports many different animation properties, -including moving one or both of the side/main panels, darkening -either/both widgets, changing side panel opacity, and changing which -widget is on top. The user can edit these individually to taste (this -is enough rope to hang oneself, it's easy to make a useless or silly -configuration!), or use one of a few preset animations. - -The hidden panel might normally a set of navigation buttons (e.g. in a -GridLayout), but the implementation lets the user use any widget(s). - -The first widget added to the NavigationDrawer is automatically used -as the side panel, and the second widget as the main panel. No further -widgets can be added, further changes are left to the user via editing -the panel widgets. - -# Usage summary - -- The first widget added to a NavigationDrawer is used as the hidden - side panel. -- The second widget added is used as the main panel. -- Both widgets can be removed with remove_widget, or alternatively - set/removed with set_main_panel and set_side_panel. -- The hidden side panel can be revealed by dragging from the left of - the NavigationDrawer. The touch detection width is the - touch_accept_width property. -- Every animation property is user-editable, or default animations - can be chosen by setting anim_type. - -See the example and docstrings for information on individual properties. - - -# Example:: - - from kivy.app import App - from kivy.base import runTouchApp - from kivy.uix.boxlayout import BoxLayout - from kivy.uix.label import Label - from kivy.uix.button import Button - from kivy.uix.image import Image - from kivy.uix.widget import Widget - from kivy.core.window import Window - from kivy.metrics import dp - - from kivy.garden.navigationdrawer import NavigationDrawer - - class ExampleApp(App): - - def build(self): - navigationdrawer = NavigationDrawer() - - side_panel = BoxLayout(orientation='vertical') - side_panel.add_widget(Label(text='Panel label')) - side_panel.add_widget(Button(text='A button')) - side_panel.add_widget(Button(text='Another button')) - navigationdrawer.add_widget(side_panel) - - label_head = ( - '[b]Example label filling main panel[/b]\n\n[color=ff0000](p' - 'ull from left to right!)[/color]\n\nIn this example, the le' - 'ft panel is a simple boxlayout menu, and this main panel is' - ' a BoxLayout with a label and example image.\n\nSeveral pre' - 'set layouts are available (see buttons below), but users ma' - 'y edit every parameter for much more customisation.') - main_panel = BoxLayout(orientation='vertical') - label_bl = BoxLayout(orientation='horizontal') - label = Label(text=label_head, font_size='15sp', - markup=True, valign='top') - label_bl.add_widget(Widget(size_hint_x=None, width=dp(10))) - label_bl.add_widget(label) - label_bl.add_widget(Widget(size_hint_x=None, width=dp(10))) - main_panel.add_widget(Widget(size_hint_y=None, height=dp(10))) - main_panel.add_widget(label_bl) - main_panel.add_widget(Widget(size_hint_y=None, height=dp(10))) - main_panel.add_widget(Image(source='red_pixel.png', allow_stretch=True, - keep_ratio=False, size_hint_y=0.2)) - navigationdrawer.add_widget(main_panel) - label.bind(size=label.setter('text_size')) - - def set_anim_type(name): - navigationdrawer.anim_type = name - modes_layout = BoxLayout(orientation='horizontal') - modes_layout.add_widget(Label(text='preset\nanims:')) - slide_an = Button(text='slide_\nabove_\nanim') - slide_an.bind(on_press=lambda j: set_anim_type('slide_above_anim')) - slide_sim = Button(text='slide_\nabove_\nsimple') - slide_sim.bind(on_press=lambda j: set_anim_type('slide_above_simple')) - fade_in_button = Button(text='fade_in') - fade_in_button.bind(on_press=lambda j: set_anim_type('fade_in')) - reveal_button = Button(text='reveal_\nbelow_\nanim') - reveal_button.bind(on_press= - lambda j: set_anim_type('reveal_below_anim')) - slide_button = Button(text='reveal_\nbelow_\nsimple') - slide_button.bind(on_press= - lambda j: set_anim_type('reveal_below_simple')) - modes_layout.add_widget(slide_an) - modes_layout.add_widget(slide_sim) - modes_layout.add_widget(fade_in_button) - modes_layout.add_widget(reveal_button) - modes_layout.add_widget(slide_button) - main_panel.add_widget(modes_layout) - - button = Button(text='toggle NavigationDrawer state (animate)', - size_hint_y=0.2) - button.bind(on_press=lambda j: navigationdrawer.toggle_state()) - button2 = Button(text='toggle NavigationDrawer state (jump)', - size_hint_y=0.2) - button2.bind(on_press=lambda j: navigationdrawer.toggle_state(False)) - button3 = Button(text='toggle _main_above', size_hint_y=0.2) - button3.bind(on_press=navigationdrawer.toggle_main_above) - main_panel.add_widget(button) - main_panel.add_widget(button2) - main_panel.add_widget(button3) - - return navigationdrawer - - ExampleApp().run() - diff --git a/libs/garden/garden.navigationdrawer/__init__.pyc b/libs/garden/garden.navigationdrawer/__init__.pyc deleted file mode 100644 index 1ba01f15ad71f6c38d1000debf4fe2a72754a583..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23935 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 bzlr9V!^v+}39rMWwFop+buT|vwhsRfc5@e| diff --git a/libs/garden/garden.navigationdrawer/navigationdrawer_open_example1.png b/libs/garden/garden.navigationdrawer/navigationdrawer_open_example1.png deleted file mode 100644 index 46b7da64bd3763b6048f6c301b1b331b794aee7b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24303 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

02!@ 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 MT85g>QRwjh0Opq;O#lD@ diff --git a/background.png b/remoteshell/data/background.png similarity index 100% rename from background.png rename to remoteshell/data/background.png diff --git a/ham_icon.png b/remoteshell/data/ham_icon.png similarity index 100% rename from ham_icon.png rename to remoteshell/data/ham_icon.png diff --git a/icon.png b/remoteshell/data/icon.png similarity index 100% rename from icon.png rename to remoteshell/data/icon.png diff --git a/remoteshell/libs/kivy_console.py b/remoteshell/libs/kivy_console.py new file mode 100644 index 0000000..1506a25 --- /dev/null +++ b/remoteshell/libs/kivy_console.py @@ -0,0 +1,904 @@ +# -*- coding: utf-8 -*- +''' +KivyConsole +=========== + +.. image:: images/KivyConsole.jpg + :align: right + +:class:`KivyConsole` is a :class:`~kivy.uix.widget.Widget` +Purpose: Providing a system console for debugging kivy by running another +instance of kivy in this console and displaying it's output. +To configure, you can use + +cached_history : +cached_commands : +font : +font_size : +shell : + +''Versionadded:: 1.0.?TODO + +''Usage: + from kivy.uix.kivyconsole import KivyConsole + + parent.add_widget(KivyConsole()) + +or + + console = KivyConsole() + +To run a command: + + console.stdin.write('ls -l') + +or + subprocess.Popen(('echo','ls'), stdout = console.stdin) + +To display something on stdout write to stdout + + console.stdout.write('this will be written to the stdout\n') + +or + subprocess.Popen('ps', stdout = console.stdout, shell = True) + +Warning: To read from stdout remember that the process is run in a thread, give +it time to complete otherwise you might get a empty or partial string; +returning whatever has been written to the stdout pipe till the time +read() was called. + + text = console.stdout.read() or read(no_of_bytes) or readline() + +TODO: create a stdin and stdout pipe for + this console like in logger.[==== ]%done +TODO: move everything that is non-specific to + a generic console in a different Project.[ ]%done +TODO: Fix Prompt, make it smaller plus give it more info + +''Shortcuts: +Inside the console you can use the following shortcuts: +Shortcut Function +_________________________________________________________ +PGup Search for previous command inside command history + starting with the text before current cursor position + +PGdn Search for Next command inside command history + starting with the text before current cursor position + +UpArrow Replace command_line with previous command + +DnArrow Replace command_line with next command + (only works if one is not at last command) + +Tab If there is nothing before the cursur when tab is pressed + contents of current directory will be displayed. + '.' before cursur will be converted to './' + '..' to '../' + If there is a path before cursur position + contents of the path will be displayed. + else contents of the path before cursor containing + the commands matching the text before cursur will + be displayed +''' + +__all__ = ('KivyConsole', ) + +import shlex +import subprocess +import re +import os +import sys +from functools import partial +from pygments.lexers import BashSessionLexer + +from kivy.uix.gridlayout import GridLayout +from kivy.properties import (NumericProperty, StringProperty, + BooleanProperty, ObjectProperty, DictProperty, + ListProperty) +from kivy.uix.button import Button +from kivy.uix.textinput import TextInput +from kivy.clock import Clock +from kivy.lang import Builder +from kivy.app import runTouchApp +from kivy.logger import Logger +from kivy.core.window import Window +from kivy.utils import platform + + +Builder.load_string(''' +: + cols:1 + txtinput_history_box: history_box.__self__ + txtinput_command_line: command_line.__self__ + ScrollView: + CodeInput: + id: history_box + size_hint: (1, None) + height: '801dp' + font_name: root.font_name + font_size: root.font_size + readonly: True + foreground_color: root.foreground_color + background_color: root.background_color + on_text: root.on_text(*args) + TextInput: + id: command_line + multiline: False + size_hint: (1, None) + font_name: root.font_name + font_size: root.font_size + readonly: root.readonly + foreground_color: root.foreground_color + background_color: root.background_color + height: '36dp' + on_text_validate: root.on_enter(*args) + on_touch_up: + self.collide_point(*args[1].pos)\\ + and root._move_cursor_to_end(self) +''') + + +class KivyConsole(GridLayout): + '''This is a Console widget used for debugging and running external + commands + + ''' + + readonly = BooleanProperty(False) + '''This defines whether a person can enter commands in the console + + :data:`readonly` is an :class:`~kivy.properties.BooleanProperty`, + Default to 'False' + ''' + + foreground_color = ListProperty((1, 1, 1, 1)) + '''This defines the color of the text in the console + + :data:`foreground_color` is an :class:`~kivy.properties.ListProperty`, + Default to '(1, 1, 1, 1)' + ''' + + background_color = ListProperty((0, 0, 0, 1)) + '''This defines the color of the text in the console + + :data:`foreground_color` is an :class:`~kivy.properties.ListProperty`, + Default to '(0, 0, 0, 1)' + ''' + + cached_history = NumericProperty(200) + '''Indicates the No. of lines to cache. Defaults to 200 + + :data:`cached_history` is an :class:`~kivy.properties.NumericProperty`, + Default to '200' + ''' + + cached_commands = NumericProperty(90) + '''Indicates the no of commands to cache. Defaults to 90 + + :data:`cached_commands` is a :class:`~kivy.properties.NumericProperty`, + Default to '90' + ''' + + font_name = StringProperty('data/fonts/RobotoMono-Regular.ttf') + '''Indicates the font Style used in the console + + :data:`font` is a :class:`~kivy.properties.StringProperty`, + Default to 'Roboto' + ''' + + environment = DictProperty(os.environ.copy()) + '''Indicates the environment the commands are run in. Set your PATH or + other environment variables here. like so:: + + kivy_console.environment['PATH']='path' + + environment is :class:`~kivy.properties.DictProperty`, defaults to + the environment for the pricess running Kivy console + ''' + + font_size = NumericProperty('14sp') + '''Indicates the size of the font used for the console + + :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, + Default to '9' + ''' + + textcache = ListProperty(['', ]) + '''Indicates the cache of the commands and their output + + :data:`textcache` is a :class:`~kivy.properties.ListProperty`, + Default to '' + ''' + + shell = BooleanProperty(False) + '''Indicates the whether system shell is used to run the commands + + :data:`shell` is a :class:`~kivy.properties.BooleanProperty`, + Default to 'False' + + WARNING: Shell = True is a security risk and therefore = False by default, + As a result with shell = False some shell specific commands and + redirections + like 'ls |grep lte' or dir >output.txt will not work. + If for some reason you need to run such commands, try running the platform + shell first + eg: /bin/sh ...etc on nix platforms and cmd.exe on windows. + As the ability to interact with the running command is built in, + you should be able to interact with the native shell. + + Shell = True, should be set only if absolutely necessary. + ''' + + txtinput_command_line = ObjectProperty(None) + + def __init__(self, **kwargs): + self.register_event_type('on_subprocess_done') + self.register_event_type('on_command_list_done') + super(KivyConsole, self).__init__(**kwargs) + # initialisations + self.txtinput_command_line_refocus = False + self.txtinput_run_command_refocus = False + self.win = None + self.scheduled = False + self.command_history = [] + self.command_history_pos = 0 + self.command_status = 'closed' + if sys.version_info >= (3, 0): + self.cur_dir = os.getcwd() + else: + self.cur_dir = os.getcwdu() + self.command_list = [] # list of cmds to be executed + self.stdout = std_in_out(self, 'stdout') + self.stdin = std_in_out(self, 'stdin') + self.popen_obj = None + # self.stderror = stderror(self) + # delayed initialisation + Clock.schedule_once(self._initialize) + self_change_txtcache = self._change_txtcache + _trig = Clock.create_trigger(self_change_txtcache) + self.bind(textcache=_trig) + self._hostname = 'unknown' + try: + if hasattr(os, 'uname'): + self._hostname = os.uname()[1] + else: + self._hostname = os.environ.get('COMPUTERNAME', 'unknown') + except Exception: + pass + self._username = os.environ.get('USER', '') + if not self._username: + self._username = os.environ.get('USERNAME', 'unknown') + + def run_command(self, command, *args): + '''Run a command using Kivy Console. + The output will be visible in the Kivy console. + Returns False if there is a command running and stops. Otherwise + start the execution of the commands and returns True + ''' + if self.popen_obj: + return False + + if isinstance(command, list): + self.command_list = command + else: + self.command_list = [command] + self._run_command_list() + + return True + + def _run_command_list(self, *kwargs): + '''Runs a list of commands + ''' + if self.command_list: + self.stdin.write(self.command_list.pop(0)) + self.bind(on_subprocess_done=self._run_command_list) + else: + self.dispatch('on_command_list_done') + + def clear(self, *args): + '''Clear the Kivy Console area + ''' + self.txtinput_history_box.text = '' + self.textcache = ['', ] + + def _initialize(self, dt): + '''Set console default variable values + ''' + cl = self.txtinput_command_line + self.txtinput_history_box.lexer = BashSessionLexer() + self.txtinput_history_box.text = u''.join(self.textcache) + self.txtinput_command_line.text = self.prompt() + self.txtinput_command_line.bind(focus=self.on_focus) + self.txtinput_command_line.bind( + selection_text=self.on_txtinput_selection) + Clock.schedule_once(self._change_txtcache) + self._focus(self.txtinput_command_line) + self._list = [self.txtinput_command_line] + + def on_txtinput_selection(self, *args): + '''Callback to command input text selection. + Cannot select the PS1 variable, so it'll handle to select only + input text. + ''' + ticl = self.txtinput_command_line + col = len(self.prompt()) + ticl.select_text(col, len(ticl.text)) + + def _move_cursor_to_end(self, instance): + '''Moves the command input cursor to the end + ''' + def mte(*l): + instance.cursor = instance.get_cursor_from_index(len_prompt) + len_prompt = len(self.prompt()) + if instance.cursor[0] < len_prompt: + Clock.schedule_once(mte, -1) + + def _focus(self, widg, t_f=True): + Clock.schedule_once(partial(self._deffered_focus, widg, t_f)) + + def _deffered_focus(self, widg, t_f, dt): + if widg.get_root_window(): + widg.focus = t_f + + def prompt(self, *args): + '''Returns the PS1 variable + ''' + return "[%s@%s %s]>> " % ( + self._username, self._hostname, + os.path.basename(str(self.cur_dir))) + + def _change_txtcache(self, *args): + '''Update the Kivy Console output area + ''' + tihb = self.txtinput_history_box + tihb.text = ''.join(self.textcache) + if not self.get_root_window(): + return + tihb.height = max(tihb.minimum_height, tihb.parent.height) + tihb.parent.scroll_y = 0 + + def on_text(self, instance, txt): + # check if history_box has more text than indicated buy + # self.cached_history and remove excess lines from top + if txt == '': + return + try: + # self._skip_textcache = True + self.textcache = self.textcache[-self.cached_history:] + except IndexError: + pass + # self._skip_textcache = False + + def on_key_down(self, *l): + '''Handle the on_key_down from keyboard + ''' + ticl = self.txtinput_command_line + + def move_cursor_to(col): + '''Update the cursor position + ''' + ticl.cursor =\ + col, ticl.cursor[1] + + def search_history(up_dn): + if up_dn == 'up': + plus_minus = -1 + else: + plus_minus = 1 + l_curdir = len(self.prompt()) + col = ticl.cursor_col + command = ticl.text[l_curdir: col] + max_len = len(self.command_history) - 1 + chp = self.command_history_pos + + while max_len >= 0: + if plus_minus == 1: + if self.command_history_pos > max_len - 1: + self.command_history_pos = max_len + return + else: + if self.command_history_pos <= 0: + self.command_history_pos = max_len + return + self.command_history_pos = self.command_history_pos\ + + plus_minus + cmd = self.command_history[self.command_history_pos] + if cmd[:len(command)] == command: + ticl.text = u''.join(( + self.prompt(), cmd)) + move_cursor_to(col) + return + self.command_history_pos = max_len + 1 + + if ticl.focus: + if l[1] == 273: + # up arrow: display previous command + if self.command_history_pos > 0: + self.command_history_pos = self.command_history_pos - 1 + ticl.text = u''.join( + (self.prompt(), + self.command_history[self.command_history_pos])) + return + if l[1] == 274: + # dn arrow: display next command + if self.command_history_pos < len(self.command_history) - 1: + self.command_history_pos = self.command_history_pos + 1 + ticl.text = u''.join( + (self.prompt(), + self.command_history[self.command_history_pos])) + else: + self.command_history_pos = len(self.command_history) + ticl.text = self.prompt() + col = len(ticl.text) + move_cursor_to(col) + return + if l[1] == 9: + # tab: autocomplete + def display_dir(cur_dir, starts_with=None): + # display contents of dir from cur_dir variable + starts_with_is_not_None = starts_with is not None + try: + dir_list = os.listdir(cur_dir) + except OSError as err: + self.add_to_cache(u''.join((err.strerror, '\n'))) + return + if starts_with_is_not_None: + len_starts_with = len(starts_with) + self.add_to_cache(u''.join(('contents of directory: ', + cur_dir, '\n'))) + txt = u'' + no_of_matches = 0 + for _file in dir_list: + if starts_with_is_not_None: + if _file[:len_starts_with] == starts_with: + # if file matches starts with + txt = u''.join((txt, _file, ' ')) + no_of_matches += 1 + else: + self.add_to_cache(u''.join((_file, '\t'))) + if no_of_matches == 1: + len_txt = len(txt) - 1 + cmdl_text = ticl.text + len_cmdl = len(cmdl_text) + os_sep = os.sep \ + if col == len_cmdl or (col < len_cmdl and + cmdl_text[col] != + os.sep) else '' + ticl.text = u''.join( + (self.prompt(), text_before_cursor, + txt[len_starts_with:len_txt], os_sep, + cmdl_text[col:])) + move_cursor_to(col + (len_txt - len_starts_with) + 1) + elif no_of_matches > 1: + self.add_to_cache(txt) + self.add_to_cache('\n') + + # send back space to command line -remove the tab + Clock.schedule_once(ticl.do_backspace, 0) + l_curdir = len(self.prompt()) + move_cursor_to(l_curdir + len(ticl.text)) + ntext = os.path.expandvars(ticl.text) + # store text before cursor for comparison + col = ticl.cursor_col + if ntext != ticl.text: + ticl.text = ntext + col = len(ntext) + text_before_cursor = ticl.text[l_curdir: col] + + # if empty or space before: list cur dir + if text_before_cursor == ''\ + or ticl.text[col - 1] == ' ': + display_dir(self.cur_dir) + # if in mid command: + else: + # list commands in PATH starting with text before cursor + # split command into path till the seperator + cmd_start = text_before_cursor.rfind(' ') + cmd_start += 1 + cur_dir = self.cur_dir\ + if text_before_cursor[cmd_start] != os.sep\ + else os.sep + os_sep = os.sep if cur_dir != os.sep else '' + cmd_end = text_before_cursor.rfind(os.sep) + len_txt_bef_cur = len(text_before_cursor) - 1 + if cmd_end == len_txt_bef_cur: + # display files in path + if text_before_cursor[cmd_start] == os.sep: + cmd_start += 1 + display_dir(u''.join((cur_dir, os_sep, + text_before_cursor[cmd_start:cmd_end]))) + elif text_before_cursor[len_txt_bef_cur] == '.': + # if / already there return + if len(ticl.text) > col\ + and ticl.text[col] == os.sep: + return + if text_before_cursor[len_txt_bef_cur - 1] == '.': + len_txt_bef_cur -= 1 + if text_before_cursor[len_txt_bef_cur - 1]\ + not in (' ', os.sep): + return + # insert at cursor os.sep: / or \ + ticl.text = u''.join((self.prompt(), + text_before_cursor, os_sep, + ticl.text[col:])) + else: + if cmd_end < 0: + cmd_end = cmd_start + else: + cmd_end += 1 + display_dir(u''.join(( + cur_dir, + os_sep, + text_before_cursor[cmd_start:cmd_end])), + text_before_cursor[cmd_end:]) + return + if l[1] == 280: + # pgup: search last command starting with... + search_history('up') + return + if l[1] == 281: + # pgdn: search next command starting with... + search_history('dn') + return + if l[1] == 278: + # Home: cursor should not go to the left of cur_dir + col = len(self.prompt()) + move_cursor_to(col) + if len(l[4]) > 0 and l[4][0] == 'shift': + ticl.selection_to = col + return + if l[1] == 276 or l[1] == 8: + # left arrow/bkspc: cursor should not go left of cur_dir + col = len(self.prompt()) + if ticl.cursor_col < col: + if l[1] == 8: + ticl.text = self.prompt() + move_cursor_to(col) + return + + def on_focus(self, instance, value): + '''Handle the focus on the command input + ''' + if value: + # focused + if instance is self.txtinput_command_line: + Window.unbind(on_key_down=self.on_key_down) + Window.bind(on_key_down=self.on_key_down) + else: + # defocused + Window.unbind(on_key_down=self.on_key_down) + if self.txtinput_command_line_refocus: + self.txtinput_command_line_refocus = False + if self.txtinput_command_line.get_root_window(): + self.txtinput_command_line.focus = True + self.txtinput_command_line.scroll_x = 0 + if self.txtinput_run_command_refocus: + self.txtinput_run_command_refocus = False + instance.focus = True + instance.scroll_x = 0 + instance.text = u'' + + def add_to_cache(self, _string): + # os.write(self.stdout.stdout_pipe, _string.encode('utf-8')) + # self.stdout.flush() + self.textcache.append(_string) + _string = None + + def kill_process(self, *l): + if self.popen_obj: + self.popen_obj.kill() + + def on_enter(self, *l): + '''When the user press enter and wants to run a command + ''' + self.unbind(on_subprocess_done=self.on_enter) + if self.command_status == 'started': + self.kill_process() + self.bind(on_subprocess_done=self.on_enter) + return + + txtinput_command_line = self.txtinput_command_line + add_to_cache = self.add_to_cache + command_history = self.command_history + + def remove_command_interaction_widgets(*l): + '''command finished:remove widget responsible for interaction + ''' + parent.remove_widget(self.interact_layout) + self.interact_layout = None + # enable running a new command + try: + parent.add_widget(self.txtinput_command_line) + except: + self._initialize(0) + + self._focus(txtinput_command_line, True) + Clock.schedule_once(self._change_txtcache, -1) + self.command_status = 'closed' + self.dispatch('on_subprocess_done') + + def run_cmd(*l): + '''Run the command + ''' + # this is run inside a thread so take care, avoid gui ops + try: + _posix = True + if sys.platform[0] == 'w': + _posix = False + cmd = shlex.split(str(command), posix=_posix)\ + if not self.shell else command + except Exception as err: + cmd = '' + self.add_to_cache(u''.join((str(err), ' <', command, ' >\n'))) + if len(cmd) > 0: + prev_stdout = sys.stdout + sys.stdout = self.stdout + try: + # execute command + self.popen_obj = popen = subprocess.Popen( + cmd, + bufsize=-1, + stdout=subprocess.PIPE, + stdin=subprocess.PIPE, + stderr=subprocess.STDOUT, + preexec_fn=None, + close_fds=False, + shell=self.shell, + cwd=self.cur_dir, + env=self.environment, + universal_newlines=False, + startupinfo=None, + creationflags=0) + popen_stdout_r = popen.stdout.readline + popen_stdout_flush = popen.stdout.flush + txt = popen_stdout_r() + plat = platform + # print(txt, '<<') + while len(txt) > 0 and self.command_status == 'started': + # skip flush on android + if plat[0] != 'a': + popen_stdout_flush() + add_to_cache(txt.decode('utf8')) + Logger.debug(txt.decode('utf8')) + txt = popen_stdout_r() + except (OSError, ValueError) as err: + add_to_cache(u''.join((str(err.strerror), + ' < ', command, ' >\n'))) + self.command_status = 'closed' + sys.stdout = prev_stdout + self.popen_obj = None + Clock.schedule_once(remove_command_interaction_widgets, 0) + + # append text to textcache + add_to_cache(u''.join((self.txtinput_command_line.text, '\n'))) + command = txtinput_command_line.text[len(self.prompt()):] + + if command == '': + self.txtinput_command_line_refocus = True + return + + # store command in command_history + if self.command_history_pos > 0: + self.command_history_pos = len(command_history) + if command_history[self.command_history_pos - 1] != command: + command_history.append(command) + else: + command_history.append(command) + + len_command_history = len(command_history) + self.command_history_pos = len(command_history) + + # on reaching limit(cached_lines) pop first command + if len_command_history >= self.cached_commands: + self.command_history = command_history[1:] + + # replce $PATH with + command = os.path.expandvars(command) + + # if command = cd change directory + if command == 'clear' or command == 'cls': + self.clear() + txtinput_command_line.text = self.prompt() + self.txtinput_command_line_refocus = True + self.command_status = 'closed' + self.dispatch('on_subprocess_done') + return + if command.startswith('cd ') or command.startswith('export '): + if command[0] == 'e': + e_q = command[7:].find('=') + _exprt = command[7:] + if e_q: + os.environ[_exprt[:e_q]] = _exprt[e_q + 1:] + self.environment = os.environ.copy() + else: + try: + command = re.sub('[ ]+', ' ', command) + if command[3] == os.sep: + os.chdir(command[3:]) + else: + os.chdir(self.cur_dir + os.sep + command[3:]) + if sys.version_info >= (3, 0): + self.cur_dir = os.getcwd() + else: + self.cur_dir = os.getcwdu() + except OSError as err: + Logger.debug('Shell Console: err:' + err.strerror + + ' directory:' + command[3:]) + add_to_cache(u''.join((err.strerror, '\n'))) + txtinput_command_line.text = self.prompt() + self.txtinput_command_line_refocus = True + self.command_status = 'closed' + self.dispatch('on_subprocess_done') + return + + txtinput_command_line.text = self.prompt() + # store output in textcache + parent = txtinput_command_line.parent + # disable running a new command while and old one is running + parent.remove_widget(txtinput_command_line) + # add widget for interaction with the running command + txtinput_run_command = TextInput(multiline=False, + font_size=self.font_size, + font_name=self.font_name) + + def interact_with_command(*l): + '''Text input to interact with the running command + ''' + popen_obj = self.popen_obj + if not popen_obj: + return + txt = l[0].text + u'\n' + popen_obj_stdin = popen_obj.stdin + popen_obj_stdin.write(txt.encode('utf-8')) + popen_obj_stdin.flush() + self.txtinput_run_command_refocus = True + + self.txtinput_run_command_refocus = False + txtinput_run_command.bind(on_text_validate=interact_with_command) + txtinput_run_command.bind(focus=self.on_focus) + btn_kill = Button(text="Stop", + width=60, + size_hint=(None, 1)) + + self.interact_layout = il = GridLayout(rows=1, cols=2, height=27, + size_hint=(1, None)) + btn_kill.bind(on_press=self.kill_process) + il.add_widget(txtinput_run_command) + il.add_widget(btn_kill) + parent.add_widget(il) + + txtinput_run_command.focus = True + self.command_status = 'started' + import threading + threading.Thread(target=run_cmd, daemon=True).start() + # Clock.schedule_once(run_cmd, 0) + + def on_subprocess_done(self, *args): + '''Event handler for when a process was killed + or just finished the execution. + ''' + pass + + def on_command_list_done(self, *args): + '''Event handler for when the whole command list was executed or killed + ''' + pass + + +class std_in_out(object): + ''' class for writing to/reading from this console''' + + def __init__(self, obj, mode='stdout'): + self.obj = obj + self.mode = mode + self.stdin_pipe, self.stdout_pipe = os.pipe() + import threading + threading.Thread(target=self.read_from_in_pipe).start() + self.textcache = None + + def update_cache(self, text_line, obj, *l): + '''Update the output text area + ''' + obj.textcache.append(text_line) + + def read_from_in_pipe(self, *l): + '''Read the output from the command + ''' + txt = '\n' + txt_line = '' + os_read = os.read + self_stdin_pipe = self.stdin_pipe + self_mode = self.mode + self_write = self.write + Clock_schedule_once = Clock.schedule_once + self_update_cache = self.update_cache + self_flush = self.flush + obj = self.obj + try: + while txt != '': + txt = os_read(self_stdin_pipe, 1) + txt_line = u''.join((txt_line, txt)) + if txt == '\n': + if self_mode == 'stdin': + # run command + self_write(txt_line) + else: + Clock_schedule_once( + partial(self_update_cache, txt_line, obj), 0) + self_flush() + txt_line = '' + except OSError as e: + Logger.exception(e) + + def close(self): + '''Close the pipes + ''' + os.close(self.stdin_pipe) + os.close(self.stdout_pipe) + + def __del__(self): + self.close() + + def fileno(self): + return self.stdout_pipe + + def write(self, s): + '''Write a command to the pipe + ''' + Logger.debug('write called with command:' + str(s)) + if self.mode == 'stdout': + self.obj.add_to_cache(s) + self.flush() + else: + # process.stdout.write ...run command + if self.mode == 'stdin': + self.obj.txtinput_command_line.text = ''.join(( + self.obj.prompt(), s)) + self.obj.on_enter() + + def read(self, no_of_bytes=0): + if self.mode == 'stdin': + # stdin.read + Logger.exception('KivyConsole: can not read from a stdin pipe') + return + # process.stdout/in.read + txtc = self.textcache + if no_of_bytes == 0: + # return all data + if txtc is None: + self.flush() + while self.obj.command_status != 'closed': + pass + txtc = self.textcache + return txtc + try: + self.textcache = txtc[no_of_bytes:] + except IndexError: + self.textcache = txtc + return txtc[:no_of_bytes] + + def readline(self): + if self.mode == 'stdin': + # stdin.readline + Logger.exception('KivyConsole: can not read from a stdin pipe') + return + else: + # process.stdout.readline + if self.textcache is None: + self.flush() + txt = self.textcache + x = txt.find('\n') + if x < 0: + Logger.Debug('console_shell: no more data') + return + self.textcache = txt[x:] + # ##self. write to ... + return txt[:x] + + def flush(self): + self.textcache = u''.join(self.obj.textcache) + return + + +if __name__ == '__main__': + runTouchApp(KivyConsole()) diff --git a/remoteshell/libs/kivy_python_console.py b/remoteshell/libs/kivy_python_console.py new file mode 100644 index 0000000..f3326be --- /dev/null +++ b/remoteshell/libs/kivy_python_console.py @@ -0,0 +1,311 @@ +"""TODO: ctrl + left/right (move past word), ctrl + backspace/del (del word), shift + del (del line) + ...: Smart movement through leading indentation. + ...: Except for first line, up/down to work normally on multi-line console input. +""" +from code import InteractiveConsole +from collections import deque +from dataclasses import dataclass +from io import StringIO +from itertools import chain, takewhile +from more_itertools import ilen +import sys +from kivy.uix.codeinput import CodeInput +from pygments.lexers import PythonConsoleLexer + + +@dataclass(frozen=True) +class Key: + # ANY equals everything! -- if you don't care about matching modifiers, set them equal to Key.ANY + ANY = type('ANY', (), { '__eq__': lambda *args: True, + '__repr__': lambda self: 'ANY', + '__hash__': lambda self: -1})() + + code: int + shift: bool = False + ctrl: bool = False + + def __eq__(self, other): + if isinstance(other, int): return other == self.code + return self.__dict__ == other.__dict__ + + def iter_similar(self): + """Return an iterator that yields keys equal to self.""" + yield self + yield Key(self.code, self.shift, Key.ANY) + yield Key(self.code, Key.ANY, self.ctrl) + yield Key(self.code, Key.ANY, Key.ANY) + + +SHIFT, CTRL = (303, 304), (305, 306) + +EXACT = map(Key, (13, 9, 275, 276, 278, 279)) +ANY_MODS = (Key(code, Key.ANY, Key.ANY) for code in (273, 274, 8, 127)) + +KEYS \ + = ENTER, TAB, RIGHT, LEFT, HOME, END, UP, DOWN, BACKSPACE, DELETE \ + = tuple(chain(EXACT, ANY_MODS)) + +del EXACT; del ANY_MODS # Generators exhausted and we don't need them anymore + +CUT = Key(120, False, True) # +COPY = Key(99 , False, True) # +REDO = Key(122, True, True) # + +SELECT_LEFT = Key(276, True, False) # +SELECT_RIGHT = Key(275, True, False) # +SELECT_HOME = Key(278, True, False) # +SELECT_END = Key(279, True, False) # + + +class RedirectConsoleOut: + """Redirect sys.excepthook and sys.stdout in a single context manager. + InteractiveConsole (IC) `write` method won't be used if sys.excepthook isn't sys.__excepthook__, + so we redirect sys.excepthook when pushing to the IC. This redirect probably isn't necessary: + testing was done in IPython which sets sys.excepthook to a crashhandler, but running this file + normally would probably avoid the need for a redirect; still, better safe than sorry. + """ + def __init__(self): + self.stack = deque() + + def __enter__(self): + self.old_hook = sys.excepthook + self.old_out = sys.stdout + + sys.excepthook = sys.__excepthook__ + sys.stdout = StringIO() + + sys.stdout.write('\n') + + def __exit__(self, type, value, tb): + self.stack.append(sys.stdout.getvalue()) + + sys.stdout = self.old_out + sys.excepthook = self.old_hook + + +class Console(InteractiveConsole): + def __init__(self, text_input, locals=None, filename=""): + super().__init__(locals, filename) + self.text_input = text_input + self.out_context = RedirectConsoleOut() + + def push(self, line): + out = self.out_context + with out: needs_more = super().push(line) + + if not needs_more: + out.stack.reverse() + self.text_input.text += ''.join(out.stack) + out.stack.clear() + + return needs_more + + def write(self, data): + self.out_context.stack.append(data) + + +class InputHandler: + def __init__(self, text_input): + self.text_input = text_input + + self.pre = { COPY: self._copy, + CUT: self._cut, + REDO: self._redo} + + self.post = { LEFT: self._left, + RIGHT: self._right, + END: self._end, + HOME: self._home, + SELECT_LEFT: self._select_left, + SELECT_RIGHT: self._select_right, + SELECT_END: self._select_end, + SELECT_HOME: self._select_home, + TAB: self._tab, + ENTER: self._enter, + UP: self._up, + DOWN: self._down, + BACKSPACE: self._backspace} + + def __call__(self, key, read_only): + if handle := self.pre.get(key): return handle + + if read_only: return self._read_only + + for key in key.iter_similar(): + if handle := self.post.get(key): return handle + + def _copy(self, **kwargs): self.text_input.copy() + + def _cut(self, read_only, **kwargs): + self.text_input.copy() if read_only else self.text_input.cut() + + def _redo(self, **kwargs): self.text_input.do_redo() + + def _left(self, at_home, **kwargs): + self.text_input.cancel_selection() + if not at_home: self.text_input.move_cursor('left') + + def _right(self, at_end, **kwargs): + self.text_input.cancel_selection() + if not at_end: self.text_input.move_cursor('right') + + def _end(self, **kwargs): + self.text_input.cancel_selection() + self.text_input.move_cursor('end') + + def _home(self, **kwargs): + self.text_input.cancel_selection() + self.text_input.move_cursor('home') + + def _select_left(self, at_home, has_selection, _from, _to, **kwargs): + if at_home: return + i = self.text_input.move_cursor('left') + if not has_selection: self.text_input.select_text(i, i + 1) + elif i < _from : self.text_input.select_text(i, _to) + elif i >= _from : self.text_input.select_text(_from, i) + + def _select_right(self, at_end, has_selection, _from, _to, **kwargs): + if at_end: return + i = self.text_input.move_cursor('right') + if not has_selection: self.text_input.select_text(i - 1, i) + elif i > _to : self.text_input.select_text(_from, i) + elif i <= _to : self.text_input.select_text(i, _to) + + def _select_end(self, has_selection, _to, _from, i, end, **kwargs): + if not has_selection: start = i + elif _to == i : start = _from + else : start = _to + self.text_input.select_text(start, end) + self.text_input.move_cursor('end') + + def _select_home(self, has_selection, _to, _from, i, home, **kwargs): + if not has_selection: fin = i + elif _from == i : fin = _to + else : fin = _from + self.text_input.select_text(home, fin) + self.text_input.move_cursor('home') + + def _tab(self, has_selection, at_home, **kwargs): + ti = self.text_input + if not has_selection and at_home: ti.insert_text(' ' * ti.tab_width) + + def _enter(self, home, **kwargs): + ti = self.text_input + text = ti.text[home:].rstrip() + + if text and (len(ti.history) == 1 or ti.history[1] != text): + ti.history.popleft() + ti.history.appendleft(text) + ti.history.appendleft('') + ti._history_index = 0 + + needs_more = ti.console.push(text) + ti.prompt(needs_more) + + def _up(self, **kwargs): self.text_input.input_from_history() + + def _down(self, **kwargs): self.text_input.input_from_history(reverse=True) + + def _backspace(self, at_home, has_selection, window, keycode, text, modifiers, **kwargs): + ti = self.text_input + if not at_home or has_selection: + super(KivyConsole, ti).keyboard_on_key_down(window, keycode, text, modifiers) + + def _read_only(self, key, window, keycode, text, modifiers, **kwargs): + ti = self.text_input + ti.cancel_selection() + ti.move_cursor('end') + if key.code not in KEYS: + super(KivyConsole, ti).keyboard_on_key_down(window, keycode, text, modifiers) + + +class KivyConsole(CodeInput): + prompt_1 = '\n>>> ' + prompt_2 = '\n... ' + + _home_pos = 0 + _indent_level = 0 + _history_index = 0 + + def __init__(self, *args, locals=None, banner=None, **kwargs): + super().__init__(*args, **kwargs) + self.lexer = PythonConsoleLexer() + self.history = deque(['']) + self.console = Console(self, locals) + self.input_handler = InputHandler(self) + + if banner is None: + self.text = (f'Python {sys.version.splitlines()[0]}\n' + 'Welcome to the KivyConsole -- A Python interpreter widget for Kivy!\n') + else: self.text = banner + self.prompt() + + def prompt(self, needs_more=False): + if needs_more: + prompt = self.prompt_2 + self._indent_level = self.count_indents() + if self.text.rstrip().endswith(':'): self._indent_level += 1 + else: + prompt = self.prompt_1 + self._indent_level = 0 + + indent = self.tab_width * self._indent_level + self.text += prompt + ' ' * indent + self._home_pos = self.cursor_index() - indent + self.reset_undo() + + def count_indents(self): + return ilen(takewhile(str.isspace, self.history[1])) // self.tab_width + + def keyboard_on_key_down(self, window, keycode, text, modifiers): + """Emulate a python console: disallow editing of previous console output.""" + if keycode[0] in CTRL or keycode[0] in SHIFT and 'ctrl' in modifiers: return + + key = Key(keycode[0], 'shift' in modifiers, 'ctrl' in modifiers) + + # force `selection_from` <= `selection_to` (mouse selections can reverse the order): + _from, _to = sorted((self.selection_from, self.selection_to)) + has_selection = bool(self.selection_text) + i, home, end = self.cursor_index(), self._home_pos, len(self.text) + + read_only = i < home or has_selection and _from < home + at_home = i == home + at_end = i == end + + kwargs = locals(); del kwargs['self'] + if handle := self.input_handler(key, read_only): return handle(**kwargs) + + return super().keyboard_on_key_down(window, keycode, text, modifiers) + + def move_cursor(self, pos): + """Similar to `do_cursor_movement` but we account for `_home_pos` and we return the new cursor index.""" + if pos == 'end' : index = len(self.text) + elif pos == 'home' : index = self._home_pos + elif pos == 'left' : index = self.cursor_index() - 1 + elif pos == 'right': index = self.cursor_index() + 1 + self.cursor = self.get_cursor_from_index(index) + return index + + def input_from_history(self, reverse=False): + self._history_index += -1 if reverse else 1 + self._history_index = min(max(0, self._history_index), len(self.history) - 1) + self.text = self.text[: self._home_pos] + self.history[self._history_index] + + +if __name__ == "__main__": + from textwrap import dedent + from kivy.app import App + from kivy.lang import Builder + + KV = """ + KivyConsole: + font_name : './UbuntuMono-R.ttf' + style_name: 'monokai' + """ + + + class KivyInterpreter(App): + def build(self): return Builder.load_string(dedent(KV)) + + + KivyInterpreter().run() \ No newline at end of file diff --git a/libs/garden/garden.navigationdrawer/__init__.py b/remoteshell/libs/navigationdrawer/__init__.py similarity index 99% rename from libs/garden/garden.navigationdrawer/__init__.py rename to remoteshell/libs/navigationdrawer/__init__.py index 7021e67..f899d0b 100644 --- a/libs/garden/garden.navigationdrawer/__init__.py +++ b/remoteshell/libs/navigationdrawer/__init__.py @@ -445,7 +445,7 @@ def set_main_panel(self, widget): # Clear existing side panel entries if len(self._main_panel.children) > 0: for child in self._main_panel.children: - self._main_panel.remove(child) + self._main_panel.remove_widget(child) # Set new side panel self._main_panel.add_widget(widget) self.main_panel = widget diff --git a/remoteshell/libs/navigationdrawer/_version.py b/remoteshell/libs/navigationdrawer/_version.py new file mode 100644 index 0000000..a6221b3 --- /dev/null +++ b/remoteshell/libs/navigationdrawer/_version.py @@ -0,0 +1 @@ +__version__ = '1.0.2' diff --git a/libs/garden/garden.navigationdrawer/navigationdrawer_gradient_ltor.png b/remoteshell/libs/navigationdrawer/navigationdrawer_gradient_ltor.png similarity index 100% rename from libs/garden/garden.navigationdrawer/navigationdrawer_gradient_ltor.png rename to remoteshell/libs/navigationdrawer/navigationdrawer_gradient_ltor.png diff --git a/libs/garden/garden.navigationdrawer/navigationdrawer_gradient_rtol.png b/remoteshell/libs/navigationdrawer/navigationdrawer_gradient_rtol.png similarity index 100% rename from libs/garden/garden.navigationdrawer/navigationdrawer_gradient_rtol.png rename to remoteshell/libs/navigationdrawer/navigationdrawer_gradient_rtol.png diff --git a/remoteshell/libs/navigationdrawer/tests/test_import.py b/remoteshell/libs/navigationdrawer/tests/test_import.py new file mode 100644 index 0000000..9bbe60d --- /dev/null +++ b/remoteshell/libs/navigationdrawer/tests/test_import.py @@ -0,0 +1,6 @@ +import pytest + + +def test_flower(): + from kivy_garden.navigationdrawer import NavigationDrawer + widget = NavigationDrawer() diff --git a/main.py b/remoteshell/main.py similarity index 95% rename from main.py rename to remoteshell/main.py index b0a682b..392a574 100644 --- a/main.py +++ b/remoteshell/main.py @@ -16,8 +16,10 @@ from kivy.properties import StringProperty from kivy.app import App -from kivy.garden import navigationdrawer +from libs.navigationdrawer import NavigationDrawer from kivy.uix.screenmanager import Screen +from kivy.core.window import Window +Window.softinput_mode = 'below_target' app = None #+---[RSA 3072]----+ @@ -120,7 +122,10 @@ class MainScreen(Screen): def __init__(self, **kwargs): super(MainScreen, self).__init__(**kwargs) - ip = socket.gethostbyname(socket.gethostname()) + try: + ip = socket.gethostbyname(socket.gethostname()) + except Exception as Error: + ip = socket.gethostbyname('localhost') if ip.startswith('127.'): interfaces = ['eth0', 'eth1', 'eth2', 'wlan0', 'wlan1', 'wifi0', 'tiwlan0', 'tiwlan1', 'ath0', 'ath1', 'ppp0'] diff --git a/plyer_command_list.rst b/remoteshell/plyer_command_list.rst similarity index 81% rename from plyer_command_list.rst rename to remoteshell/plyer_command_list.rst index d9f4d83..3efa784 100644 --- a/plyer_command_list.rst +++ b/remoteshell/plyer_command_list.rst @@ -17,7 +17,7 @@ Command list - ACCELERATOR_ - GYROSCOPE_ - CALL_ - + TTS(Text to speech) ------------------- @@ -28,33 +28,6 @@ Example:: top_ -GPS ---- - -.. _GPS: - -.. note:: - - This will work only on versions before android 6.0 . - - For android 6.0 + the coder needs to explictly ask permissions. - - -Here is an example of the usage of gps:: - - from plyer import gps - coordinate = 0 - def print_locations(**kwargs): - global coordinate - coordinate = kwargs - - gps.configure(on_location=print_locations) - gps.start() - # later - print coordinate - gps.stop() - - Notification ------------ @@ -207,4 +180,3 @@ IrBlaster Example:: from plyer import irblaster - diff --git a/remotekivy.kv b/remoteshell/remotekivy.kv similarity index 56% rename from remotekivy.kv rename to remoteshell/remotekivy.kv index e301b66..eab17ce 100644 --- a/remotekivy.kv +++ b/remoteshell/remotekivy.kv @@ -11,9 +11,13 @@ text: open('plyer_command_list.rst').read() + #KivyConsole + opacity: 1 on_release: app.root.ids.manager.current = self.text.lower() + size_hint_y: None + height: sp(64) orientation: 'vertical' @@ -23,9 +27,13 @@ text: 'Plyer' NavigationButton text: 'Console' + NavigationButton + text: 'Python Interpretter' + Widget - size_hint_y: .2 + size_hint_y: None + height: sp(64) pos_hint: {'top': 1} canvas: Color: @@ -37,13 +45,13 @@ size_hint_x: None width: self.height border: 0, 0, 0, 0 - background_normal: 'ham_icon.png' - background_down: 'ham_icon.png' + background_normal: 'data/ham_icon.png' + background_down: 'data/ham_icon.png' opacity: 1 if self.state == 'normal' else .5 on_state: app.root.toggle_state() Widget Image: - source: 'icon.png' + source: 'data/icon.png' mipmap: True size_hint_x: None width: '100dp' @@ -59,7 +67,7 @@ NavigationDrawer NavigationScreen Screen Image: - source: 'background.png' + source: 'data/background.png' allow_stretch: True keep_ratio: False BoxLayout @@ -69,8 +77,22 @@ NavigationDrawer id: manager MainScreen name: 'remote' - # CommandScreen - # name: 'plyer' + CommandScreen + name: 'plyer' ShellScreen name: 'console' - + on_enter: + if not self.children:\ + from libs.kivy_console import KivyConsole as ShellConsole;\ + sc = ShellConsole();\ + sc.font_name = 'RobotoMono-Regular.ttf';\ + self.add_widget(sc) + Screen + name: 'python interpretter' + on_enter: + if not self.children:\ + from libs.kivy_python_console import KivyConsole as PythonConsole;\ + pc = PythonConsole();\ + pc.font_name = 'RobotoMono-Regular.ttf';\ + pc.style_name = 'monokai';\ + self.add_widget(pc) diff --git a/test.py b/tests/test.py similarity index 100% rename from test.py rename to tests/test.py diff --git a/tools/build/build-android.sh b/tools/build/build-android.sh new file mode 100644 index 0000000..d8663ae --- /dev/null +++ b/tools/build/build-android.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euxo pipefail + +ROOT=$(realpath $(dirname "$0")/../..) + +cd "$ROOT/tools/build" + +BUILDOZER_BIN_DIR="$ROOT/.buildozer/bin" BUILDOZER_BUILD_DIR="${ROOT}"/.buildozer buildozer android debug deploy run logcat diff --git a/buildozer.spec b/tools/build/buildozer.spec similarity index 72% rename from buildozer.spec rename to tools/build/buildozer.spec index 3c76fc0..f616cdd 100644 --- a/buildozer.spec +++ b/tools/build/buildozer.spec @@ -1,5 +1,6 @@ [app] +icon=%(source.dir)s/data/icon.png # title of the application title = Kivy Remote Shell @@ -10,7 +11,7 @@ package.name = remoteshell package.domain = org.kivy # indicate where the source code is living -source.dir = . +source.dir = ../../remoteshell source.include_exts = py,png,kv,rst # search the version information into the source code @@ -18,7 +19,7 @@ version.regex = __version__ = '(.*)' version.filename = %(source.dir)s/main.py # requirements of the app -requirements = android,cryptography,pyasn1,bcrypt,attrs,twisted,kivy,docutils,pygments,cffi +requirements = android,cryptography,pyasn1,bcrypt,attrs,twisted,kivy,docutils,pygments,cffi, more_itertools,plyer # android specific android.permissions = INTERNET, WAKE_LOCK, CAMERA, VIBRATE, ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION, SEND_SMS, CALL_PRIVILEGED, CALL_PHONE, BLUETOOTH @@ -27,13 +28,20 @@ android.permissions = INTERNET, WAKE_LOCK, CAMERA, VIBRATE, ACCESS_COARSE_LOCATI #android.api=22 android.accept_sdk_license=True + +# (str) Android logcat filters to use +android.logcat_filters = *:S python,mediaserver,SDL:D android.wakelock=True + orientation=portrait fullscreen=True p4a.branch = develop +p4a.local_recipes = p4a_recipes -#presplash.filename= +#presplash.filename= [buildozer] log_level = 2 warn_on_root = 1 +#bin_dir = ../..//buildozer/bin +#build_dir = ../../.buildozer diff --git a/tools/build/p4a_recipes/cryptography/__init__.py b/tools/build/p4a_recipes/cryptography/__init__.py new file mode 100644 index 0000000..182c745 --- /dev/null +++ b/tools/build/p4a_recipes/cryptography/__init__.py @@ -0,0 +1,22 @@ +from pythonforandroid.recipe import CompiledComponentsPythonRecipe, Recipe + + +class CryptographyRecipe(CompiledComponentsPythonRecipe): + name = 'cryptography' + version = '2.8' + url = 'https://github.com/pyca/cryptography/archive/{version}.tar.gz' + depends = ['openssl', 'six', 'setuptools', 'cffi'] + call_hostpython_via_targetpython = False + + def get_recipe_env(self, arch): + env = super().get_recipe_env(arch) + + openssl_recipe = Recipe.get_recipe('openssl', self.ctx) + env['CFLAGS'] += openssl_recipe.include_flags(arch) + env['LDFLAGS'] += openssl_recipe.link_dirs_flags(arch) + env['LIBS'] = openssl_recipe.link_libs_flags() + + return env + + +recipe = CryptographyRecipe() diff --git a/requirements.txt b/tools/build/requirements.txt similarity index 79% rename from requirements.txt rename to tools/build/requirements.txt index af72371..47c63b7 100644 --- a/requirements.txt +++ b/tools/build/requirements.txt @@ -6,3 +6,5 @@ pycrypto==2.6.1 requests==2.32.2 Twisted==24.7.0 #zope.interface==4.3.2 +more_itertools==2.2 +plyer==master From a089d7420aff51a84dd173010ba395f47d070564 Mon Sep 17 00:00:00 2001 From: Akshay Arora Date: Tue, 25 Feb 2025 20:37:04 +0530 Subject: [PATCH 2/5] Update android script --- tools/build/build-android.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build/build-android.sh b/tools/build/build-android.sh index d8663ae..79acc90 100644 --- a/tools/build/build-android.sh +++ b/tools/build/build-android.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -euxo pipefail +set -eux pipefail ROOT=$(realpath $(dirname "$0")/../..) From d2dd3ebfa2eafee3f148278ae325cdc15071ce24 Mon Sep 17 00:00:00 2001 From: Akshay Arora Date: Tue, 25 Feb 2025 21:01:49 +0530 Subject: [PATCH 3/5] Update android.yml --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index cc22739..1c88a67 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -26,7 +26,7 @@ jobs: pip install Cython - name: Install Linux dependencies if: matrix.os == 'ubuntu-latest' - run: sudo apt -y install automake + run: sudo apt -y install libltdl-dev automake - name: buildozer android debug run: | sh tools/build/build-android.sh From 3b03f9af30202af6ecc6750ead48f0ec67ac5fd3 Mon Sep 17 00:00:00 2001 From: Akshay Arora Date: Tue, 25 Feb 2025 21:46:21 +0530 Subject: [PATCH 4/5] Update python version --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 1c88a67..d10f65d 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -11,7 +11,7 @@ jobs: - name: Setup python uses: actions/setup-python@v2 with: - python-version: 3.8 + python-version: 3.12 - uses: actions/checkout@v2 - uses: actions/cache@v4 with: From 27a2ef610b7b10381d9ec27c57d2692601f32670 Mon Sep 17 00:00:00 2001 From: Akshay Arora Date: Tue, 25 Feb 2025 21:48:54 +0530 Subject: [PATCH 5/5] Update android.yml --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d10f65d..513828d 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -23,7 +23,7 @@ jobs: - name: Setup environment run: | pip install buildozer - pip install Cython + pip install Cython setuptools - name: Install Linux dependencies if: matrix.os == 'ubuntu-latest' run: sudo apt -y install libltdl-dev automake