├── .github └── workflows │ └── build.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── assets ├── logo.png ├── logo.svg └── waydroidstop.jpg ├── clickable.yaml ├── deps ├── pexpect │ ├── ANSI.py │ ├── FSM.py │ ├── __init__.py │ ├── _async.py │ ├── bashrc.sh │ ├── exceptions.py │ ├── expect.py │ ├── fdpexpect.py │ ├── popen_spawn.py │ ├── pty_spawn.py │ ├── pxssh.py │ ├── replwrap.py │ ├── run.py │ ├── screen.py │ ├── spawnbase.py │ └── utils.py └── ptyprocess │ ├── __init__.py │ ├── _fork_pty.py │ ├── ptyprocess.py │ └── util.py ├── manifest.json.in ├── po ├── CMakeLists.txt ├── de.po ├── fr.po ├── nl.po ├── pt_PT.po └── waydroidhelper.aaronhafer.pot ├── qml ├── About.qml ├── Installer.qml ├── Main.qml ├── PasswordPrompt.qml ├── Uninstaller.qml └── modules │ └── CenteredLabel.qml ├── src ├── __init__.py ├── installer.py ├── main.py ├── pam.py ├── password_type.py └── waydroid-custom-init ├── waydroidhelper.apparmor └── waydroidhelper.desktop.in /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Marcel Alexandru Nitan 2 | # https://github.com/nitanmarcel/cinny-click-packaging/blob/main/.github/workflows/build.yml 3 | 4 | name: Clickable Build 5 | 6 | on: [ push, pull_request ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Install clickable 15 | run: | 16 | python3 -m pip install clickable-ut 17 | - name: Build 18 | run: | 19 | clickable build 20 | - name: Upload Artifacts 21 | uses: actions/upload-artifact@v3 22 | with: 23 | name: waydroid_helper_UNCONFINED 24 | path: build/all/app/*.click 25 | if-no-files-found: error 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | project(waydroidhelper C CXX) 3 | 4 | # Automatically create moc files 5 | set(CMAKE_AUTOMOC ON) 6 | 7 | execute_process( 8 | COMMAND dpkg-architecture -qDEB_HOST_MULTIARCH 9 | OUTPUT_VARIABLE ARCH_TRIPLET 10 | OUTPUT_STRIP_TRAILING_WHITESPACE 11 | ) 12 | set(QT_IMPORTS_DIR "lib/${ARCH_TRIPLET}") 13 | 14 | set(PROJECT_NAME "waydroidhelper") 15 | set(FULL_PROJECT_NAME "waydroidhelper.aaronhafer") 16 | set(DATA_DIR /) 17 | set(DESKTOP_FILE_NAME ${PROJECT_NAME}.desktop) 18 | 19 | # This command figures out the minimum SDK framework for use in the manifest 20 | # file via the environment variable provided by Clickable or sets a default value otherwise. 21 | if(DEFINED ENV{SDK_FRAMEWORK}) 22 | set(CLICK_FRAMEWORK "$ENV{SDK_FRAMEWORK}") 23 | else() 24 | set(CLICK_FRAMEWORK "ubuntu-sdk-16.04.3") 25 | endif() 26 | 27 | # This figures out the target architecture for use in the manifest file. 28 | if(DEFINED ENV{ARCH}) 29 | set(CLICK_ARCH "$ENV{ARCH}") 30 | else() 31 | execute_process( 32 | COMMAND dpkg-architecture -qDEB_HOST_ARCH 33 | OUTPUT_VARIABLE CLICK_ARCH 34 | OUTPUT_STRIP_TRAILING_WHITESPACE 35 | ) 36 | endif() 37 | 38 | configure_file(manifest.json.in ${CMAKE_CURRENT_BINARY_DIR}/manifest.json) 39 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/manifest.json DESTINATION ${CMAKE_INSTALL_PREFIX}) 40 | install(FILES ${PROJECT_NAME}.apparmor DESTINATION ${DATA_DIR}) 41 | 42 | install(DIRECTORY assets DESTINATION ${DATA_DIR}) 43 | install(DIRECTORY src DESTINATION ${DATA_DIR}) 44 | 45 | install(DIRECTORY qml DESTINATION ${DATA_DIR}) 46 | install(DIRECTORY deps DESTINATION ${DATA_DIR}) 47 | # Translations 48 | file(GLOB_RECURSE I18N_SRC_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/po qml/*.qml qml/*.js) 49 | list(APPEND I18N_SRC_FILES ${DESKTOP_FILE_NAME}.in.h) 50 | 51 | find_program(INTLTOOL_MERGE intltool-merge) 52 | if(NOT INTLTOOL_MERGE) 53 | message(FATAL_ERROR "Could not find intltool-merge, please install the intltool package") 54 | endif() 55 | find_program(INTLTOOL_EXTRACT intltool-extract) 56 | if(NOT INTLTOOL_EXTRACT) 57 | message(FATAL_ERROR "Could not find intltool-extract, please install the intltool package") 58 | endif() 59 | 60 | add_custom_target(${DESKTOP_FILE_NAME} ALL 61 | COMMENT "Merging translations into ${DESKTOP_FILE_NAME}..." 62 | COMMAND LC_ALL=C ${INTLTOOL_MERGE} -d -u ${CMAKE_SOURCE_DIR}/po ${CMAKE_SOURCE_DIR}/${DESKTOP_FILE_NAME}.in ${DESKTOP_FILE_NAME} 63 | COMMAND sed -i 's/${PROJECT_NAME}-//g' ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE_NAME} 64 | ) 65 | 66 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE_NAME} DESTINATION ${DATA_DIR}) 67 | 68 | add_subdirectory(po) 69 | 70 | # Make source files visible in qtcreator 71 | file(GLOB_RECURSE PROJECT_SRC_FILES 72 | RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} 73 | qml/*.qml 74 | qml/*.js 75 | src/* 76 | deps/* 77 | *.json 78 | *.json.in 79 | *.apparmor 80 | *.desktop.in 81 | ) 82 | 83 | add_custom_target(${PROJECT_NAME}_FILES ALL SOURCES ${PROJECT_SRC_FILES}) 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Waydroid Helper 2 | 3 | A collection of tools that help you to use Waydroid on UbuntuTouch. 4 | Current features: 5 | * Install/Uninstall WayDroid 6 | * Hide unwanted apps 7 | * A "Stop App" 8 | * Access to help resources 9 | 10 | Translations can be submitted here: https://poeditor.com/join/project?hash=DIES6h7HNF 11 | 12 | Donations: https://www.paypal.com/paypalme/AaronTheIssueGuy 13 | 14 | ## License 15 | 16 | Copyright (C) 2021-2022 Aaron Hafer 17 | 18 | This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published 19 | by the Free Software Foundation. 20 | 21 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 22 | 23 | You should have received a copy of the GNU General Public License along with this program. If not, see . 24 | -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aarontheissueguy/WaydroidHelper/26800434fb1be15a5bd87cfa5ec31be2f03aaf21/assets/logo.png -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /assets/waydroidstop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aarontheissueguy/WaydroidHelper/26800434fb1be15a5bd87cfa5ec31be2f03aaf21/assets/waydroidstop.jpg -------------------------------------------------------------------------------- /clickable.yaml: -------------------------------------------------------------------------------- 1 | clickable_minimum_required: 6.22.0 2 | builder: pure-qml-cmake 3 | kill: qmlscene 4 | skip_review: true 5 | framework: ubuntu-sdk-20.04 6 | -------------------------------------------------------------------------------- /deps/pexpect/ANSI.py: -------------------------------------------------------------------------------- 1 | '''This implements an ANSI (VT100) terminal emulator as a subclass of screen. 2 | 3 | PEXPECT LICENSE 4 | 5 | This license is approved by the OSI and FSF as GPL-compatible. 6 | http://opensource.org/licenses/isc-license.txt 7 | 8 | Copyright (c) 2012, Noah Spurrier 9 | PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY 10 | PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE 11 | COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. 12 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 | 20 | ''' 21 | 22 | # references: 23 | # http://en.wikipedia.org/wiki/ANSI_escape_code 24 | # http://www.retards.org/terminals/vt102.html 25 | # http://vt100.net/docs/vt102-ug/contents.html 26 | # http://vt100.net/docs/vt220-rm/ 27 | # http://www.termsys.demon.co.uk/vtansi.htm 28 | 29 | from . import screen 30 | from . import FSM 31 | import string 32 | 33 | # 34 | # The 'Do.*' functions are helper functions for the ANSI class. 35 | # 36 | def DoEmit (fsm): 37 | 38 | screen = fsm.memory[0] 39 | screen.write_ch(fsm.input_symbol) 40 | 41 | def DoStartNumber (fsm): 42 | 43 | fsm.memory.append (fsm.input_symbol) 44 | 45 | def DoBuildNumber (fsm): 46 | 47 | ns = fsm.memory.pop() 48 | ns = ns + fsm.input_symbol 49 | fsm.memory.append (ns) 50 | 51 | def DoBackOne (fsm): 52 | 53 | screen = fsm.memory[0] 54 | screen.cursor_back () 55 | 56 | def DoBack (fsm): 57 | 58 | count = int(fsm.memory.pop()) 59 | screen = fsm.memory[0] 60 | screen.cursor_back (count) 61 | 62 | def DoDownOne (fsm): 63 | 64 | screen = fsm.memory[0] 65 | screen.cursor_down () 66 | 67 | def DoDown (fsm): 68 | 69 | count = int(fsm.memory.pop()) 70 | screen = fsm.memory[0] 71 | screen.cursor_down (count) 72 | 73 | def DoForwardOne (fsm): 74 | 75 | screen = fsm.memory[0] 76 | screen.cursor_forward () 77 | 78 | def DoForward (fsm): 79 | 80 | count = int(fsm.memory.pop()) 81 | screen = fsm.memory[0] 82 | screen.cursor_forward (count) 83 | 84 | def DoUpReverse (fsm): 85 | 86 | screen = fsm.memory[0] 87 | screen.cursor_up_reverse() 88 | 89 | def DoUpOne (fsm): 90 | 91 | screen = fsm.memory[0] 92 | screen.cursor_up () 93 | 94 | def DoUp (fsm): 95 | 96 | count = int(fsm.memory.pop()) 97 | screen = fsm.memory[0] 98 | screen.cursor_up (count) 99 | 100 | def DoHome (fsm): 101 | 102 | c = int(fsm.memory.pop()) 103 | r = int(fsm.memory.pop()) 104 | screen = fsm.memory[0] 105 | screen.cursor_home (r,c) 106 | 107 | def DoHomeOrigin (fsm): 108 | 109 | c = 1 110 | r = 1 111 | screen = fsm.memory[0] 112 | screen.cursor_home (r,c) 113 | 114 | def DoEraseDown (fsm): 115 | 116 | screen = fsm.memory[0] 117 | screen.erase_down() 118 | 119 | def DoErase (fsm): 120 | 121 | arg = int(fsm.memory.pop()) 122 | screen = fsm.memory[0] 123 | if arg == 0: 124 | screen.erase_down() 125 | elif arg == 1: 126 | screen.erase_up() 127 | elif arg == 2: 128 | screen.erase_screen() 129 | 130 | def DoEraseEndOfLine (fsm): 131 | 132 | screen = fsm.memory[0] 133 | screen.erase_end_of_line() 134 | 135 | def DoEraseLine (fsm): 136 | 137 | arg = int(fsm.memory.pop()) 138 | screen = fsm.memory[0] 139 | if arg == 0: 140 | screen.erase_end_of_line() 141 | elif arg == 1: 142 | screen.erase_start_of_line() 143 | elif arg == 2: 144 | screen.erase_line() 145 | 146 | def DoEnableScroll (fsm): 147 | 148 | screen = fsm.memory[0] 149 | screen.scroll_screen() 150 | 151 | def DoCursorSave (fsm): 152 | 153 | screen = fsm.memory[0] 154 | screen.cursor_save_attrs() 155 | 156 | def DoCursorRestore (fsm): 157 | 158 | screen = fsm.memory[0] 159 | screen.cursor_restore_attrs() 160 | 161 | def DoScrollRegion (fsm): 162 | 163 | screen = fsm.memory[0] 164 | r2 = int(fsm.memory.pop()) 165 | r1 = int(fsm.memory.pop()) 166 | screen.scroll_screen_rows (r1,r2) 167 | 168 | def DoMode (fsm): 169 | 170 | screen = fsm.memory[0] 171 | mode = fsm.memory.pop() # Should be 4 172 | # screen.setReplaceMode () 173 | 174 | def DoLog (fsm): 175 | 176 | screen = fsm.memory[0] 177 | fsm.memory = [screen] 178 | fout = open ('log', 'a') 179 | fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n') 180 | fout.close() 181 | 182 | class term (screen.screen): 183 | 184 | '''This class is an abstract, generic terminal. 185 | This does nothing. This is a placeholder that 186 | provides a common base class for other terminals 187 | such as an ANSI terminal. ''' 188 | 189 | def __init__ (self, r=24, c=80, *args, **kwargs): 190 | 191 | screen.screen.__init__(self, r,c,*args,**kwargs) 192 | 193 | class ANSI (term): 194 | '''This class implements an ANSI (VT100) terminal. 195 | It is a stream filter that recognizes ANSI terminal 196 | escape sequences and maintains the state of a screen object. ''' 197 | 198 | def __init__ (self, r=24,c=80,*args,**kwargs): 199 | 200 | term.__init__(self,r,c,*args,**kwargs) 201 | 202 | #self.screen = screen (24,80) 203 | self.state = FSM.FSM ('INIT',[self]) 204 | self.state.set_default_transition (DoLog, 'INIT') 205 | self.state.add_transition_any ('INIT', DoEmit, 'INIT') 206 | self.state.add_transition ('\x1b', 'INIT', None, 'ESC') 207 | self.state.add_transition_any ('ESC', DoLog, 'INIT') 208 | self.state.add_transition ('(', 'ESC', None, 'G0SCS') 209 | self.state.add_transition (')', 'ESC', None, 'G1SCS') 210 | self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT') 211 | self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT') 212 | self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT') 213 | self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT') 214 | self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT') 215 | self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT') 216 | self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT') 217 | self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad. 218 | self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND') 219 | self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT') 220 | self.state.add_transition ('[', 'ESC', None, 'ELB') 221 | # ELB means Escape Left Bracket. That is ^[[ 222 | self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT') 223 | self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT') 224 | self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT') 225 | self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT') 226 | self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT') 227 | self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT') 228 | self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT') 229 | self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT') 230 | self.state.add_transition ('m', 'ELB', self.do_sgr, 'INIT') 231 | self.state.add_transition ('?', 'ELB', None, 'MODECRAP') 232 | self.state.add_transition_list (string.digits, 'ELB', DoStartNumber, 'NUMBER_1') 233 | self.state.add_transition_list (string.digits, 'NUMBER_1', DoBuildNumber, 'NUMBER_1') 234 | self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT') 235 | self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT') 236 | self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT') 237 | self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT') 238 | self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT') 239 | self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT') 240 | self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT') 241 | ### It gets worse... the 'm' code can have infinite number of 242 | ### number;number;number before it. I've never seen more than two, 243 | ### but the specs say it's allowed. crap! 244 | self.state.add_transition ('m', 'NUMBER_1', self.do_sgr, 'INIT') 245 | ### LED control. Same implementation problem as 'm' code. 246 | self.state.add_transition ('q', 'NUMBER_1', self.do_decsca, 'INIT') 247 | 248 | # \E[?47h switch to alternate screen 249 | # \E[?47l restores to normal screen from alternate screen. 250 | self.state.add_transition_list (string.digits, 'MODECRAP', DoStartNumber, 'MODECRAP_NUM') 251 | self.state.add_transition_list (string.digits, 'MODECRAP_NUM', DoBuildNumber, 'MODECRAP_NUM') 252 | self.state.add_transition ('l', 'MODECRAP_NUM', self.do_modecrap, 'INIT') 253 | self.state.add_transition ('h', 'MODECRAP_NUM', self.do_modecrap, 'INIT') 254 | 255 | #RM Reset Mode Esc [ Ps l none 256 | self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON') 257 | self.state.add_transition_any ('SEMICOLON', DoLog, 'INIT') 258 | self.state.add_transition_list (string.digits, 'SEMICOLON', DoStartNumber, 'NUMBER_2') 259 | self.state.add_transition_list (string.digits, 'NUMBER_2', DoBuildNumber, 'NUMBER_2') 260 | self.state.add_transition_any ('NUMBER_2', DoLog, 'INIT') 261 | self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT') 262 | self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT') 263 | self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT') 264 | ### It gets worse... the 'm' code can have infinite number of 265 | ### number;number;number before it. I've never seen more than two, 266 | ### but the specs say it's allowed. crap! 267 | self.state.add_transition ('m', 'NUMBER_2', self.do_sgr, 'INIT') 268 | ### LED control. Same problem as 'm' code. 269 | self.state.add_transition ('q', 'NUMBER_2', self.do_decsca, 'INIT') 270 | self.state.add_transition (';', 'NUMBER_2', None, 'SEMICOLON_X') 271 | 272 | # Create a state for 'q' and 'm' which allows an infinite number of ignored numbers 273 | self.state.add_transition_any ('SEMICOLON_X', DoLog, 'INIT') 274 | self.state.add_transition_list (string.digits, 'SEMICOLON_X', DoStartNumber, 'NUMBER_X') 275 | self.state.add_transition_list (string.digits, 'NUMBER_X', DoBuildNumber, 'NUMBER_X') 276 | self.state.add_transition_any ('NUMBER_X', DoLog, 'INIT') 277 | self.state.add_transition ('m', 'NUMBER_X', self.do_sgr, 'INIT') 278 | self.state.add_transition ('q', 'NUMBER_X', self.do_decsca, 'INIT') 279 | self.state.add_transition (';', 'NUMBER_X', None, 'SEMICOLON_X') 280 | 281 | def process (self, c): 282 | """Process a single character. Called by :meth:`write`.""" 283 | if isinstance(c, bytes): 284 | c = self._decode(c) 285 | self.state.process(c) 286 | 287 | def process_list (self, l): 288 | 289 | self.write(l) 290 | 291 | def write (self, s): 292 | """Process text, writing it to the virtual screen while handling 293 | ANSI escape codes. 294 | """ 295 | if isinstance(s, bytes): 296 | s = self._decode(s) 297 | for c in s: 298 | self.process(c) 299 | 300 | def flush (self): 301 | pass 302 | 303 | def write_ch (self, ch): 304 | '''This puts a character at the current cursor position. The cursor 305 | position is moved forward with wrap-around, but no scrolling is done if 306 | the cursor hits the lower-right corner of the screen. ''' 307 | 308 | if isinstance(ch, bytes): 309 | ch = self._decode(ch) 310 | 311 | #\r and \n both produce a call to cr() and lf(), respectively. 312 | ch = ch[0] 313 | 314 | if ch == u'\r': 315 | self.cr() 316 | return 317 | if ch == u'\n': 318 | self.crlf() 319 | return 320 | if ch == chr(screen.BS): 321 | self.cursor_back() 322 | return 323 | self.put_abs(self.cur_r, self.cur_c, ch) 324 | old_r = self.cur_r 325 | old_c = self.cur_c 326 | self.cursor_forward() 327 | if old_c == self.cur_c: 328 | self.cursor_down() 329 | if old_r != self.cur_r: 330 | self.cursor_home (self.cur_r, 1) 331 | else: 332 | self.scroll_up () 333 | self.cursor_home (self.cur_r, 1) 334 | self.erase_line() 335 | 336 | def do_sgr (self, fsm): 337 | '''Select Graphic Rendition, e.g. color. ''' 338 | screen = fsm.memory[0] 339 | fsm.memory = [screen] 340 | 341 | def do_decsca (self, fsm): 342 | '''Select character protection attribute. ''' 343 | screen = fsm.memory[0] 344 | fsm.memory = [screen] 345 | 346 | def do_modecrap (self, fsm): 347 | '''Handler for \x1b[?h and \x1b[?l. If anyone 348 | wanted to actually use these, they'd need to add more states to the 349 | FSM rather than just improve or override this method. ''' 350 | screen = fsm.memory[0] 351 | fsm.memory = [screen] 352 | -------------------------------------------------------------------------------- /deps/pexpect/FSM.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | '''This module implements a Finite State Machine (FSM). In addition to state 4 | this FSM also maintains a user defined "memory". So this FSM can be used as a 5 | Push-down Automata (PDA) since a PDA is a FSM + memory. 6 | 7 | The following describes how the FSM works, but you will probably also need to 8 | see the example function to understand how the FSM is used in practice. 9 | 10 | You define an FSM by building tables of transitions. For a given input symbol 11 | the process() method uses these tables to decide what action to call and what 12 | the next state will be. The FSM has a table of transitions that associate: 13 | 14 | (input_symbol, current_state) --> (action, next_state) 15 | 16 | Where "action" is a function you define. The symbols and states can be any 17 | objects. You use the add_transition() and add_transition_list() methods to add 18 | to the transition table. The FSM also has a table of transitions that 19 | associate: 20 | 21 | (current_state) --> (action, next_state) 22 | 23 | You use the add_transition_any() method to add to this transition table. The 24 | FSM also has one default transition that is not associated with any specific 25 | input_symbol or state. You use the set_default_transition() method to set the 26 | default transition. 27 | 28 | When an action function is called it is passed a reference to the FSM. The 29 | action function may then access attributes of the FSM such as input_symbol, 30 | current_state, or "memory". The "memory" attribute can be any object that you 31 | want to pass along to the action functions. It is not used by the FSM itself. 32 | For parsing you would typically pass a list to be used as a stack. 33 | 34 | The processing sequence is as follows. The process() method is given an 35 | input_symbol to process. The FSM will search the table of transitions that 36 | associate: 37 | 38 | (input_symbol, current_state) --> (action, next_state) 39 | 40 | If the pair (input_symbol, current_state) is found then process() will call the 41 | associated action function and then set the current state to the next_state. 42 | 43 | If the FSM cannot find a match for (input_symbol, current_state) it will then 44 | search the table of transitions that associate: 45 | 46 | (current_state) --> (action, next_state) 47 | 48 | If the current_state is found then the process() method will call the 49 | associated action function and then set the current state to the next_state. 50 | Notice that this table lacks an input_symbol. It lets you define transitions 51 | for a current_state and ANY input_symbol. Hence, it is called the "any" table. 52 | Remember, it is always checked after first searching the table for a specific 53 | (input_symbol, current_state). 54 | 55 | For the case where the FSM did not match either of the previous two cases the 56 | FSM will try to use the default transition. If the default transition is 57 | defined then the process() method will call the associated action function and 58 | then set the current state to the next_state. This lets you define a default 59 | transition as a catch-all case. You can think of it as an exception handler. 60 | There can be only one default transition. 61 | 62 | Finally, if none of the previous cases are defined for an input_symbol and 63 | current_state then the FSM will raise an exception. This may be desirable, but 64 | you can always prevent this just by defining a default transition. 65 | 66 | Noah Spurrier 20020822 67 | 68 | PEXPECT LICENSE 69 | 70 | This license is approved by the OSI and FSF as GPL-compatible. 71 | http://opensource.org/licenses/isc-license.txt 72 | 73 | Copyright (c) 2012, Noah Spurrier 74 | PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY 75 | PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE 76 | COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. 77 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 78 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 79 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 80 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 81 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 82 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 83 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 84 | 85 | ''' 86 | 87 | class ExceptionFSM(Exception): 88 | 89 | '''This is the FSM Exception class.''' 90 | 91 | def __init__(self, value): 92 | self.value = value 93 | 94 | def __str__(self): 95 | return 'ExceptionFSM: ' + str(self.value) 96 | 97 | class FSM: 98 | 99 | '''This is a Finite State Machine (FSM). 100 | ''' 101 | 102 | def __init__(self, initial_state, memory=None): 103 | 104 | '''This creates the FSM. You set the initial state here. The "memory" 105 | attribute is any object that you want to pass along to the action 106 | functions. It is not used by the FSM. For parsing you would typically 107 | pass a list to be used as a stack. ''' 108 | 109 | # Map (input_symbol, current_state) --> (action, next_state). 110 | self.state_transitions = {} 111 | # Map (current_state) --> (action, next_state). 112 | self.state_transitions_any = {} 113 | self.default_transition = None 114 | 115 | self.input_symbol = None 116 | self.initial_state = initial_state 117 | self.current_state = self.initial_state 118 | self.next_state = None 119 | self.action = None 120 | self.memory = memory 121 | 122 | def reset (self): 123 | 124 | '''This sets the current_state to the initial_state and sets 125 | input_symbol to None. The initial state was set by the constructor 126 | __init__(). ''' 127 | 128 | self.current_state = self.initial_state 129 | self.input_symbol = None 130 | 131 | def add_transition (self, input_symbol, state, action=None, next_state=None): 132 | 133 | '''This adds a transition that associates: 134 | 135 | (input_symbol, current_state) --> (action, next_state) 136 | 137 | The action may be set to None in which case the process() method will 138 | ignore the action and only set the next_state. The next_state may be 139 | set to None in which case the current state will be unchanged. 140 | 141 | You can also set transitions for a list of symbols by using 142 | add_transition_list(). ''' 143 | 144 | if next_state is None: 145 | next_state = state 146 | self.state_transitions[(input_symbol, state)] = (action, next_state) 147 | 148 | def add_transition_list (self, list_input_symbols, state, action=None, next_state=None): 149 | 150 | '''This adds the same transition for a list of input symbols. 151 | You can pass a list or a string. Note that it is handy to use 152 | string.digits, string.whitespace, string.letters, etc. to add 153 | transitions that match character classes. 154 | 155 | The action may be set to None in which case the process() method will 156 | ignore the action and only set the next_state. The next_state may be 157 | set to None in which case the current state will be unchanged. ''' 158 | 159 | if next_state is None: 160 | next_state = state 161 | for input_symbol in list_input_symbols: 162 | self.add_transition (input_symbol, state, action, next_state) 163 | 164 | def add_transition_any (self, state, action=None, next_state=None): 165 | 166 | '''This adds a transition that associates: 167 | 168 | (current_state) --> (action, next_state) 169 | 170 | That is, any input symbol will match the current state. 171 | The process() method checks the "any" state associations after it first 172 | checks for an exact match of (input_symbol, current_state). 173 | 174 | The action may be set to None in which case the process() method will 175 | ignore the action and only set the next_state. The next_state may be 176 | set to None in which case the current state will be unchanged. ''' 177 | 178 | if next_state is None: 179 | next_state = state 180 | self.state_transitions_any [state] = (action, next_state) 181 | 182 | def set_default_transition (self, action, next_state): 183 | 184 | '''This sets the default transition. This defines an action and 185 | next_state if the FSM cannot find the input symbol and the current 186 | state in the transition list and if the FSM cannot find the 187 | current_state in the transition_any list. This is useful as a final 188 | fall-through state for catching errors and undefined states. 189 | 190 | The default transition can be removed by setting the attribute 191 | default_transition to None. ''' 192 | 193 | self.default_transition = (action, next_state) 194 | 195 | def get_transition (self, input_symbol, state): 196 | 197 | '''This returns (action, next state) given an input_symbol and state. 198 | This does not modify the FSM state, so calling this method has no side 199 | effects. Normally you do not call this method directly. It is called by 200 | process(). 201 | 202 | The sequence of steps to check for a defined transition goes from the 203 | most specific to the least specific. 204 | 205 | 1. Check state_transitions[] that match exactly the tuple, 206 | (input_symbol, state) 207 | 208 | 2. Check state_transitions_any[] that match (state) 209 | In other words, match a specific state and ANY input_symbol. 210 | 211 | 3. Check if the default_transition is defined. 212 | This catches any input_symbol and any state. 213 | This is a handler for errors, undefined states, or defaults. 214 | 215 | 4. No transition was defined. If we get here then raise an exception. 216 | ''' 217 | 218 | if (input_symbol, state) in self.state_transitions: 219 | return self.state_transitions[(input_symbol, state)] 220 | elif state in self.state_transitions_any: 221 | return self.state_transitions_any[state] 222 | elif self.default_transition is not None: 223 | return self.default_transition 224 | else: 225 | raise ExceptionFSM ('Transition is undefined: (%s, %s).' % 226 | (str(input_symbol), str(state)) ) 227 | 228 | def process (self, input_symbol): 229 | 230 | '''This is the main method that you call to process input. This may 231 | cause the FSM to change state and call an action. This method calls 232 | get_transition() to find the action and next_state associated with the 233 | input_symbol and current_state. If the action is None then the action 234 | is not called and only the current state is changed. This method 235 | processes one complete input symbol. You can process a list of symbols 236 | (or a string) by calling process_list(). ''' 237 | 238 | self.input_symbol = input_symbol 239 | (self.action, self.next_state) = self.get_transition (self.input_symbol, self.current_state) 240 | if self.action is not None: 241 | self.action (self) 242 | self.current_state = self.next_state 243 | self.next_state = None 244 | 245 | def process_list (self, input_symbols): 246 | 247 | '''This takes a list and sends each element to process(). The list may 248 | be a string or any iterable object. ''' 249 | 250 | for s in input_symbols: 251 | self.process (s) 252 | 253 | ############################################################################## 254 | # The following is an example that demonstrates the use of the FSM class to 255 | # process an RPN expression. Run this module from the command line. You will 256 | # get a prompt > for input. Enter an RPN Expression. Numbers may be integers. 257 | # Operators are * / + - Use the = sign to evaluate and print the expression. 258 | # For example: 259 | # 260 | # 167 3 2 2 * * * 1 - = 261 | # 262 | # will print: 263 | # 264 | # 2003 265 | ############################################################################## 266 | 267 | import sys 268 | import string 269 | 270 | PY3 = (sys.version_info[0] >= 3) 271 | 272 | # 273 | # These define the actions. 274 | # Note that "memory" is a list being used as a stack. 275 | # 276 | 277 | def BeginBuildNumber (fsm): 278 | fsm.memory.append (fsm.input_symbol) 279 | 280 | def BuildNumber (fsm): 281 | s = fsm.memory.pop () 282 | s = s + fsm.input_symbol 283 | fsm.memory.append (s) 284 | 285 | def EndBuildNumber (fsm): 286 | s = fsm.memory.pop () 287 | fsm.memory.append (int(s)) 288 | 289 | def DoOperator (fsm): 290 | ar = fsm.memory.pop() 291 | al = fsm.memory.pop() 292 | if fsm.input_symbol == '+': 293 | fsm.memory.append (al + ar) 294 | elif fsm.input_symbol == '-': 295 | fsm.memory.append (al - ar) 296 | elif fsm.input_symbol == '*': 297 | fsm.memory.append (al * ar) 298 | elif fsm.input_symbol == '/': 299 | fsm.memory.append (al / ar) 300 | 301 | def DoEqual (fsm): 302 | print(str(fsm.memory.pop())) 303 | 304 | def Error (fsm): 305 | print('That does not compute.') 306 | print(str(fsm.input_symbol)) 307 | 308 | def main(): 309 | 310 | '''This is where the example starts and the FSM state transitions are 311 | defined. Note that states are strings (such as 'INIT'). This is not 312 | necessary, but it makes the example easier to read. ''' 313 | 314 | f = FSM ('INIT', []) 315 | f.set_default_transition (Error, 'INIT') 316 | f.add_transition_any ('INIT', None, 'INIT') 317 | f.add_transition ('=', 'INIT', DoEqual, 'INIT') 318 | f.add_transition_list (string.digits, 'INIT', BeginBuildNumber, 'BUILDING_NUMBER') 319 | f.add_transition_list (string.digits, 'BUILDING_NUMBER', BuildNumber, 'BUILDING_NUMBER') 320 | f.add_transition_list (string.whitespace, 'BUILDING_NUMBER', EndBuildNumber, 'INIT') 321 | f.add_transition_list ('+-*/', 'INIT', DoOperator, 'INIT') 322 | 323 | print() 324 | print('Enter an RPN Expression.') 325 | print('Numbers may be integers. Operators are * / + -') 326 | print('Use the = sign to evaluate and print the expression.') 327 | print('For example: ') 328 | print(' 167 3 2 2 * * * 1 - =') 329 | inputstr = (input if PY3 else raw_input)('> ') # analysis:ignore 330 | f.process_list(inputstr) 331 | 332 | 333 | if __name__ == '__main__': 334 | main() 335 | -------------------------------------------------------------------------------- /deps/pexpect/__init__.py: -------------------------------------------------------------------------------- 1 | '''Pexpect is a Python module for spawning child applications and controlling 2 | them automatically. Pexpect can be used for automating interactive applications 3 | such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup 4 | scripts for duplicating software package installations on different servers. It 5 | can be used for automated software testing. Pexpect is in the spirit of Don 6 | Libes' Expect, but Pexpect is pure Python. Other Expect-like modules for Python 7 | require TCL and Expect or require C extensions to be compiled. Pexpect does not 8 | use C, Expect, or TCL extensions. It should work on any platform that supports 9 | the standard Python pty module. The Pexpect interface focuses on ease of use so 10 | that simple tasks are easy. 11 | 12 | There are two main interfaces to the Pexpect system; these are the function, 13 | run() and the class, spawn. The spawn class is more powerful. The run() 14 | function is simpler than spawn, and is good for quickly calling program. When 15 | you call the run() function it executes a given program and then returns the 16 | output. This is a handy replacement for os.system(). 17 | 18 | For example:: 19 | 20 | pexpect.run('ls -la') 21 | 22 | The spawn class is the more powerful interface to the Pexpect system. You can 23 | use this to spawn a child program then interact with it by sending input and 24 | expecting responses (waiting for patterns in the child's output). 25 | 26 | For example:: 27 | 28 | child = pexpect.spawn('scp foo user@example.com:.') 29 | child.expect('Password:') 30 | child.sendline(mypassword) 31 | 32 | This works even for commands that ask for passwords or other input outside of 33 | the normal stdio streams. For example, ssh reads input directly from the TTY 34 | device which bypasses stdin. 35 | 36 | Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett, 37 | Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids 38 | vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin, 39 | Jacques-Etienne Baudoux, Geoffrey Marshall, Francisco Lourenco, Glen Mabey, 40 | Karthik Gurusamy, Fernando Perez, Corey Minyard, Jon Cohen, Guillaume 41 | Chazarain, Andrew Ryan, Nick Craig-Wood, Andrew Stone, Jorgen Grahn, John 42 | Spiegel, Jan Grant, and Shane Kerr. Let me know if I forgot anyone. 43 | 44 | Pexpect is free, open source, and all that good stuff. 45 | http://pexpect.sourceforge.net/ 46 | 47 | PEXPECT LICENSE 48 | 49 | This license is approved by the OSI and FSF as GPL-compatible. 50 | http://opensource.org/licenses/isc-license.txt 51 | 52 | Copyright (c) 2012, Noah Spurrier 53 | PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY 54 | PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE 55 | COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. 56 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 57 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 58 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 59 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 60 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 61 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 62 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 63 | 64 | ''' 65 | 66 | import sys 67 | PY3 = (sys.version_info[0] >= 3) 68 | 69 | from .exceptions import ExceptionPexpect, EOF, TIMEOUT 70 | from .utils import split_command_line, which, is_executable_file 71 | from .expect import Expecter, searcher_re, searcher_string 72 | 73 | if sys.platform != 'win32': 74 | # On Unix, these are available at the top level for backwards compatibility 75 | from .pty_spawn import spawn, spawnu 76 | from .run import run, runu 77 | 78 | __version__ = '4.8.0' 79 | __revision__ = '' 80 | __all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'spawnu', 'run', 'runu', 81 | 'which', 'split_command_line', '__version__', '__revision__'] 82 | 83 | 84 | 85 | # vim: set shiftround expandtab tabstop=4 shiftwidth=4 ft=python autoindent : 86 | -------------------------------------------------------------------------------- /deps/pexpect/_async.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import errno 3 | import signal 4 | 5 | from pexpect import EOF 6 | 7 | @asyncio.coroutine 8 | def expect_async(expecter, timeout=None): 9 | # First process data that was previously read - if it maches, we don't need 10 | # async stuff. 11 | idx = expecter.existing_data() 12 | if idx is not None: 13 | return idx 14 | if not expecter.spawn.async_pw_transport: 15 | pw = PatternWaiter() 16 | pw.set_expecter(expecter) 17 | transport, pw = yield from asyncio.get_event_loop()\ 18 | .connect_read_pipe(lambda: pw, expecter.spawn) 19 | expecter.spawn.async_pw_transport = pw, transport 20 | else: 21 | pw, transport = expecter.spawn.async_pw_transport 22 | pw.set_expecter(expecter) 23 | transport.resume_reading() 24 | try: 25 | return (yield from asyncio.wait_for(pw.fut, timeout)) 26 | except asyncio.TimeoutError as e: 27 | transport.pause_reading() 28 | return expecter.timeout(e) 29 | 30 | @asyncio.coroutine 31 | def repl_run_command_async(repl, cmdlines, timeout=-1): 32 | res = [] 33 | repl.child.sendline(cmdlines[0]) 34 | for line in cmdlines[1:]: 35 | yield from repl._expect_prompt(timeout=timeout, async_=True) 36 | res.append(repl.child.before) 37 | repl.child.sendline(line) 38 | 39 | # Command was fully submitted, now wait for the next prompt 40 | prompt_idx = yield from repl._expect_prompt(timeout=timeout, async_=True) 41 | if prompt_idx == 1: 42 | # We got the continuation prompt - command was incomplete 43 | repl.child.kill(signal.SIGINT) 44 | yield from repl._expect_prompt(timeout=1, async_=True) 45 | raise ValueError("Continuation prompt found - input was incomplete:") 46 | return u''.join(res + [repl.child.before]) 47 | 48 | class PatternWaiter(asyncio.Protocol): 49 | transport = None 50 | 51 | def set_expecter(self, expecter): 52 | self.expecter = expecter 53 | self.fut = asyncio.Future() 54 | 55 | def found(self, result): 56 | if not self.fut.done(): 57 | self.fut.set_result(result) 58 | self.transport.pause_reading() 59 | 60 | def error(self, exc): 61 | if not self.fut.done(): 62 | self.fut.set_exception(exc) 63 | self.transport.pause_reading() 64 | 65 | def connection_made(self, transport): 66 | self.transport = transport 67 | 68 | def data_received(self, data): 69 | spawn = self.expecter.spawn 70 | s = spawn._decoder.decode(data) 71 | spawn._log(s, 'read') 72 | 73 | if self.fut.done(): 74 | spawn._before.write(s) 75 | spawn._buffer.write(s) 76 | return 77 | 78 | try: 79 | index = self.expecter.new_data(s) 80 | if index is not None: 81 | # Found a match 82 | self.found(index) 83 | except Exception as e: 84 | self.expecter.errored() 85 | self.error(e) 86 | 87 | def eof_received(self): 88 | # N.B. If this gets called, async will close the pipe (the spawn object) 89 | # for us 90 | try: 91 | self.expecter.spawn.flag_eof = True 92 | index = self.expecter.eof() 93 | except EOF as e: 94 | self.error(e) 95 | else: 96 | self.found(index) 97 | 98 | def connection_lost(self, exc): 99 | if isinstance(exc, OSError) and exc.errno == errno.EIO: 100 | # We may get here without eof_received being called, e.g on Linux 101 | self.eof_received() 102 | elif exc is not None: 103 | self.error(exc) 104 | -------------------------------------------------------------------------------- /deps/pexpect/bashrc.sh: -------------------------------------------------------------------------------- 1 | # Different platforms have different names for the systemwide bashrc 2 | if [[ -f /etc/bashrc ]]; then 3 | source /etc/bashrc 4 | fi 5 | if [[ -f /etc/bash.bashrc ]]; then 6 | source /etc/bash.bashrc 7 | fi 8 | if [[ -f ~/.bashrc ]]; then 9 | source ~/.bashrc 10 | fi 11 | 12 | # Reset PS1 so pexpect can find it 13 | PS1="$" 14 | 15 | # Unset PROMPT_COMMAND, so that it can't change PS1 to something unexpected. 16 | unset PROMPT_COMMAND 17 | -------------------------------------------------------------------------------- /deps/pexpect/exceptions.py: -------------------------------------------------------------------------------- 1 | """Exception classes used by Pexpect""" 2 | 3 | import traceback 4 | import sys 5 | 6 | class ExceptionPexpect(Exception): 7 | '''Base class for all exceptions raised by this module. 8 | ''' 9 | 10 | def __init__(self, value): 11 | super(ExceptionPexpect, self).__init__(value) 12 | self.value = value 13 | 14 | def __str__(self): 15 | return str(self.value) 16 | 17 | def get_trace(self): 18 | '''This returns an abbreviated stack trace with lines that only concern 19 | the caller. In other words, the stack trace inside the Pexpect module 20 | is not included. ''' 21 | 22 | tblist = traceback.extract_tb(sys.exc_info()[2]) 23 | tblist = [item for item in tblist if ('pexpect/__init__' not in item[0]) 24 | and ('pexpect/expect' not in item[0])] 25 | tblist = traceback.format_list(tblist) 26 | return ''.join(tblist) 27 | 28 | 29 | class EOF(ExceptionPexpect): 30 | '''Raised when EOF is read from a child. 31 | This usually means the child has exited.''' 32 | 33 | 34 | class TIMEOUT(ExceptionPexpect): 35 | '''Raised when a read time exceeds the timeout. ''' 36 | -------------------------------------------------------------------------------- /deps/pexpect/expect.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from .exceptions import EOF, TIMEOUT 4 | 5 | class Expecter(object): 6 | def __init__(self, spawn, searcher, searchwindowsize=-1): 7 | self.spawn = spawn 8 | self.searcher = searcher 9 | # A value of -1 means to use the figure from spawn, which should 10 | # be None or a positive number. 11 | if searchwindowsize == -1: 12 | searchwindowsize = spawn.searchwindowsize 13 | self.searchwindowsize = searchwindowsize 14 | self.lookback = None 15 | if hasattr(searcher, 'longest_string'): 16 | self.lookback = searcher.longest_string 17 | 18 | def do_search(self, window, freshlen): 19 | spawn = self.spawn 20 | searcher = self.searcher 21 | if freshlen > len(window): 22 | freshlen = len(window) 23 | index = searcher.search(window, freshlen, self.searchwindowsize) 24 | if index >= 0: 25 | spawn._buffer = spawn.buffer_type() 26 | spawn._buffer.write(window[searcher.end:]) 27 | spawn.before = spawn._before.getvalue()[ 28 | 0:-(len(window) - searcher.start)] 29 | spawn._before = spawn.buffer_type() 30 | spawn._before.write(window[searcher.end:]) 31 | spawn.after = window[searcher.start:searcher.end] 32 | spawn.match = searcher.match 33 | spawn.match_index = index 34 | # Found a match 35 | return index 36 | elif self.searchwindowsize or self.lookback: 37 | maintain = self.searchwindowsize or self.lookback 38 | if spawn._buffer.tell() > maintain: 39 | spawn._buffer = spawn.buffer_type() 40 | spawn._buffer.write(window[-maintain:]) 41 | 42 | def existing_data(self): 43 | # First call from a new call to expect_loop or expect_async. 44 | # self.searchwindowsize may have changed. 45 | # Treat all data as fresh. 46 | spawn = self.spawn 47 | before_len = spawn._before.tell() 48 | buf_len = spawn._buffer.tell() 49 | freshlen = before_len 50 | if before_len > buf_len: 51 | if not self.searchwindowsize: 52 | spawn._buffer = spawn.buffer_type() 53 | window = spawn._before.getvalue() 54 | spawn._buffer.write(window) 55 | elif buf_len < self.searchwindowsize: 56 | spawn._buffer = spawn.buffer_type() 57 | spawn._before.seek( 58 | max(0, before_len - self.searchwindowsize)) 59 | window = spawn._before.read() 60 | spawn._buffer.write(window) 61 | else: 62 | spawn._buffer.seek(max(0, buf_len - self.searchwindowsize)) 63 | window = spawn._buffer.read() 64 | else: 65 | if self.searchwindowsize: 66 | spawn._buffer.seek(max(0, buf_len - self.searchwindowsize)) 67 | window = spawn._buffer.read() 68 | else: 69 | window = spawn._buffer.getvalue() 70 | return self.do_search(window, freshlen) 71 | 72 | def new_data(self, data): 73 | # A subsequent call, after a call to existing_data. 74 | spawn = self.spawn 75 | freshlen = len(data) 76 | spawn._before.write(data) 77 | if not self.searchwindowsize: 78 | if self.lookback: 79 | # search lookback + new data. 80 | old_len = spawn._buffer.tell() 81 | spawn._buffer.write(data) 82 | spawn._buffer.seek(max(0, old_len - self.lookback)) 83 | window = spawn._buffer.read() 84 | else: 85 | # copy the whole buffer (really slow for large datasets). 86 | spawn._buffer.write(data) 87 | window = spawn.buffer 88 | else: 89 | if len(data) >= self.searchwindowsize or not spawn._buffer.tell(): 90 | window = data[-self.searchwindowsize:] 91 | spawn._buffer = spawn.buffer_type() 92 | spawn._buffer.write(window[-self.searchwindowsize:]) 93 | else: 94 | spawn._buffer.write(data) 95 | new_len = spawn._buffer.tell() 96 | spawn._buffer.seek(max(0, new_len - self.searchwindowsize)) 97 | window = spawn._buffer.read() 98 | return self.do_search(window, freshlen) 99 | 100 | def eof(self, err=None): 101 | spawn = self.spawn 102 | 103 | spawn.before = spawn._before.getvalue() 104 | spawn._buffer = spawn.buffer_type() 105 | spawn._before = spawn.buffer_type() 106 | spawn.after = EOF 107 | index = self.searcher.eof_index 108 | if index >= 0: 109 | spawn.match = EOF 110 | spawn.match_index = index 111 | return index 112 | else: 113 | spawn.match = None 114 | spawn.match_index = None 115 | msg = str(spawn) 116 | msg += '\nsearcher: %s' % self.searcher 117 | if err is not None: 118 | msg = str(err) + '\n' + msg 119 | 120 | exc = EOF(msg) 121 | exc.__cause__ = None # in Python 3.x we can use "raise exc from None" 122 | raise exc 123 | 124 | def timeout(self, err=None): 125 | spawn = self.spawn 126 | 127 | spawn.before = spawn._before.getvalue() 128 | spawn.after = TIMEOUT 129 | index = self.searcher.timeout_index 130 | if index >= 0: 131 | spawn.match = TIMEOUT 132 | spawn.match_index = index 133 | return index 134 | else: 135 | spawn.match = None 136 | spawn.match_index = None 137 | msg = str(spawn) 138 | msg += '\nsearcher: %s' % self.searcher 139 | if err is not None: 140 | msg = str(err) + '\n' + msg 141 | 142 | exc = TIMEOUT(msg) 143 | exc.__cause__ = None # in Python 3.x we can use "raise exc from None" 144 | raise exc 145 | 146 | def errored(self): 147 | spawn = self.spawn 148 | spawn.before = spawn._before.getvalue() 149 | spawn.after = None 150 | spawn.match = None 151 | spawn.match_index = None 152 | 153 | def expect_loop(self, timeout=-1): 154 | """Blocking expect""" 155 | spawn = self.spawn 156 | 157 | if timeout is not None: 158 | end_time = time.time() + timeout 159 | 160 | try: 161 | idx = self.existing_data() 162 | if idx is not None: 163 | return idx 164 | while True: 165 | # No match at this point 166 | if (timeout is not None) and (timeout < 0): 167 | return self.timeout() 168 | # Still have time left, so read more data 169 | incoming = spawn.read_nonblocking(spawn.maxread, timeout) 170 | if self.spawn.delayafterread is not None: 171 | time.sleep(self.spawn.delayafterread) 172 | idx = self.new_data(incoming) 173 | # Keep reading until exception or return. 174 | if idx is not None: 175 | return idx 176 | if timeout is not None: 177 | timeout = end_time - time.time() 178 | except EOF as e: 179 | return self.eof(e) 180 | except TIMEOUT as e: 181 | return self.timeout(e) 182 | except: 183 | self.errored() 184 | raise 185 | 186 | 187 | class searcher_string(object): 188 | '''This is a plain string search helper for the spawn.expect_any() method. 189 | This helper class is for speed. For more powerful regex patterns 190 | see the helper class, searcher_re. 191 | 192 | Attributes: 193 | 194 | eof_index - index of EOF, or -1 195 | timeout_index - index of TIMEOUT, or -1 196 | 197 | After a successful match by the search() method the following attributes 198 | are available: 199 | 200 | start - index into the buffer, first byte of match 201 | end - index into the buffer, first byte after match 202 | match - the matching string itself 203 | 204 | ''' 205 | 206 | def __init__(self, strings): 207 | '''This creates an instance of searcher_string. This argument 'strings' 208 | may be a list; a sequence of strings; or the EOF or TIMEOUT types. ''' 209 | 210 | self.eof_index = -1 211 | self.timeout_index = -1 212 | self._strings = [] 213 | self.longest_string = 0 214 | for n, s in enumerate(strings): 215 | if s is EOF: 216 | self.eof_index = n 217 | continue 218 | if s is TIMEOUT: 219 | self.timeout_index = n 220 | continue 221 | self._strings.append((n, s)) 222 | if len(s) > self.longest_string: 223 | self.longest_string = len(s) 224 | 225 | def __str__(self): 226 | '''This returns a human-readable string that represents the state of 227 | the object.''' 228 | 229 | ss = [(ns[0], ' %d: %r' % ns) for ns in self._strings] 230 | ss.append((-1, 'searcher_string:')) 231 | if self.eof_index >= 0: 232 | ss.append((self.eof_index, ' %d: EOF' % self.eof_index)) 233 | if self.timeout_index >= 0: 234 | ss.append((self.timeout_index, 235 | ' %d: TIMEOUT' % self.timeout_index)) 236 | ss.sort() 237 | ss = list(zip(*ss))[1] 238 | return '\n'.join(ss) 239 | 240 | def search(self, buffer, freshlen, searchwindowsize=None): 241 | '''This searches 'buffer' for the first occurrence of one of the search 242 | strings. 'freshlen' must indicate the number of bytes at the end of 243 | 'buffer' which have not been searched before. It helps to avoid 244 | searching the same, possibly big, buffer over and over again. 245 | 246 | See class spawn for the 'searchwindowsize' argument. 247 | 248 | If there is a match this returns the index of that string, and sets 249 | 'start', 'end' and 'match'. Otherwise, this returns -1. ''' 250 | 251 | first_match = None 252 | 253 | # 'freshlen' helps a lot here. Further optimizations could 254 | # possibly include: 255 | # 256 | # using something like the Boyer-Moore Fast String Searching 257 | # Algorithm; pre-compiling the search through a list of 258 | # strings into something that can scan the input once to 259 | # search for all N strings; realize that if we search for 260 | # ['bar', 'baz'] and the input is '...foo' we need not bother 261 | # rescanning until we've read three more bytes. 262 | # 263 | # Sadly, I don't know enough about this interesting topic. /grahn 264 | 265 | for index, s in self._strings: 266 | if searchwindowsize is None: 267 | # the match, if any, can only be in the fresh data, 268 | # or at the very end of the old data 269 | offset = -(freshlen + len(s)) 270 | else: 271 | # better obey searchwindowsize 272 | offset = -searchwindowsize 273 | n = buffer.find(s, offset) 274 | if n >= 0 and (first_match is None or n < first_match): 275 | first_match = n 276 | best_index, best_match = index, s 277 | if first_match is None: 278 | return -1 279 | self.match = best_match 280 | self.start = first_match 281 | self.end = self.start + len(self.match) 282 | return best_index 283 | 284 | 285 | class searcher_re(object): 286 | '''This is regular expression string search helper for the 287 | spawn.expect_any() method. This helper class is for powerful 288 | pattern matching. For speed, see the helper class, searcher_string. 289 | 290 | Attributes: 291 | 292 | eof_index - index of EOF, or -1 293 | timeout_index - index of TIMEOUT, or -1 294 | 295 | After a successful match by the search() method the following attributes 296 | are available: 297 | 298 | start - index into the buffer, first byte of match 299 | end - index into the buffer, first byte after match 300 | match - the re.match object returned by a successful re.search 301 | 302 | ''' 303 | 304 | def __init__(self, patterns): 305 | '''This creates an instance that searches for 'patterns' Where 306 | 'patterns' may be a list or other sequence of compiled regular 307 | expressions, or the EOF or TIMEOUT types.''' 308 | 309 | self.eof_index = -1 310 | self.timeout_index = -1 311 | self._searches = [] 312 | for n, s in enumerate(patterns): 313 | if s is EOF: 314 | self.eof_index = n 315 | continue 316 | if s is TIMEOUT: 317 | self.timeout_index = n 318 | continue 319 | self._searches.append((n, s)) 320 | 321 | def __str__(self): 322 | '''This returns a human-readable string that represents the state of 323 | the object.''' 324 | 325 | #ss = [(n, ' %d: re.compile("%s")' % 326 | # (n, repr(s.pattern))) for n, s in self._searches] 327 | ss = list() 328 | for n, s in self._searches: 329 | ss.append((n, ' %d: re.compile(%r)' % (n, s.pattern))) 330 | ss.append((-1, 'searcher_re:')) 331 | if self.eof_index >= 0: 332 | ss.append((self.eof_index, ' %d: EOF' % self.eof_index)) 333 | if self.timeout_index >= 0: 334 | ss.append((self.timeout_index, ' %d: TIMEOUT' % 335 | self.timeout_index)) 336 | ss.sort() 337 | ss = list(zip(*ss))[1] 338 | return '\n'.join(ss) 339 | 340 | def search(self, buffer, freshlen, searchwindowsize=None): 341 | '''This searches 'buffer' for the first occurrence of one of the regular 342 | expressions. 'freshlen' must indicate the number of bytes at the end of 343 | 'buffer' which have not been searched before. 344 | 345 | See class spawn for the 'searchwindowsize' argument. 346 | 347 | If there is a match this returns the index of that string, and sets 348 | 'start', 'end' and 'match'. Otherwise, returns -1.''' 349 | 350 | first_match = None 351 | # 'freshlen' doesn't help here -- we cannot predict the 352 | # length of a match, and the re module provides no help. 353 | if searchwindowsize is None: 354 | searchstart = 0 355 | else: 356 | searchstart = max(0, len(buffer) - searchwindowsize) 357 | for index, s in self._searches: 358 | match = s.search(buffer, searchstart) 359 | if match is None: 360 | continue 361 | n = match.start() 362 | if first_match is None or n < first_match: 363 | first_match = n 364 | the_match = match 365 | best_index = index 366 | if first_match is None: 367 | return -1 368 | self.start = first_match 369 | self.match = the_match 370 | self.end = self.match.end() 371 | return best_index 372 | -------------------------------------------------------------------------------- /deps/pexpect/fdpexpect.py: -------------------------------------------------------------------------------- 1 | '''This is like pexpect, but it will work with any file descriptor that you 2 | pass it. You are responsible for opening and close the file descriptor. 3 | This allows you to use Pexpect with sockets and named pipes (FIFOs). 4 | 5 | PEXPECT LICENSE 6 | 7 | This license is approved by the OSI and FSF as GPL-compatible. 8 | http://opensource.org/licenses/isc-license.txt 9 | 10 | Copyright (c) 2012, Noah Spurrier 11 | PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY 12 | PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE 13 | COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. 14 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 15 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 16 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 17 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 18 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 19 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 20 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 21 | 22 | ''' 23 | 24 | from .spawnbase import SpawnBase 25 | from .exceptions import ExceptionPexpect, TIMEOUT 26 | from .utils import select_ignore_interrupts, poll_ignore_interrupts 27 | import os 28 | 29 | __all__ = ['fdspawn'] 30 | 31 | class fdspawn(SpawnBase): 32 | '''This is like pexpect.spawn but allows you to supply your own open file 33 | descriptor. For example, you could use it to read through a file looking 34 | for patterns, or to control a modem or serial device. ''' 35 | 36 | def __init__ (self, fd, args=None, timeout=30, maxread=2000, searchwindowsize=None, 37 | logfile=None, encoding=None, codec_errors='strict', use_poll=False): 38 | '''This takes a file descriptor (an int) or an object that support the 39 | fileno() method (returning an int). All Python file-like objects 40 | support fileno(). ''' 41 | 42 | if type(fd) != type(0) and hasattr(fd, 'fileno'): 43 | fd = fd.fileno() 44 | 45 | if type(fd) != type(0): 46 | raise ExceptionPexpect('The fd argument is not an int. If this is a command string then maybe you want to use pexpect.spawn.') 47 | 48 | try: # make sure fd is a valid file descriptor 49 | os.fstat(fd) 50 | except OSError: 51 | raise ExceptionPexpect('The fd argument is not a valid file descriptor.') 52 | 53 | self.args = None 54 | self.command = None 55 | SpawnBase.__init__(self, timeout, maxread, searchwindowsize, logfile, 56 | encoding=encoding, codec_errors=codec_errors) 57 | self.child_fd = fd 58 | self.own_fd = False 59 | self.closed = False 60 | self.name = '' % fd 61 | self.use_poll = use_poll 62 | 63 | def close (self): 64 | """Close the file descriptor. 65 | 66 | Calling this method a second time does nothing, but if the file 67 | descriptor was closed elsewhere, :class:`OSError` will be raised. 68 | """ 69 | if self.child_fd == -1: 70 | return 71 | 72 | self.flush() 73 | os.close(self.child_fd) 74 | self.child_fd = -1 75 | self.closed = True 76 | 77 | def isalive (self): 78 | '''This checks if the file descriptor is still valid. If :func:`os.fstat` 79 | does not raise an exception then we assume it is alive. ''' 80 | 81 | if self.child_fd == -1: 82 | return False 83 | try: 84 | os.fstat(self.child_fd) 85 | return True 86 | except: 87 | return False 88 | 89 | def terminate (self, force=False): # pragma: no cover 90 | '''Deprecated and invalid. Just raises an exception.''' 91 | raise ExceptionPexpect('This method is not valid for file descriptors.') 92 | 93 | # These four methods are left around for backwards compatibility, but not 94 | # documented as part of fdpexpect. You're encouraged to use os.write 95 | # directly. 96 | def send(self, s): 97 | "Write to fd, return number of bytes written" 98 | s = self._coerce_send_string(s) 99 | self._log(s, 'send') 100 | 101 | b = self._encoder.encode(s, final=False) 102 | return os.write(self.child_fd, b) 103 | 104 | def sendline(self, s): 105 | "Write to fd with trailing newline, return number of bytes written" 106 | s = self._coerce_send_string(s) 107 | return self.send(s + self.linesep) 108 | 109 | def write(self, s): 110 | "Write to fd, return None" 111 | self.send(s) 112 | 113 | def writelines(self, sequence): 114 | "Call self.write() for each item in sequence" 115 | for s in sequence: 116 | self.write(s) 117 | 118 | def read_nonblocking(self, size=1, timeout=-1): 119 | """ 120 | Read from the file descriptor and return the result as a string. 121 | 122 | The read_nonblocking method of :class:`SpawnBase` assumes that a call 123 | to os.read will not block (timeout parameter is ignored). This is not 124 | the case for POSIX file-like objects such as sockets and serial ports. 125 | 126 | Use :func:`select.select`, timeout is implemented conditionally for 127 | POSIX systems. 128 | 129 | :param int size: Read at most *size* bytes. 130 | :param int timeout: Wait timeout seconds for file descriptor to be 131 | ready to read. When -1 (default), use self.timeout. When 0, poll. 132 | :return: String containing the bytes read 133 | """ 134 | if os.name == 'posix': 135 | if timeout == -1: 136 | timeout = self.timeout 137 | rlist = [self.child_fd] 138 | wlist = [] 139 | xlist = [] 140 | if self.use_poll: 141 | rlist = poll_ignore_interrupts(rlist, timeout) 142 | else: 143 | rlist, wlist, xlist = select_ignore_interrupts( 144 | rlist, wlist, xlist, timeout 145 | ) 146 | if self.child_fd not in rlist: 147 | raise TIMEOUT('Timeout exceeded.') 148 | return super(fdspawn, self).read_nonblocking(size) 149 | -------------------------------------------------------------------------------- /deps/pexpect/popen_spawn.py: -------------------------------------------------------------------------------- 1 | """Provides an interface like pexpect.spawn interface using subprocess.Popen 2 | """ 3 | import os 4 | import threading 5 | import subprocess 6 | import sys 7 | import time 8 | import signal 9 | import shlex 10 | 11 | try: 12 | from queue import Queue, Empty # Python 3 13 | except ImportError: 14 | from Queue import Queue, Empty # Python 2 15 | 16 | from .spawnbase import SpawnBase, PY3 17 | from .exceptions import EOF 18 | from .utils import string_types 19 | 20 | class PopenSpawn(SpawnBase): 21 | def __init__(self, cmd, timeout=30, maxread=2000, searchwindowsize=None, 22 | logfile=None, cwd=None, env=None, encoding=None, 23 | codec_errors='strict', preexec_fn=None): 24 | super(PopenSpawn, self).__init__(timeout=timeout, maxread=maxread, 25 | searchwindowsize=searchwindowsize, logfile=logfile, 26 | encoding=encoding, codec_errors=codec_errors) 27 | 28 | # Note that `SpawnBase` initializes `self.crlf` to `\r\n` 29 | # because the default behaviour for a PTY is to convert 30 | # incoming LF to `\r\n` (see the `onlcr` flag and 31 | # https://stackoverflow.com/a/35887657/5397009). Here we set 32 | # it to `os.linesep` because that is what the spawned 33 | # application outputs by default and `popen` doesn't translate 34 | # anything. 35 | if encoding is None: 36 | self.crlf = os.linesep.encode ("ascii") 37 | else: 38 | self.crlf = self.string_type (os.linesep) 39 | 40 | kwargs = dict(bufsize=0, stdin=subprocess.PIPE, 41 | stderr=subprocess.STDOUT, stdout=subprocess.PIPE, 42 | cwd=cwd, preexec_fn=preexec_fn, env=env) 43 | 44 | if sys.platform == 'win32': 45 | startupinfo = subprocess.STARTUPINFO() 46 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 47 | kwargs['startupinfo'] = startupinfo 48 | kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP 49 | 50 | if isinstance(cmd, string_types) and sys.platform != 'win32': 51 | cmd = shlex.split(cmd, posix=os.name == 'posix') 52 | 53 | self.proc = subprocess.Popen(cmd, **kwargs) 54 | self.pid = self.proc.pid 55 | self.closed = False 56 | self._buf = self.string_type() 57 | 58 | self._read_queue = Queue() 59 | self._read_thread = threading.Thread(target=self._read_incoming) 60 | self._read_thread.setDaemon(True) 61 | self._read_thread.start() 62 | 63 | _read_reached_eof = False 64 | 65 | def read_nonblocking(self, size, timeout): 66 | buf = self._buf 67 | if self._read_reached_eof: 68 | # We have already finished reading. Use up any buffered data, 69 | # then raise EOF 70 | if buf: 71 | self._buf = buf[size:] 72 | return buf[:size] 73 | else: 74 | self.flag_eof = True 75 | raise EOF('End Of File (EOF).') 76 | 77 | if timeout == -1: 78 | timeout = self.timeout 79 | elif timeout is None: 80 | timeout = 1e6 81 | 82 | t0 = time.time() 83 | while (time.time() - t0) < timeout and size and len(buf) < size: 84 | try: 85 | incoming = self._read_queue.get_nowait() 86 | except Empty: 87 | break 88 | else: 89 | if incoming is None: 90 | self._read_reached_eof = True 91 | break 92 | 93 | buf += self._decoder.decode(incoming, final=False) 94 | 95 | r, self._buf = buf[:size], buf[size:] 96 | 97 | self._log(r, 'read') 98 | return r 99 | 100 | def _read_incoming(self): 101 | """Run in a thread to move output from a pipe to a queue.""" 102 | fileno = self.proc.stdout.fileno() 103 | while 1: 104 | buf = b'' 105 | try: 106 | buf = os.read(fileno, 1024) 107 | except OSError as e: 108 | self._log(e, 'read') 109 | 110 | if not buf: 111 | # This indicates we have reached EOF 112 | self._read_queue.put(None) 113 | return 114 | 115 | self._read_queue.put(buf) 116 | 117 | def write(self, s): 118 | '''This is similar to send() except that there is no return value. 119 | ''' 120 | self.send(s) 121 | 122 | def writelines(self, sequence): 123 | '''This calls write() for each element in the sequence. 124 | 125 | The sequence can be any iterable object producing strings, typically a 126 | list of strings. This does not add line separators. There is no return 127 | value. 128 | ''' 129 | for s in sequence: 130 | self.send(s) 131 | 132 | def send(self, s): 133 | '''Send data to the subprocess' stdin. 134 | 135 | Returns the number of bytes written. 136 | ''' 137 | s = self._coerce_send_string(s) 138 | self._log(s, 'send') 139 | 140 | b = self._encoder.encode(s, final=False) 141 | if PY3: 142 | return self.proc.stdin.write(b) 143 | else: 144 | # On Python 2, .write() returns None, so we return the length of 145 | # bytes written ourselves. This assumes they all got written. 146 | self.proc.stdin.write(b) 147 | return len(b) 148 | 149 | def sendline(self, s=''): 150 | '''Wraps send(), sending string ``s`` to child process, with os.linesep 151 | automatically appended. Returns number of bytes written. ''' 152 | 153 | n = self.send(s) 154 | return n + self.send(self.linesep) 155 | 156 | def wait(self): 157 | '''Wait for the subprocess to finish. 158 | 159 | Returns the exit code. 160 | ''' 161 | status = self.proc.wait() 162 | if status >= 0: 163 | self.exitstatus = status 164 | self.signalstatus = None 165 | else: 166 | self.exitstatus = None 167 | self.signalstatus = -status 168 | self.terminated = True 169 | return status 170 | 171 | def kill(self, sig): 172 | '''Sends a Unix signal to the subprocess. 173 | 174 | Use constants from the :mod:`signal` module to specify which signal. 175 | ''' 176 | if sys.platform == 'win32': 177 | if sig in [signal.SIGINT, signal.CTRL_C_EVENT]: 178 | sig = signal.CTRL_C_EVENT 179 | elif sig in [signal.SIGBREAK, signal.CTRL_BREAK_EVENT]: 180 | sig = signal.CTRL_BREAK_EVENT 181 | else: 182 | sig = signal.SIGTERM 183 | 184 | os.kill(self.proc.pid, sig) 185 | 186 | def sendeof(self): 187 | '''Closes the stdin pipe from the writing end.''' 188 | self.proc.stdin.close() 189 | -------------------------------------------------------------------------------- /deps/pexpect/replwrap.py: -------------------------------------------------------------------------------- 1 | """Generic wrapper for read-eval-print-loops, a.k.a. interactive shells 2 | """ 3 | import os.path 4 | import signal 5 | import sys 6 | 7 | import pexpect 8 | 9 | PY3 = (sys.version_info[0] >= 3) 10 | 11 | if PY3: 12 | basestring = str 13 | 14 | PEXPECT_PROMPT = u'[PEXPECT_PROMPT>' 15 | PEXPECT_CONTINUATION_PROMPT = u'[PEXPECT_PROMPT+' 16 | 17 | class REPLWrapper(object): 18 | """Wrapper for a REPL. 19 | 20 | :param cmd_or_spawn: This can either be an instance of :class:`pexpect.spawn` 21 | in which a REPL has already been started, or a str command to start a new 22 | REPL process. 23 | :param str orig_prompt: The prompt to expect at first. 24 | :param str prompt_change: A command to change the prompt to something more 25 | unique. If this is ``None``, the prompt will not be changed. This will 26 | be formatted with the new and continuation prompts as positional 27 | parameters, so you can use ``{}`` style formatting to insert them into 28 | the command. 29 | :param str new_prompt: The more unique prompt to expect after the change. 30 | :param str extra_init_cmd: Commands to do extra initialisation, such as 31 | disabling pagers. 32 | """ 33 | def __init__(self, cmd_or_spawn, orig_prompt, prompt_change, 34 | new_prompt=PEXPECT_PROMPT, 35 | continuation_prompt=PEXPECT_CONTINUATION_PROMPT, 36 | extra_init_cmd=None): 37 | if isinstance(cmd_or_spawn, basestring): 38 | self.child = pexpect.spawn(cmd_or_spawn, echo=False, encoding='utf-8') 39 | else: 40 | self.child = cmd_or_spawn 41 | if self.child.echo: 42 | # Existing spawn instance has echo enabled, disable it 43 | # to prevent our input from being repeated to output. 44 | self.child.setecho(False) 45 | self.child.waitnoecho() 46 | 47 | if prompt_change is None: 48 | self.prompt = orig_prompt 49 | else: 50 | self.set_prompt(orig_prompt, 51 | prompt_change.format(new_prompt, continuation_prompt)) 52 | self.prompt = new_prompt 53 | self.continuation_prompt = continuation_prompt 54 | 55 | self._expect_prompt() 56 | 57 | if extra_init_cmd is not None: 58 | self.run_command(extra_init_cmd) 59 | 60 | def set_prompt(self, orig_prompt, prompt_change): 61 | self.child.expect(orig_prompt) 62 | self.child.sendline(prompt_change) 63 | 64 | def _expect_prompt(self, timeout=-1, async_=False): 65 | return self.child.expect_exact([self.prompt, self.continuation_prompt], 66 | timeout=timeout, async_=async_) 67 | 68 | def run_command(self, command, timeout=-1, async_=False): 69 | """Send a command to the REPL, wait for and return output. 70 | 71 | :param str command: The command to send. Trailing newlines are not needed. 72 | This should be a complete block of input that will trigger execution; 73 | if a continuation prompt is found after sending input, :exc:`ValueError` 74 | will be raised. 75 | :param int timeout: How long to wait for the next prompt. -1 means the 76 | default from the :class:`pexpect.spawn` object (default 30 seconds). 77 | None means to wait indefinitely. 78 | :param bool async_: On Python 3.4, or Python 3.3 with asyncio 79 | installed, passing ``async_=True`` will make this return an 80 | :mod:`asyncio` Future, which you can yield from to get the same 81 | result that this method would normally give directly. 82 | """ 83 | # Split up multiline commands and feed them in bit-by-bit 84 | cmdlines = command.splitlines() 85 | # splitlines ignores trailing newlines - add it back in manually 86 | if command.endswith('\n'): 87 | cmdlines.append('') 88 | if not cmdlines: 89 | raise ValueError("No command was given") 90 | 91 | if async_: 92 | from ._async import repl_run_command_async 93 | return repl_run_command_async(self, cmdlines, timeout) 94 | 95 | res = [] 96 | self.child.sendline(cmdlines[0]) 97 | for line in cmdlines[1:]: 98 | self._expect_prompt(timeout=timeout) 99 | res.append(self.child.before) 100 | self.child.sendline(line) 101 | 102 | # Command was fully submitted, now wait for the next prompt 103 | if self._expect_prompt(timeout=timeout) == 1: 104 | # We got the continuation prompt - command was incomplete 105 | self.child.kill(signal.SIGINT) 106 | self._expect_prompt(timeout=1) 107 | raise ValueError("Continuation prompt found - input was incomplete:\n" 108 | + command) 109 | return u''.join(res + [self.child.before]) 110 | 111 | def python(command="python"): 112 | """Start a Python shell and return a :class:`REPLWrapper` object.""" 113 | return REPLWrapper(command, u">>> ", u"import sys; sys.ps1={0!r}; sys.ps2={1!r}") 114 | 115 | def bash(command="bash"): 116 | """Start a bash shell and return a :class:`REPLWrapper` object.""" 117 | bashrc = os.path.join(os.path.dirname(__file__), 'bashrc.sh') 118 | child = pexpect.spawn(command, ['--rcfile', bashrc], echo=False, 119 | encoding='utf-8') 120 | 121 | # If the user runs 'env', the value of PS1 will be in the output. To avoid 122 | # replwrap seeing that as the next prompt, we'll embed the marker characters 123 | # for invisible characters in the prompt; these show up when inspecting the 124 | # environment variable, but not when bash displays the prompt. 125 | ps1 = PEXPECT_PROMPT[:5] + u'\\[\\]' + PEXPECT_PROMPT[5:] 126 | ps2 = PEXPECT_CONTINUATION_PROMPT[:5] + u'\\[\\]' + PEXPECT_CONTINUATION_PROMPT[5:] 127 | prompt_change = u"PS1='{0}' PS2='{1}' PROMPT_COMMAND=''".format(ps1, ps2) 128 | 129 | return REPLWrapper(child, u'\\$', prompt_change, 130 | extra_init_cmd="export PAGER=cat") 131 | -------------------------------------------------------------------------------- /deps/pexpect/run.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import types 3 | 4 | from .exceptions import EOF, TIMEOUT 5 | from .pty_spawn import spawn 6 | 7 | def run(command, timeout=30, withexitstatus=False, events=None, 8 | extra_args=None, logfile=None, cwd=None, env=None, **kwargs): 9 | 10 | ''' 11 | This function runs the given command; waits for it to finish; then 12 | returns all output as a string. STDERR is included in output. If the full 13 | path to the command is not given then the path is searched. 14 | 15 | Note that lines are terminated by CR/LF (\\r\\n) combination even on 16 | UNIX-like systems because this is the standard for pseudottys. If you set 17 | 'withexitstatus' to true, then run will return a tuple of (command_output, 18 | exitstatus). If 'withexitstatus' is false then this returns just 19 | command_output. 20 | 21 | The run() function can often be used instead of creating a spawn instance. 22 | For example, the following code uses spawn:: 23 | 24 | from pexpect import * 25 | child = spawn('scp foo user@example.com:.') 26 | child.expect('(?i)password') 27 | child.sendline(mypassword) 28 | 29 | The previous code can be replace with the following:: 30 | 31 | from pexpect import * 32 | run('scp foo user@example.com:.', events={'(?i)password': mypassword}) 33 | 34 | **Examples** 35 | 36 | Start the apache daemon on the local machine:: 37 | 38 | from pexpect import * 39 | run("/usr/local/apache/bin/apachectl start") 40 | 41 | Check in a file using SVN:: 42 | 43 | from pexpect import * 44 | run("svn ci -m 'automatic commit' my_file.py") 45 | 46 | Run a command and capture exit status:: 47 | 48 | from pexpect import * 49 | (command_output, exitstatus) = run('ls -l /bin', withexitstatus=1) 50 | 51 | The following will run SSH and execute 'ls -l' on the remote machine. The 52 | password 'secret' will be sent if the '(?i)password' pattern is ever seen:: 53 | 54 | run("ssh username@machine.example.com 'ls -l'", 55 | events={'(?i)password':'secret\\n'}) 56 | 57 | This will start mencoder to rip a video from DVD. This will also display 58 | progress ticks every 5 seconds as it runs. For example:: 59 | 60 | from pexpect import * 61 | def print_ticks(d): 62 | print d['event_count'], 63 | run("mencoder dvd://1 -o video.avi -oac copy -ovc copy", 64 | events={TIMEOUT:print_ticks}, timeout=5) 65 | 66 | The 'events' argument should be either a dictionary or a tuple list that 67 | contains patterns and responses. Whenever one of the patterns is seen 68 | in the command output, run() will send the associated response string. 69 | So, run() in the above example can be also written as: 70 | 71 | run("mencoder dvd://1 -o video.avi -oac copy -ovc copy", 72 | events=[(TIMEOUT,print_ticks)], timeout=5) 73 | 74 | Use a tuple list for events if the command output requires a delicate 75 | control over what pattern should be matched, since the tuple list is passed 76 | to pexpect() as its pattern list, with the order of patterns preserved. 77 | 78 | Note that you should put newlines in your string if Enter is necessary. 79 | 80 | Like the example above, the responses may also contain a callback, either 81 | a function or method. It should accept a dictionary value as an argument. 82 | The dictionary contains all the locals from the run() function, so you can 83 | access the child spawn object or any other variable defined in run() 84 | (event_count, child, and extra_args are the most useful). A callback may 85 | return True to stop the current run process. Otherwise run() continues 86 | until the next event. A callback may also return a string which will be 87 | sent to the child. 'extra_args' is not used by directly run(). It provides 88 | a way to pass data to a callback function through run() through the locals 89 | dictionary passed to a callback. 90 | 91 | Like :class:`spawn`, passing *encoding* will make it work with unicode 92 | instead of bytes. You can pass *codec_errors* to control how errors in 93 | encoding and decoding are handled. 94 | ''' 95 | if timeout == -1: 96 | child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env, 97 | **kwargs) 98 | else: 99 | child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile, 100 | cwd=cwd, env=env, **kwargs) 101 | if isinstance(events, list): 102 | patterns= [x for x,y in events] 103 | responses = [y for x,y in events] 104 | elif isinstance(events, dict): 105 | patterns = list(events.keys()) 106 | responses = list(events.values()) 107 | else: 108 | # This assumes EOF or TIMEOUT will eventually cause run to terminate. 109 | patterns = None 110 | responses = None 111 | child_result_list = [] 112 | event_count = 0 113 | while True: 114 | try: 115 | index = child.expect(patterns) 116 | if isinstance(child.after, child.allowed_string_types): 117 | child_result_list.append(child.before + child.after) 118 | else: 119 | # child.after may have been a TIMEOUT or EOF, 120 | # which we don't want appended to the list. 121 | child_result_list.append(child.before) 122 | if isinstance(responses[index], child.allowed_string_types): 123 | child.send(responses[index]) 124 | elif (isinstance(responses[index], types.FunctionType) or 125 | isinstance(responses[index], types.MethodType)): 126 | callback_result = responses[index](locals()) 127 | sys.stdout.flush() 128 | if isinstance(callback_result, child.allowed_string_types): 129 | child.send(callback_result) 130 | elif callback_result: 131 | break 132 | else: 133 | raise TypeError("parameter `event' at index {index} must be " 134 | "a string, method, or function: {value!r}" 135 | .format(index=index, value=responses[index])) 136 | event_count = event_count + 1 137 | except TIMEOUT: 138 | child_result_list.append(child.before) 139 | break 140 | except EOF: 141 | child_result_list.append(child.before) 142 | break 143 | child_result = child.string_type().join(child_result_list) 144 | if withexitstatus: 145 | child.close() 146 | return (child_result, child.exitstatus) 147 | else: 148 | return child_result 149 | 150 | def runu(command, timeout=30, withexitstatus=False, events=None, 151 | extra_args=None, logfile=None, cwd=None, env=None, **kwargs): 152 | """Deprecated: pass encoding to run() instead. 153 | """ 154 | kwargs.setdefault('encoding', 'utf-8') 155 | return run(command, timeout=timeout, withexitstatus=withexitstatus, 156 | events=events, extra_args=extra_args, logfile=logfile, cwd=cwd, 157 | env=env, **kwargs) 158 | -------------------------------------------------------------------------------- /deps/pexpect/screen.py: -------------------------------------------------------------------------------- 1 | '''This implements a virtual screen. This is used to support ANSI terminal 2 | emulation. The screen representation and state is implemented in this class. 3 | Most of the methods are inspired by ANSI screen control codes. The 4 | :class:`~pexpect.ANSI.ANSI` class extends this class to add parsing of ANSI 5 | escape codes. 6 | 7 | PEXPECT LICENSE 8 | 9 | This license is approved by the OSI and FSF as GPL-compatible. 10 | http://opensource.org/licenses/isc-license.txt 11 | 12 | Copyright (c) 2012, Noah Spurrier 13 | PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY 14 | PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE 15 | COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES. 16 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 17 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 19 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 20 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 21 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 22 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 23 | 24 | ''' 25 | 26 | import codecs 27 | import copy 28 | import sys 29 | 30 | import warnings 31 | 32 | warnings.warn(("pexpect.screen and pexpect.ANSI are deprecated. " 33 | "We recommend using pyte to emulate a terminal screen: " 34 | "https://pypi.python.org/pypi/pyte"), 35 | stacklevel=2) 36 | 37 | NUL = 0 # Fill character; ignored on input. 38 | ENQ = 5 # Transmit answerback message. 39 | BEL = 7 # Ring the bell. 40 | BS = 8 # Move cursor left. 41 | HT = 9 # Move cursor to next tab stop. 42 | LF = 10 # Line feed. 43 | VT = 11 # Same as LF. 44 | FF = 12 # Same as LF. 45 | CR = 13 # Move cursor to left margin or newline. 46 | SO = 14 # Invoke G1 character set. 47 | SI = 15 # Invoke G0 character set. 48 | XON = 17 # Resume transmission. 49 | XOFF = 19 # Halt transmission. 50 | CAN = 24 # Cancel escape sequence. 51 | SUB = 26 # Same as CAN. 52 | ESC = 27 # Introduce a control sequence. 53 | DEL = 127 # Fill character; ignored on input. 54 | SPACE = u' ' # Space or blank character. 55 | 56 | PY3 = (sys.version_info[0] >= 3) 57 | if PY3: 58 | unicode = str 59 | 60 | def constrain (n, min, max): 61 | 62 | '''This returns a number, n constrained to the min and max bounds. ''' 63 | 64 | if n < min: 65 | return min 66 | if n > max: 67 | return max 68 | return n 69 | 70 | class screen: 71 | '''This object maintains the state of a virtual text screen as a 72 | rectangular array. This maintains a virtual cursor position and handles 73 | scrolling as characters are added. This supports most of the methods needed 74 | by an ANSI text screen. Row and column indexes are 1-based (not zero-based, 75 | like arrays). 76 | 77 | Characters are represented internally using unicode. Methods that accept 78 | input characters, when passed 'bytes' (which in Python 2 is equivalent to 79 | 'str'), convert them from the encoding specified in the 'encoding' 80 | parameter to the constructor. Methods that return screen contents return 81 | unicode strings, with the exception of __str__() under Python 2. Passing 82 | ``encoding=None`` limits the API to only accept unicode input, so passing 83 | bytes in will raise :exc:`TypeError`. 84 | ''' 85 | def __init__(self, r=24, c=80, encoding='latin-1', encoding_errors='replace'): 86 | '''This initializes a blank screen of the given dimensions.''' 87 | 88 | self.rows = r 89 | self.cols = c 90 | self.encoding = encoding 91 | self.encoding_errors = encoding_errors 92 | if encoding is not None: 93 | self.decoder = codecs.getincrementaldecoder(encoding)(encoding_errors) 94 | else: 95 | self.decoder = None 96 | self.cur_r = 1 97 | self.cur_c = 1 98 | self.cur_saved_r = 1 99 | self.cur_saved_c = 1 100 | self.scroll_row_start = 1 101 | self.scroll_row_end = self.rows 102 | self.w = [ [SPACE] * self.cols for _ in range(self.rows)] 103 | 104 | def _decode(self, s): 105 | '''This converts from the external coding system (as passed to 106 | the constructor) to the internal one (unicode). ''' 107 | if self.decoder is not None: 108 | return self.decoder.decode(s) 109 | else: 110 | raise TypeError("This screen was constructed with encoding=None, " 111 | "so it does not handle bytes.") 112 | 113 | def _unicode(self): 114 | '''This returns a printable representation of the screen as a unicode 115 | string (which, under Python 3.x, is the same as 'str'). The end of each 116 | screen line is terminated by a newline.''' 117 | 118 | return u'\n'.join ([ u''.join(c) for c in self.w ]) 119 | 120 | if PY3: 121 | __str__ = _unicode 122 | else: 123 | __unicode__ = _unicode 124 | 125 | def __str__(self): 126 | '''This returns a printable representation of the screen. The end of 127 | each screen line is terminated by a newline. ''' 128 | encoding = self.encoding or 'ascii' 129 | return self._unicode().encode(encoding, 'replace') 130 | 131 | def dump (self): 132 | '''This returns a copy of the screen as a unicode string. This is similar to 133 | __str__/__unicode__ except that lines are not terminated with line 134 | feeds.''' 135 | 136 | return u''.join ([ u''.join(c) for c in self.w ]) 137 | 138 | def pretty (self): 139 | '''This returns a copy of the screen as a unicode string with an ASCII 140 | text box around the screen border. This is similar to 141 | __str__/__unicode__ except that it adds a box.''' 142 | 143 | top_bot = u'+' + u'-'*self.cols + u'+\n' 144 | return top_bot + u'\n'.join([u'|'+line+u'|' for line in unicode(self).split(u'\n')]) + u'\n' + top_bot 145 | 146 | def fill (self, ch=SPACE): 147 | 148 | if isinstance(ch, bytes): 149 | ch = self._decode(ch) 150 | 151 | self.fill_region (1,1,self.rows,self.cols, ch) 152 | 153 | def fill_region (self, rs,cs, re,ce, ch=SPACE): 154 | 155 | if isinstance(ch, bytes): 156 | ch = self._decode(ch) 157 | 158 | rs = constrain (rs, 1, self.rows) 159 | re = constrain (re, 1, self.rows) 160 | cs = constrain (cs, 1, self.cols) 161 | ce = constrain (ce, 1, self.cols) 162 | if rs > re: 163 | rs, re = re, rs 164 | if cs > ce: 165 | cs, ce = ce, cs 166 | for r in range (rs, re+1): 167 | for c in range (cs, ce + 1): 168 | self.put_abs (r,c,ch) 169 | 170 | def cr (self): 171 | '''This moves the cursor to the beginning (col 1) of the current row. 172 | ''' 173 | 174 | self.cursor_home (self.cur_r, 1) 175 | 176 | def lf (self): 177 | '''This moves the cursor down with scrolling. 178 | ''' 179 | 180 | old_r = self.cur_r 181 | self.cursor_down() 182 | if old_r == self.cur_r: 183 | self.scroll_up () 184 | self.erase_line() 185 | 186 | def crlf (self): 187 | '''This advances the cursor with CRLF properties. 188 | The cursor will line wrap and the screen may scroll. 189 | ''' 190 | 191 | self.cr () 192 | self.lf () 193 | 194 | def newline (self): 195 | '''This is an alias for crlf(). 196 | ''' 197 | 198 | self.crlf() 199 | 200 | def put_abs (self, r, c, ch): 201 | '''Screen array starts at 1 index.''' 202 | 203 | r = constrain (r, 1, self.rows) 204 | c = constrain (c, 1, self.cols) 205 | if isinstance(ch, bytes): 206 | ch = self._decode(ch)[0] 207 | else: 208 | ch = ch[0] 209 | self.w[r-1][c-1] = ch 210 | 211 | def put (self, ch): 212 | '''This puts a characters at the current cursor position. 213 | ''' 214 | 215 | if isinstance(ch, bytes): 216 | ch = self._decode(ch) 217 | 218 | self.put_abs (self.cur_r, self.cur_c, ch) 219 | 220 | def insert_abs (self, r, c, ch): 221 | '''This inserts a character at (r,c). Everything under 222 | and to the right is shifted right one character. 223 | The last character of the line is lost. 224 | ''' 225 | 226 | if isinstance(ch, bytes): 227 | ch = self._decode(ch) 228 | 229 | r = constrain (r, 1, self.rows) 230 | c = constrain (c, 1, self.cols) 231 | for ci in range (self.cols, c, -1): 232 | self.put_abs (r,ci, self.get_abs(r,ci-1)) 233 | self.put_abs (r,c,ch) 234 | 235 | def insert (self, ch): 236 | 237 | if isinstance(ch, bytes): 238 | ch = self._decode(ch) 239 | 240 | self.insert_abs (self.cur_r, self.cur_c, ch) 241 | 242 | def get_abs (self, r, c): 243 | 244 | r = constrain (r, 1, self.rows) 245 | c = constrain (c, 1, self.cols) 246 | return self.w[r-1][c-1] 247 | 248 | def get (self): 249 | 250 | self.get_abs (self.cur_r, self.cur_c) 251 | 252 | def get_region (self, rs,cs, re,ce): 253 | '''This returns a list of lines representing the region. 254 | ''' 255 | 256 | rs = constrain (rs, 1, self.rows) 257 | re = constrain (re, 1, self.rows) 258 | cs = constrain (cs, 1, self.cols) 259 | ce = constrain (ce, 1, self.cols) 260 | if rs > re: 261 | rs, re = re, rs 262 | if cs > ce: 263 | cs, ce = ce, cs 264 | sc = [] 265 | for r in range (rs, re+1): 266 | line = u'' 267 | for c in range (cs, ce + 1): 268 | ch = self.get_abs (r,c) 269 | line = line + ch 270 | sc.append (line) 271 | return sc 272 | 273 | def cursor_constrain (self): 274 | '''This keeps the cursor within the screen area. 275 | ''' 276 | 277 | self.cur_r = constrain (self.cur_r, 1, self.rows) 278 | self.cur_c = constrain (self.cur_c, 1, self.cols) 279 | 280 | def cursor_home (self, r=1, c=1): # [{ROW};{COLUMN}H 281 | 282 | self.cur_r = r 283 | self.cur_c = c 284 | self.cursor_constrain () 285 | 286 | def cursor_back (self,count=1): # [{COUNT}D (not confused with down) 287 | 288 | self.cur_c = self.cur_c - count 289 | self.cursor_constrain () 290 | 291 | def cursor_down (self,count=1): # [{COUNT}B (not confused with back) 292 | 293 | self.cur_r = self.cur_r + count 294 | self.cursor_constrain () 295 | 296 | def cursor_forward (self,count=1): # [{COUNT}C 297 | 298 | self.cur_c = self.cur_c + count 299 | self.cursor_constrain () 300 | 301 | def cursor_up (self,count=1): # [{COUNT}A 302 | 303 | self.cur_r = self.cur_r - count 304 | self.cursor_constrain () 305 | 306 | def cursor_up_reverse (self): # M (called RI -- Reverse Index) 307 | 308 | old_r = self.cur_r 309 | self.cursor_up() 310 | if old_r == self.cur_r: 311 | self.scroll_up() 312 | 313 | def cursor_force_position (self, r, c): # [{ROW};{COLUMN}f 314 | '''Identical to Cursor Home.''' 315 | 316 | self.cursor_home (r, c) 317 | 318 | def cursor_save (self): # [s 319 | '''Save current cursor position.''' 320 | 321 | self.cursor_save_attrs() 322 | 323 | def cursor_unsave (self): # [u 324 | '''Restores cursor position after a Save Cursor.''' 325 | 326 | self.cursor_restore_attrs() 327 | 328 | def cursor_save_attrs (self): # 7 329 | '''Save current cursor position.''' 330 | 331 | self.cur_saved_r = self.cur_r 332 | self.cur_saved_c = self.cur_c 333 | 334 | def cursor_restore_attrs (self): # 8 335 | '''Restores cursor position after a Save Cursor.''' 336 | 337 | self.cursor_home (self.cur_saved_r, self.cur_saved_c) 338 | 339 | def scroll_constrain (self): 340 | '''This keeps the scroll region within the screen region.''' 341 | 342 | if self.scroll_row_start <= 0: 343 | self.scroll_row_start = 1 344 | if self.scroll_row_end > self.rows: 345 | self.scroll_row_end = self.rows 346 | 347 | def scroll_screen (self): # [r 348 | '''Enable scrolling for entire display.''' 349 | 350 | self.scroll_row_start = 1 351 | self.scroll_row_end = self.rows 352 | 353 | def scroll_screen_rows (self, rs, re): # [{start};{end}r 354 | '''Enable scrolling from row {start} to row {end}.''' 355 | 356 | self.scroll_row_start = rs 357 | self.scroll_row_end = re 358 | self.scroll_constrain() 359 | 360 | def scroll_down (self): # D 361 | '''Scroll display down one line.''' 362 | 363 | # Screen is indexed from 1, but arrays are indexed from 0. 364 | s = self.scroll_row_start - 1 365 | e = self.scroll_row_end - 1 366 | self.w[s+1:e+1] = copy.deepcopy(self.w[s:e]) 367 | 368 | def scroll_up (self): # M 369 | '''Scroll display up one line.''' 370 | 371 | # Screen is indexed from 1, but arrays are indexed from 0. 372 | s = self.scroll_row_start - 1 373 | e = self.scroll_row_end - 1 374 | self.w[s:e] = copy.deepcopy(self.w[s+1:e+1]) 375 | 376 | def erase_end_of_line (self): # [0K -or- [K 377 | '''Erases from the current cursor position to the end of the current 378 | line.''' 379 | 380 | self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols) 381 | 382 | def erase_start_of_line (self): # [1K 383 | '''Erases from the current cursor position to the start of the current 384 | line.''' 385 | 386 | self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c) 387 | 388 | def erase_line (self): # [2K 389 | '''Erases the entire current line.''' 390 | 391 | self.fill_region (self.cur_r, 1, self.cur_r, self.cols) 392 | 393 | def erase_down (self): # [0J -or- [J 394 | '''Erases the screen from the current line down to the bottom of the 395 | screen.''' 396 | 397 | self.erase_end_of_line () 398 | self.fill_region (self.cur_r + 1, 1, self.rows, self.cols) 399 | 400 | def erase_up (self): # [1J 401 | '''Erases the screen from the current line up to the top of the 402 | screen.''' 403 | 404 | self.erase_start_of_line () 405 | self.fill_region (self.cur_r-1, 1, 1, self.cols) 406 | 407 | def erase_screen (self): # [2J 408 | '''Erases the screen with the background color.''' 409 | 410 | self.fill () 411 | 412 | def set_tab (self): # H 413 | '''Sets a tab at the current position.''' 414 | 415 | pass 416 | 417 | def clear_tab (self): # [g 418 | '''Clears tab at the current position.''' 419 | 420 | pass 421 | 422 | def clear_all_tabs (self): # [3g 423 | '''Clears all tabs.''' 424 | 425 | pass 426 | 427 | # Insert line Esc [ Pn L 428 | # Delete line Esc [ Pn M 429 | # Delete character Esc [ Pn P 430 | # Scrolling region Esc [ Pn(top);Pn(bot) r 431 | 432 | -------------------------------------------------------------------------------- /deps/pexpect/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import stat 4 | import select 5 | import time 6 | import errno 7 | 8 | try: 9 | InterruptedError 10 | except NameError: 11 | # Alias Python2 exception to Python3 12 | InterruptedError = select.error 13 | 14 | if sys.version_info[0] >= 3: 15 | string_types = (str,) 16 | else: 17 | string_types = (unicode, str) 18 | 19 | 20 | def is_executable_file(path): 21 | """Checks that path is an executable regular file, or a symlink towards one. 22 | 23 | This is roughly ``os.path isfile(path) and os.access(path, os.X_OK)``. 24 | """ 25 | # follow symlinks, 26 | fpath = os.path.realpath(path) 27 | 28 | if not os.path.isfile(fpath): 29 | # non-files (directories, fifo, etc.) 30 | return False 31 | 32 | mode = os.stat(fpath).st_mode 33 | 34 | if (sys.platform.startswith('sunos') 35 | and os.getuid() == 0): 36 | # When root on Solaris, os.X_OK is True for *all* files, irregardless 37 | # of their executability -- instead, any permission bit of any user, 38 | # group, or other is fine enough. 39 | # 40 | # (This may be true for other "Unix98" OS's such as HP-UX and AIX) 41 | return bool(mode & (stat.S_IXUSR | 42 | stat.S_IXGRP | 43 | stat.S_IXOTH)) 44 | 45 | return os.access(fpath, os.X_OK) 46 | 47 | 48 | def which(filename, env=None): 49 | '''This takes a given filename; tries to find it in the environment path; 50 | then checks if it is executable. This returns the full path to the filename 51 | if found and executable. Otherwise this returns None.''' 52 | 53 | # Special case where filename contains an explicit path. 54 | if os.path.dirname(filename) != '' and is_executable_file(filename): 55 | return filename 56 | if env is None: 57 | env = os.environ 58 | p = env.get('PATH') 59 | if not p: 60 | p = os.defpath 61 | pathlist = p.split(os.pathsep) 62 | for path in pathlist: 63 | ff = os.path.join(path, filename) 64 | if is_executable_file(ff): 65 | return ff 66 | return None 67 | 68 | 69 | def split_command_line(command_line): 70 | 71 | '''This splits a command line into a list of arguments. It splits arguments 72 | on spaces, but handles embedded quotes, doublequotes, and escaped 73 | characters. It's impossible to do this with a regular expression, so I 74 | wrote a little state machine to parse the command line. ''' 75 | 76 | arg_list = [] 77 | arg = '' 78 | 79 | # Constants to name the states we can be in. 80 | state_basic = 0 81 | state_esc = 1 82 | state_singlequote = 2 83 | state_doublequote = 3 84 | # The state when consuming whitespace between commands. 85 | state_whitespace = 4 86 | state = state_basic 87 | 88 | for c in command_line: 89 | if state == state_basic or state == state_whitespace: 90 | if c == '\\': 91 | # Escape the next character 92 | state = state_esc 93 | elif c == r"'": 94 | # Handle single quote 95 | state = state_singlequote 96 | elif c == r'"': 97 | # Handle double quote 98 | state = state_doublequote 99 | elif c.isspace(): 100 | # Add arg to arg_list if we aren't in the middle of whitespace. 101 | if state == state_whitespace: 102 | # Do nothing. 103 | None 104 | else: 105 | arg_list.append(arg) 106 | arg = '' 107 | state = state_whitespace 108 | else: 109 | arg = arg + c 110 | state = state_basic 111 | elif state == state_esc: 112 | arg = arg + c 113 | state = state_basic 114 | elif state == state_singlequote: 115 | if c == r"'": 116 | state = state_basic 117 | else: 118 | arg = arg + c 119 | elif state == state_doublequote: 120 | if c == r'"': 121 | state = state_basic 122 | else: 123 | arg = arg + c 124 | 125 | if arg != '': 126 | arg_list.append(arg) 127 | return arg_list 128 | 129 | 130 | def select_ignore_interrupts(iwtd, owtd, ewtd, timeout=None): 131 | 132 | '''This is a wrapper around select.select() that ignores signals. If 133 | select.select raises a select.error exception and errno is an EINTR 134 | error then it is ignored. Mainly this is used to ignore sigwinch 135 | (terminal resize). ''' 136 | 137 | # if select() is interrupted by a signal (errno==EINTR) then 138 | # we loop back and enter the select() again. 139 | if timeout is not None: 140 | end_time = time.time() + timeout 141 | while True: 142 | try: 143 | return select.select(iwtd, owtd, ewtd, timeout) 144 | except InterruptedError: 145 | err = sys.exc_info()[1] 146 | if err.args[0] == errno.EINTR: 147 | # if we loop back we have to subtract the 148 | # amount of time we already waited. 149 | if timeout is not None: 150 | timeout = end_time - time.time() 151 | if timeout < 0: 152 | return([], [], []) 153 | else: 154 | # something else caused the select.error, so 155 | # this actually is an exception. 156 | raise 157 | 158 | 159 | def poll_ignore_interrupts(fds, timeout=None): 160 | '''Simple wrapper around poll to register file descriptors and 161 | ignore signals.''' 162 | 163 | if timeout is not None: 164 | end_time = time.time() + timeout 165 | 166 | poller = select.poll() 167 | for fd in fds: 168 | poller.register(fd, select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR) 169 | 170 | while True: 171 | try: 172 | timeout_ms = None if timeout is None else timeout * 1000 173 | results = poller.poll(timeout_ms) 174 | return [afd for afd, _ in results] 175 | except InterruptedError: 176 | err = sys.exc_info()[1] 177 | if err.args[0] == errno.EINTR: 178 | # if we loop back we have to subtract the 179 | # amount of time we already waited. 180 | if timeout is not None: 181 | timeout = end_time - time.time() 182 | if timeout < 0: 183 | return [] 184 | else: 185 | # something else caused the select.error, so 186 | # this actually is an exception. 187 | raise 188 | -------------------------------------------------------------------------------- /deps/ptyprocess/__init__.py: -------------------------------------------------------------------------------- 1 | """Run a subprocess in a pseudo terminal""" 2 | from .ptyprocess import PtyProcess, PtyProcessUnicode, PtyProcessError 3 | 4 | __version__ = '0.7.0' 5 | -------------------------------------------------------------------------------- /deps/ptyprocess/_fork_pty.py: -------------------------------------------------------------------------------- 1 | """Substitute for the forkpty system call, to support Solaris. 2 | """ 3 | import os 4 | import errno 5 | 6 | from pty import (STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, CHILD) 7 | from .util import PtyProcessError 8 | 9 | def fork_pty(): 10 | '''This implements a substitute for the forkpty system call. This 11 | should be more portable than the pty.fork() function. Specifically, 12 | this should work on Solaris. 13 | 14 | Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to 15 | resolve the issue with Python's pty.fork() not supporting Solaris, 16 | particularly ssh. Based on patch to posixmodule.c authored by Noah 17 | Spurrier:: 18 | 19 | http://mail.python.org/pipermail/python-dev/2003-May/035281.html 20 | 21 | ''' 22 | 23 | parent_fd, child_fd = os.openpty() 24 | if parent_fd < 0 or child_fd < 0: 25 | raise OSError("os.openpty() failed") 26 | 27 | pid = os.fork() 28 | if pid == CHILD: 29 | # Child. 30 | os.close(parent_fd) 31 | pty_make_controlling_tty(child_fd) 32 | 33 | os.dup2(child_fd, STDIN_FILENO) 34 | os.dup2(child_fd, STDOUT_FILENO) 35 | os.dup2(child_fd, STDERR_FILENO) 36 | 37 | else: 38 | # Parent. 39 | os.close(child_fd) 40 | 41 | return pid, parent_fd 42 | 43 | def pty_make_controlling_tty(tty_fd): 44 | '''This makes the pseudo-terminal the controlling tty. This should be 45 | more portable than the pty.fork() function. Specifically, this should 46 | work on Solaris. ''' 47 | 48 | child_name = os.ttyname(tty_fd) 49 | 50 | # Disconnect from controlling tty, if any. Raises OSError of ENXIO 51 | # if there was no controlling tty to begin with, such as when 52 | # executed by a cron(1) job. 53 | try: 54 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) 55 | os.close(fd) 56 | except OSError as err: 57 | if err.errno != errno.ENXIO: 58 | raise 59 | 60 | os.setsid() 61 | 62 | # Verify we are disconnected from controlling tty by attempting to open 63 | # it again. We expect that OSError of ENXIO should always be raised. 64 | try: 65 | fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY) 66 | os.close(fd) 67 | raise PtyProcessError("OSError of errno.ENXIO should be raised.") 68 | except OSError as err: 69 | if err.errno != errno.ENXIO: 70 | raise 71 | 72 | # Verify we can open child pty. 73 | fd = os.open(child_name, os.O_RDWR) 74 | os.close(fd) 75 | 76 | # Verify we now have a controlling tty. 77 | fd = os.open("/dev/tty", os.O_WRONLY) 78 | os.close(fd) 79 | -------------------------------------------------------------------------------- /deps/ptyprocess/util.py: -------------------------------------------------------------------------------- 1 | try: 2 | from shutil import which # Python >= 3.3 3 | except ImportError: 4 | import os, sys 5 | 6 | # This is copied from Python 3.4.1 7 | def which(cmd, mode=os.F_OK | os.X_OK, path=None): 8 | """Given a command, mode, and a PATH string, return the path which 9 | conforms to the given mode on the PATH, or None if there is no such 10 | file. 11 | 12 | `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result 13 | of os.environ.get("PATH"), or can be overridden with a custom search 14 | path. 15 | 16 | """ 17 | # Check that a given file can be accessed with the correct mode. 18 | # Additionally check that `file` is not a directory, as on Windows 19 | # directories pass the os.access check. 20 | def _access_check(fn, mode): 21 | return (os.path.exists(fn) and os.access(fn, mode) 22 | and not os.path.isdir(fn)) 23 | 24 | # If we're given a path with a directory part, look it up directly rather 25 | # than referring to PATH directories. This includes checking relative to the 26 | # current directory, e.g. ./script 27 | if os.path.dirname(cmd): 28 | if _access_check(cmd, mode): 29 | return cmd 30 | return None 31 | 32 | if path is None: 33 | path = os.environ.get("PATH", os.defpath) 34 | if not path: 35 | return None 36 | path = path.split(os.pathsep) 37 | 38 | if sys.platform == "win32": 39 | # The current directory takes precedence on Windows. 40 | if not os.curdir in path: 41 | path.insert(0, os.curdir) 42 | 43 | # PATHEXT is necessary to check on Windows. 44 | pathext = os.environ.get("PATHEXT", "").split(os.pathsep) 45 | # See if the given file matches any of the expected path extensions. 46 | # This will allow us to short circuit when given "python.exe". 47 | # If it does match, only test that one, otherwise we have to try 48 | # others. 49 | if any(cmd.lower().endswith(ext.lower()) for ext in pathext): 50 | files = [cmd] 51 | else: 52 | files = [cmd + ext for ext in pathext] 53 | else: 54 | # On other platforms you don't have things like PATHEXT to tell you 55 | # what file suffixes are executable, so just pass on cmd as-is. 56 | files = [cmd] 57 | 58 | seen = set() 59 | for dir in path: 60 | normdir = os.path.normcase(dir) 61 | if not normdir in seen: 62 | seen.add(normdir) 63 | for thefile in files: 64 | name = os.path.join(dir, thefile) 65 | if _access_check(name, mode): 66 | return name 67 | return None 68 | 69 | 70 | class PtyProcessError(Exception): 71 | """Generic error class for this package.""" 72 | -------------------------------------------------------------------------------- /manifest.json.in: -------------------------------------------------------------------------------- 1 | { 2 | "name": "waydroidhelper.aaronhafer", 3 | "description": "A collection of tools that help you to use Waydroid.", 4 | "architecture": "@CLICK_ARCH@", 5 | "title": "Waydroid Helper", 6 | "hooks": { 7 | "waydroidhelper": { 8 | "apparmor": "waydroidhelper.apparmor", 9 | "desktop": "waydroidhelper.desktop" 10 | } 11 | }, 12 | "version": "3.2.0", 13 | "maintainer": "Aaron Hafer ", 14 | "framework" : "@CLICK_FRAMEWORK@" 15 | } 16 | -------------------------------------------------------------------------------- /po/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FindGettext) 2 | find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext) 3 | 4 | set(DOMAIN ${FULL_PROJECT_NAME}) 5 | set(POT_FILE ${DOMAIN}.pot) 6 | file(GLOB PO_FILES *.po) 7 | 8 | # Creates the .pot file containing the translations template 9 | add_custom_target(${POT_FILE} ALL 10 | COMMENT "Generating translation template" 11 | COMMAND ${INTLTOOL_EXTRACT} --update --type=gettext/ini 12 | --srcdir=${CMAKE_SOURCE_DIR} ${DESKTOP_FILE_NAME}.in 13 | 14 | COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} -o ${POT_FILE} 15 | -D ${CMAKE_CURRENT_SOURCE_DIR} 16 | -D ${CMAKE_CURRENT_BINARY_DIR} 17 | --from-code=UTF-8 18 | --c++ --qt --language=javascript --add-comments=TRANSLATORS 19 | --keyword=tr --keyword=tr:1,2 --keyword=ctr:1c,2 --keyword=dctr:2c,3 --keyword=N_ --keyword=_ 20 | --keyword=dtr:2 --keyword=dtr:2,3 --keyword=tag --keyword=tag:1c,2 21 | --package-name='${DOMAIN}' 22 | --sort-by-file 23 | ${I18N_SRC_FILES} 24 | COMMAND ${CMAKE_COMMAND} -E copy ${POT_FILE} ${CMAKE_CURRENT_SOURCE_DIR}) 25 | 26 | # Builds the binary translations catalog for each language 27 | # it finds source translations (*.po) for 28 | foreach(PO_FILE ${PO_FILES}) 29 | get_filename_component(LANG ${PO_FILE} NAME_WE) 30 | gettext_process_po_files(${LANG} ALL PO_FILES ${PO_FILE}) 31 | set(INSTALL_DIR ${CMAKE_INSTALL_LOCALEDIR}/share/locale/${LANG}/LC_MESSAGES) 32 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${LANG}.gmo 33 | DESTINATION ${INSTALL_DIR} 34 | RENAME ${DOMAIN}.mo) 35 | endforeach(PO_FILE) 36 | -------------------------------------------------------------------------------- /po/de.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "MIME-Version: 1.0\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "X-Generator: POEditor.com\n" 7 | "Project-Id-Version: WaydroidHelper\n" 8 | "Language: de\n" 9 | 10 | #: ../qml/About.qml:8 ../qml/Main.qml:49 11 | msgid "About" 12 | msgstr "Über" 13 | 14 | #: ../qml/About.qml:32 15 | msgid "WayDroid Helper" 16 | msgstr "WayDroid Helper" 17 | 18 | #: ../qml/About.qml:50 19 | msgid "Version: " 20 | msgstr "Version: " 21 | 22 | #: ../qml/About.qml:58 23 | msgid "A Tweak tool application for WayDroid on Ubuntu Touch." 24 | msgstr "Ein Optimierungswerkzeug für WayDroid auf Ubuntu Touch" 25 | 26 | #. TRANSLATORS: Please make sure the URLs are correct 27 | #: ../qml/About.qml:67 28 | msgid "This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details." 29 | msgstr "Diese Anwendung ist als freie Software herausgegeben: Jeder kann sie selbst veröffentlichen und dabei Veränderungen vornehmen, solang er sich an die Richtlinien der GNU General Public License hält. Diese Richtlinien werden durch die Free Software Stiftung herausgegeben und müssen entwededer Version 3 oder einer (aktuelleren) höheren Version entsprechen. Diese Anwendungen wird veröffentlicht in der Hoffnung, dass sie nützlich ist. Jedoch wird KEINE GARANTIE übernommen - es wird auch nicht garantiert dass diese Anwendung MARKTREIF oder ANGEPASST AUF EINEN ZWECK ist. Weitere Infos hier unter GNU General Public License" 30 | 31 | #: ../qml/About.qml:76 32 | msgid "DONATE" 33 | msgstr "SPENDEN" 34 | 35 | #: ../qml/About.qml:76 36 | msgid "ISSUES" 37 | msgstr "PROBLEME" 38 | 39 | #: ../qml/About.qml:76 40 | msgid "SOURCE" 41 | msgstr "QUELLCODE" 42 | 43 | #: ../qml/About.qml:86 44 | msgid "Copyright" 45 | msgstr "Copyright" 46 | 47 | #: ../qml/About.qml:95 48 | msgid "General contributions: " 49 | msgstr "Allgemeine Teilnahme" 50 | 51 | #: ../qml/Help.qml:13 ../qml/Main.qml:55 52 | msgid "Help" 53 | msgstr "Hilfe" 54 | 55 | #: ../qml/Installer.qml:13 56 | msgid "Install Waydroid" 57 | msgstr "Waydroid installieren" 58 | 59 | #: ../qml/Installer.qml:19 60 | msgid "GAPPS" 61 | msgstr "GAPPS" 62 | 63 | #: ../qml/Installer.qml:48 64 | msgid "By pressing 'start' the installation will, well... start. The installer will let you know what it is doing at the moment. Be patient! The installation may take a while. Do not close the app during the installation! Once Waydroid is installed, your device will restart automatically. I reccomend to disable screen suspension in the settings to keep the screen always on without touching it." 65 | msgstr "Durch Klick auf \"start\" wird die Installation, nunja... starten. Der Installer wird dich wissen lassen was er im Moment tut. Sei Geduldig! Die Installation wird etwas Zeit in Anspruch nehmen. Schließ auf keinen Fall die App während der Installation! Nach der Installation wird das Gerät neustarten. Ich empfehle das automatische Abschalten des Bildschirms in den Systemeinstellungen zu deaktivieren." 66 | 67 | #: ../qml/Installer.qml:58 ../qml/Uninstaller.qml:45 68 | msgid "Start" 69 | msgstr "Start" 70 | 71 | #: ../qml/Installer.qml:73 ../qml/Uninstaller.qml:60 72 | msgid "Running" 73 | msgstr "Läuft" 74 | 75 | #: ../qml/Installer.qml:85 76 | msgid "You are about to use an experimental Waydroid installer!
Supported devices (✔️ = tested):
* VollaPhone ✔️ (X)
* Redmi Note 7 (Pro)
* Redmi Note 9 (Pro/Pro Max/S)
* Fairphone 3(+)
* Pixel 3a
There is absolutely no warranty for this to work! Do not use this installer if you dont want to risk to brick your device (,which is highly unlikely though)!" 77 | msgstr "Dieser Installer ist noch experimentell!
Unterstützte Geräte (✔️ = getestet):
* VollaPhone ✔️ (X)
* Redmi Note 7 (Pro)
* Redmi Note 9 (Pro/Pro Max/S)
* Fairphone 3(+)
* Pixel 3a
Es gibt keine Garantie, dass die Installation funktioniert! Verwende den Installer nicht, wenn du dein Gerät nicht gefährden willst (auch wenn das sehr unwahrscheinlich ist)!" 78 | 79 | #: ../qml/Installer.qml:90 80 | msgid "I understand the risk" 81 | msgstr "Ich verstehe das Risiko" 82 | 83 | #: ../qml/Installer.qml:95 ../qml/Installer.qml:162 84 | msgid "Cancel" 85 | msgstr "Abbrechen" 86 | 87 | #: ../qml/Installer.qml:114 ../qml/Uninstaller.qml:71 88 | msgid "Enter your password:" 89 | msgstr "Passwort eingeben:" 90 | 91 | #: ../qml/Installer.qml:124 ../qml/Main.qml:160 ../qml/Main.qml:344 92 | #: ../qml/Uninstaller.qml:81 93 | msgid "Ok" 94 | msgstr "Ok" 95 | 96 | #: ../qml/Installer.qml:146 97 | msgid "You can install a special version of Waydroid that comes with google apps. (I personally do not recommend this as it will result in worse privacy.)" 98 | msgstr "Du kannst eine spezielle WayDroid Version installieren, die Google Apps unterstützt (Ich kann das nicht empfehlen, da es die Privatsphäre negativ beeinflusst)" 99 | 100 | #: ../qml/Installer.qml:151 101 | msgid "Enable GAPPS" 102 | msgstr "GAPPS aktivieren" 103 | 104 | #: ../qml/Main.qml:44 waydroidhelper.desktop.in.h:1 105 | msgid "Waydroid Helper" 106 | msgstr "Waydroid Helper" 107 | 108 | #: ../qml/Main.qml:70 109 | msgid "Install Waydroid 📲>" 110 | msgstr "Waydroid Installieren 📲>" 111 | 112 | #: ../qml/Main.qml:83 113 | msgid "Show/Hide apps 🙈>" 114 | msgstr "Apps zeigen/verstecken 🙈>" 115 | 116 | #: ../qml/Main.qml:97 117 | msgid "Waydroid Stop app 🛑>" 118 | msgstr "Waydroid Stop App 🛑>" 119 | 120 | #: ../qml/Main.qml:110 121 | msgid "Waydroid Help ❓>" 122 | msgstr "Waydroid Help ❓>" 123 | 124 | #: ../qml/Main.qml:124 125 | msgid "Uninstall Waydroid 🗑>" 126 | msgstr "Waydroid deinstallieren 🗑>" 127 | 128 | #: ../qml/Main.qml:146 129 | msgid "Show/Hide" 130 | msgstr "Zeigen/Verstecken" 131 | 132 | #: ../qml/Main.qml:154 133 | msgid "Swipe on the listed apps to either hide them(bin) or show them(plus) in the Appdrawer. This will NOT install or uninstall the selected app. Reload the Appdrawer for the changes to take effect." 134 | msgstr "Wische die Einträge in der Liste um sie in der Appübersicht zu verstecken (Mülleimer) oder sie anzuzeigen (plus) Dies wird die Apps NICHT installieren oder deinstallieren. Aktualisiere die Appliste um die Änderungen zu übernehmen." 135 | 136 | #: ../qml/Main.qml:229 137 | msgid "Waydroid Stop" 138 | msgstr "Waydroid Stop" 139 | 140 | #: ../qml/Main.qml:237 141 | msgid "The 'Waydroid Stop app' allows you to easily stop Waydroid without entering the terminal. Once you press 'Add' a new icon will appear in your appdrawer. Pressing this icon will stop Waydroid if it has crashed or anything else went wrong. If you open any of your Android apps, waydroid will start automaticly again." 142 | msgstr "Mit der \"Waydroid Stop App\" kannst Du Waydroid ausschalten ohne das Terminal öffnen zu müssen. Sobald die App über \"hinzufügen\" installiert wurde, wird ein neues Symbol in der Appliste erscheinen. Wenn dieses Symbol gedrückt wird, wird Waydroid gestoppt auch wenn es gecrasht ist. Waydroid startet automatisch wieder, sobald eine Android App gestartet wird." 143 | 144 | #: ../qml/Main.qml:258 145 | msgid "Remove" 146 | msgstr "Entfernen" 147 | 148 | #: ../qml/Main.qml:271 149 | msgid "Add" 150 | msgstr "Hinzufügen" 151 | 152 | #: ../qml/Main.qml:287 153 | msgid "Waydroid Help" 154 | msgstr "Waydroid Help" 155 | 156 | #: ../qml/Main.qml:295 157 | msgid "You need to run these commands in the terminal app. Tap a command to copy it to the clipboard.

" 158 | msgstr "Diese Befehle müssen im Terminal ausgeführt werden. Die Befehle werden in die Zwischenablage kopiert, indem sie angetippt werden.

" 159 | 160 | #: ../qml/Main.qml:315 161 | msgid "waydroid -h, --help show this help message and exit

waydroid -V, --version show program's version number and exit

waydroid -l LOG, --log LOG path to log file

waydroid --details-to-stdout print details (e.g. build output) to stdout, instead of writing to the log

waydroid -v, --verbose write even more to the logfiles (this may reduce performance)

waydroid -q, --quiet do not output any log messages" 162 | msgstr "waydroid -h, --help Zeigt diese Hilfe an und beendet das Programm.

waydroid -V, --version Zeigt die Versionsnummer an und beendet das Programm

waydroid -l LOG, --log LOG Verzeichnisspfad zur Logfile

waydroid --details-to-stdout Details werden direkt ausgegeben und nicht im Log gespeichert

waydroid -v, --verbose Noch detailiertere Logfiles (Die Performance wird dadurch eventuell verschlechtert)

waydroid -q, --quiet Keine Logs werden geschrieben" 163 | 164 | #: ../qml/Main.qml:339 165 | msgid "You copied a command to your clipboard. You can now paste and use it in the terminal app." 166 | msgstr "Du hast einen Befehl in die Zwischenablage gespeichert. Er kann nun in die Terminal-App eingefügt werden." 167 | 168 | #: ../qml/Uninstaller.qml:14 169 | msgid "Uninstall Waydroid" 170 | msgstr "Waydroid deinstallieren" 171 | 172 | #: ../qml/Uninstaller.qml:35 173 | msgid "Press 'start' to uninstall WayDroid." 174 | msgstr "Drücke \"Start\" um WayDroid zu deinstallieren" 175 | 176 | -------------------------------------------------------------------------------- /po/fr.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: WaydroidHelper\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2022-02-04 12:11+0000\n" 6 | "PO-Revision-Date: \n" 7 | "Last-Translator: Anne017\n" 8 | "Language-Team: \n" 9 | "Language: fr\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "X-Generator: Poedit 3.0\n" 14 | 15 | #: ../qml/About.qml:8 ../qml/Main.qml:49 16 | msgid "About" 17 | msgstr "À propos" 18 | 19 | #: ../qml/About.qml:32 20 | msgid "WayDroid Helper" 21 | msgstr "WayDroid Helper" 22 | 23 | #: ../qml/About.qml:50 24 | msgid "Version: " 25 | msgstr "Version :" 26 | 27 | #: ../qml/About.qml:58 28 | msgid "A Tweak tool application for WayDroid on Ubuntu Touch." 29 | msgstr "Un outil de personnalisation pour WayDroid sur Ubuntu Touch." 30 | 31 | #. TRANSLATORS: Please make sure the URLs are correct 32 | #: ../qml/About.qml:67 33 | msgid "" 34 | "This program is free software: you can redistribute it and/or modify it under " 35 | "the terms of the GNU General Public License as published by the Free Software " 36 | "Foundation, either version 3 of the License, or (at your option) any later " 37 | "version. This program is distributed in the hope that it will be useful, but " 38 | "WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or " 39 | "FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details." 41 | msgstr "" 42 | "Ce programme est un logiciel libre : vous pouvez le redistribuer et/ou le " 43 | "modifier selon les termes de la licence publique générale GNU telle que " 44 | "publiée par la Free Software Foundation, soit la version 3 de la licence, soit " 45 | "(à votre choix) toute version ultérieure. Ce programme est distribué dans " 46 | "l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE ; sans même la garantie " 47 | "implicite de QUALITÉ MARCHANDE ou D'ADAPTATION À UN USAGE PARTICULIER. Voir la " 48 | "Licence publique " 49 | "générale GNU pour plus de détails." 50 | 51 | #: ../qml/About.qml:76 52 | msgid "DONATE" 53 | msgstr "SOUTENIR FINANCIÈREMENT" 54 | 55 | #: ../qml/About.qml:76 56 | msgid "ISSUES" 57 | msgstr "PROBLÈMES" 58 | 59 | #: ../qml/About.qml:76 60 | msgid "SOURCE" 61 | msgstr "SOURCE" 62 | 63 | #: ../qml/About.qml:86 64 | msgid "Copyright" 65 | msgstr "Droits d'auteur" 66 | 67 | #: ../qml/About.qml:95 68 | msgid "General contributions: " 69 | msgstr "Contributions générales" 70 | 71 | #: ../qml/Help.qml:13 ../qml/Main.qml:55 72 | msgid "Help" 73 | msgstr "Aide" 74 | 75 | #: ../qml/Installer.qml:13 76 | msgid "Install Waydroid" 77 | msgstr "Installer WayDroid" 78 | 79 | #: ../qml/Installer.qml:19 80 | msgid "GAPPS" 81 | msgstr "GAPPS (Google Apps)" 82 | 83 | #: ../qml/Installer.qml:48 84 | msgid "" 85 | "By pressing 'start' the installation will, well... start. The installer will " 86 | "let you know what it is doing at the moment. Be patient! The installation may " 87 | "take a while. Do not close the app during the installation! Once Waydroid is " 88 | "installed, your device will restart automatically. I reccomend to disable " 89 | "screen suspension in the settings to keep the screen always on without " 90 | "touching it." 91 | msgstr "" 92 | "En appuyant sur « démarrer » l'installation va débuter. L'installateur vous " 93 | "indiquera l'avancement des opérations. Soyez patient! L'installation peut " 94 | "prendre un certain temps. Ne fermez pas l'application pendant l'installation ! " 95 | "Une fois WayDroid installé, votre appareil redémarrera automatiquement. Il est " 96 | "recommandé de désactiver la suspension de l'écran dans les paramètres pour " 97 | "conserver l'écran toujours allumé sans le toucher. " 98 | 99 | #: ../qml/Installer.qml:58 ../qml/Uninstaller.qml:45 100 | msgid "Start" 101 | msgstr "Démarrer" 102 | 103 | #: ../qml/Installer.qml:73 ../qml/Uninstaller.qml:60 104 | msgid "Running" 105 | msgstr "En cours" 106 | 107 | #: ../qml/Installer.qml:85 108 | msgid "" 109 | "You are about to use an experimental Waydroid installer!
Supported " 110 | "devices (✔️ = tested):
* VollaPhone ✔️ (X)
* Redmi Note 7 (Pro)
* " 111 | "Redmi Note (9 Pro/9 Pro Max)
* Fairphone 3(+)
* Pixel (3a/XL/2 XL) " 112 | "
* MI A2 (wifi does not work with GAPPS)
* Poco F1
* SHIFT6mq " 113 | "
These devices have partial support:
* Pixel 3a XL (no video " 114 | "playback)
* Pinephone (unstable)
* Redmi Note 9 (no external " 115 | "storage)
There is absolutely no warranty for this to work! Do not use " 116 | "this installer if you dont want to risk to brick your device (,which is highly " 117 | "unlikely though)!" 118 | msgstr "" 119 | "Vous êtes sur le point d'utiliser un programme d'installation expérimental de " 120 | "WayDroid !
Appareils pris en charge (✔️ = testés) :
* VollaPhone ✔️ " 121 | "(X)
* Redmi Note 7 (Pro)
* Redmi Note 9 (Pro/Pro Max/S)
* " 122 | "Fairphone 3(+)
** Pixel (3a/XL/2 XL)
* MI A2 (le Wi-Fi ne marche pas " 123 | "avec les GAPPS)
* Poco F1
* SHIFT6mq
Ces appareils ont une " 124 | "prise en charge partielle :
* Pixel 3a XL (pas de lecture vidéo)
* " 125 | "Pinephone (instable)
* Redmi Note 9 (pas de stockage externe)
Il n'y " 126 | "a absolument aucune garantie que cela fonctionne ! N'utilisez pas ce programme " 127 | "d'installation si vous ne voulez pas risquer de détériorer le fonctionnement " 128 | "de votre appareil (ce qui est cependant hautement improbable) ! " 129 | 130 | #: ../qml/Installer.qml:90 131 | msgid "I understand the risk" 132 | msgstr "Je comprends les risques" 133 | 134 | #: ../qml/Installer.qml:95 ../qml/Installer.qml:162 135 | msgid "Cancel" 136 | msgstr "Annuler" 137 | 138 | #: ../qml/Installer.qml:114 ../qml/Uninstaller.qml:71 139 | msgid "Enter your password:" 140 | msgstr "Entrez votre mot de passe :" 141 | 142 | #: ../qml/Installer.qml:124 ../qml/Main.qml:160 ../qml/Main.qml:344 143 | #: ../qml/Uninstaller.qml:81 144 | msgid "Ok" 145 | msgstr "Valider" 146 | 147 | #: ../qml/Installer.qml:146 148 | msgid "" 149 | "You can install a special version of Waydroid that comes with google apps. (I " 150 | "personally do not recommend this as it will result in worse privacy.)" 151 | msgstr "" 152 | "Vous pouvez installer une version spécifique de WayDroid incluant les Google " 153 | "Apps. (Je déconseille toutefois cette version pour des raisons de pertes de " 154 | "confidentialité)." 155 | 156 | #: ../qml/Installer.qml:151 157 | msgid "Enable GAPPS" 158 | msgstr "Activer GAPPS (Google Apps)" 159 | 160 | #: ../qml/Main.qml:44 waydroidhelper.desktop.in.h:1 161 | msgid "Waydroid Helper" 162 | msgstr "WayDroid Helper" 163 | 164 | #: ../qml/Main.qml:70 165 | msgid "Install Waydroid 📲>" 166 | msgstr "Installer WayDroid 📲>" 167 | 168 | #: ../qml/Main.qml:83 169 | msgid "Show/Hide apps 🙈>" 170 | msgstr "Afficher/Masquer les applications 🙈>" 171 | 172 | #: ../qml/Main.qml:97 173 | msgid "Waydroid Stop app 🛑>" 174 | msgstr "Arrêter WayDroid 🛑>" 175 | 176 | #: ../qml/Main.qml:110 177 | msgid "Waydroid Help ❓>" 178 | msgstr "Aide de WayDroid ❓>" 179 | 180 | #: ../qml/Main.qml:124 181 | msgid "Uninstall Waydroid 🗑>" 182 | msgstr "Désinstaller WayDroid 🗑>" 183 | 184 | #: ../qml/Main.qml:146 185 | msgid "Show/Hide" 186 | msgstr "Afficher/Masquer" 187 | 188 | #: ../qml/Main.qml:154 189 | msgid "" 190 | "Swipe on the listed apps to either hide them(bin) or show them(plus) in the " 191 | "Appdrawer. This will NOT install or uninstall the selected app. Reload the " 192 | "Appdrawer for the changes to take effect." 193 | msgstr "" 194 | "Balayez vers la droite les applications répertoriées pour les masquer " 195 | "(poubelle) ou vers la gauche pour les afficher (signe +) dans le lanceur " 196 | "d'applications. Cela n'installera ou ne désinstallera PAS l'application " 197 | "sélectionnée. Rechargez le lanceur d'applications pour que les modifications " 198 | "prennent effet. (Balayez vers le bas)" 199 | 200 | #: ../qml/Main.qml:229 201 | msgid "Waydroid Stop" 202 | msgstr "Arrêter WayDroid" 203 | 204 | #: ../qml/Main.qml:237 205 | msgid "" 206 | "The 'Waydroid Stop app' allows you to easily stop Waydroid without entering " 207 | "the terminal. Once you press 'Add' a new icon will appear in your appdrawer. " 208 | "Pressing this icon will stop Waydroid if it has crashed or anything else went " 209 | "wrong. If you open any of your Android apps, waydroid will start automaticly " 210 | "again." 211 | msgstr "" 212 | "La fonction 'Arrêter WayDroid' vous permet d'arrêter WayDroid sans avoir " 213 | "besoin de vous rendre dans le terminal. Lorsque vous appuyez sur 'ajouter', " 214 | "une nouvelle icône va apparaître dans le lanceur. Appuyez sur cette icône pour " 215 | "arrêter WayDroid en cas de crash ou tout autre dysfonctionnement. En ouvrant " 216 | "n'importe quelle application Android, WayDroid va automatiquement redémarrer." 217 | 218 | #: ../qml/Main.qml:258 219 | msgid "Remove" 220 | msgstr "Enlever" 221 | 222 | #: ../qml/Main.qml:271 223 | msgid "Add" 224 | msgstr "Ajouter" 225 | 226 | #: ../qml/Main.qml:287 227 | msgid "Waydroid Help" 228 | msgstr "Aide de WayDroid" 229 | 230 | #: ../qml/Main.qml:295 231 | msgid "" 232 | "You need to run these commands in the terminal app. Tap a command to copy " 233 | "it to the clipboard.

" 234 | msgstr "" 235 | "Vous devez exécuter ces commandes dans l'application 'terminal'. Appuyez " 236 | "sur une commande pour la copier dans le presse-papiers.

" 237 | 238 | #: ../qml/Main.qml:315 239 | msgid "" 240 | "waydroid -h, --help show this help message and exit " 241 | "

waydroid -V, --version show program's " 242 | "version number and exit

waydroid -l LOG, " 243 | "--log LOG path to log file

waydroid --details-to-stdout print details (e.g. build output) to " 245 | "stdout, instead of writing to the log

waydroid -" 246 | "v, --verbose write even more to the logfiles (this may reduce performance) " 247 | "

waydroid -q, --quiet do not output any log " 248 | "messages" 249 | msgstr "" 250 | "waydroid -h, --help affiche ce message d'aide et " 251 | "quitte

waydroid -V , --version affiche le " 252 | "numéro de version du programme et quitte

waydroid -l LOG, --log LOG chemin vers le fichier journal

waydroid --details-to-stdout imprime " 255 | "les détails (par exemple, la sortie de construction) sur stdout, au lieu " 256 | "d'écrire dans le journal

< a href='waydroid -v'>waydroid -v, --" 257 | "verbose écrit encore plus dans les fichiers journaux (cela peut réduire les " 258 | "performances)

waydroid - q, --quiet " 259 | "n'affiche aucun message de journal " 260 | 261 | #: ../qml/Main.qml:339 262 | msgid "" 263 | "You copied a command to your clipboard. You can now paste and use it in the " 264 | "terminal app." 265 | msgstr "" 266 | "Vous avez copié une commande dans votre presse-papiers. Vous pouvez maintenant " 267 | "la coller et l'utiliser dans l'application 'terminal'. " 268 | 269 | #: ../qml/Uninstaller.qml:14 270 | msgid "Uninstall Waydroid" 271 | msgstr "Désinstaller WayDroid" 272 | 273 | #: ../qml/Uninstaller.qml:35 274 | msgid "Press 'start' to uninstall WayDroid." 275 | msgstr "Appuyer sur « démarrer » pour désinstaller WayDroid." 276 | -------------------------------------------------------------------------------- /po/nl.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: WaydroidHelper\n" 4 | "Report-Msgid-Bugs-To: \n" 5 | "POT-Creation-Date: 2024-03-27 23:26+0000\n" 6 | "PO-Revision-Date: \n" 7 | "Last-Translator: Heimen Stoffels \n" 8 | "Language-Team: \n" 9 | "Language: nl\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "X-Generator: Poedit 3.4.2\n" 14 | 15 | #: ../qml/About.qml:8 ../qml/Main.qml:97 16 | msgid "About" 17 | msgstr "Over" 18 | 19 | #: ../qml/About.qml:32 20 | msgid "WayDroid Helper" 21 | msgstr "WayDroid-hulpapp" 22 | 23 | #: ../qml/About.qml:50 24 | msgid "Version: " 25 | msgstr "Versie: " 26 | 27 | #: ../qml/About.qml:58 28 | msgid "A Tweak tool application for WayDroid on Ubuntu Touch." 29 | msgstr "Een app voor het afstellen van Waydroid op Ubuntu Touch." 30 | 31 | #. TRANSLATORS: Please make sure the URLs are correct 32 | #: ../qml/About.qml:67 33 | msgid "" 34 | "This program is free software: you can redistribute it and/or modify it " 35 | "under the terms of the GNU General Public License as published by the Free " 36 | "Software Foundation, either version 3 of the License, or (at your option) " 37 | "any later version. This program is distributed in the hope that it will be " 38 | "useful, but WITHOUT ANY WARRANTY; without even the implied warranty of " 39 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public " 41 | "License for more details." 42 | msgstr "" 43 | "Dit programma is vrije software: je mag het heruitgeven en/of wijzigen onder " 44 | "de voorwaarden van de GNU Algemene Publieke Licentie zoals gepubliceerd door " 45 | "de Free Software Foundation, onder versie 3 van de licentie of (naar eigen " 46 | "inzicht) elke latere versie. Dit programma is gedistribueerd in de hoop dat " 47 | "het nuttig zal zijn maar ZONDER ENIGE GARANTIE; zelfs zonder de impliciete " 48 | "garanties die GEBRUIKELIJK ZIJN IN DE HANDEL of voor BRUIKBAARHEID VOOR EEN " 49 | "SPECIFIEK DOEL. Zie de GNU Algemene Publieke Licentie voor meer details." 51 | 52 | #: ../qml/About.qml:76 53 | msgid "DONATE" 54 | msgstr "DONEREN" 55 | 56 | #: ../qml/About.qml:76 57 | msgid "ISSUES" 58 | msgstr "PROBLEMEN" 59 | 60 | #: ../qml/About.qml:76 61 | msgid "SOURCE" 62 | msgstr "BRONCODE" 63 | 64 | #: ../qml/About.qml:86 65 | msgid "Copyright" 66 | msgstr "Copyright" 67 | 68 | #: ../qml/About.qml:95 69 | msgid "General contributions: " 70 | msgstr "Algemene bijdragen: " 71 | 72 | #: ../qml/Installer.qml:12 73 | msgid "Install Waydroid" 74 | msgstr "Waydroid installeren" 75 | 76 | #: ../qml/Installer.qml:19 77 | msgid "GAPPS" 78 | msgstr "GAPPS" 79 | 80 | #: ../qml/Installer.qml:34 81 | msgid "" 82 | "By pressing 'start' the installation will, well... start. The installer will " 83 | "let you know what it is currently doing. The installation might take a " 84 | "while. You can safely use other apps or turn off the screen, but don't close " 85 | "this one." 86 | msgstr "" 87 | "Druk op ‘Starten’ om de installatie - je raadt het al - te starten. De " 88 | "installatiewizard toont de installatiestatus. Maar heb geduld, want het kan " 89 | "even duren. Sluit de app niet af zolang de installatie nog loopt!" 90 | 91 | #: ../qml/Installer.qml:35 92 | msgid "Installation starting" 93 | msgstr "Bezig met starten…" 94 | 95 | #: ../qml/Installer.qml:36 96 | msgid "Preparing to download system image (with GAPPS)" 97 | msgstr "Bezig met voorbereiden van schijfkopie (met GAPPS)…" 98 | 99 | #: ../qml/Installer.qml:37 100 | msgid "Preparing to download system image" 101 | msgstr "Bezig met voorbereiden van schijfkopie…" 102 | 103 | #: ../qml/Installer.qml:38 104 | msgid "Downloading system image (with GAPPS)" 105 | msgstr "Bezig met downloaden van schijfkopie (met GAPPS)…" 106 | 107 | #: ../qml/Installer.qml:39 108 | msgid "Downloading system image" 109 | msgstr "Bezig met downloaden van schijfkopie…" 110 | 111 | #: ../qml/Installer.qml:40 112 | msgid "Downloading vendor image" 113 | msgstr "Bezig met downloaden van fabrikant-schijfkopie…" 114 | 115 | #: ../qml/Installer.qml:41 116 | msgid "Validating system image" 117 | msgstr "Bezig met valideren van schijfkopie…" 118 | 119 | #: ../qml/Installer.qml:42 120 | msgid "Validating vendor image" 121 | msgstr "Bezig met valideren van fabrikant-schijfkopie…" 122 | 123 | #: ../qml/Installer.qml:43 124 | msgid "Extracting system image" 125 | msgstr "Bezig met extraheren van schijfkopie…" 126 | 127 | #: ../qml/Installer.qml:44 128 | msgid "Extracting vendor image" 129 | msgstr "Bezig met extraheren van fabrikant-schijfkopie…" 130 | 131 | #: ../qml/Installer.qml:45 132 | msgid "Installation complete!" 133 | msgstr "De installatie is afgerond!" 134 | 135 | #: ../qml/Installer.qml:143 ../qml/Main.qml:208 ../qml/Main.qml:414 136 | #: ../qml/PasswordPrompt.qml:58 ../qml/Uninstaller.qml:125 137 | msgid "Ok" 138 | msgstr "Oké" 139 | 140 | #: ../qml/Installer.qml:143 ../qml/Uninstaller.qml:125 141 | msgid "Start" 142 | msgstr "Starten" 143 | 144 | #: ../qml/Installer.qml:165 145 | msgid "" 146 | "

There is absolutely no warranty for this to work! Do not use this " 147 | "installer if you dont want to risk to brick your device permenantly (,which " 148 | "is highly unlikely though)!" 149 | msgstr "" 150 | "

Er is geen garantie dat Waydroid werkt! Gebruik de " 151 | "installatiewizard niet als je geen risico wilt lopen om je apparaat te " 152 | "permanent blokkeren (kleine kans, maar toch)!" 153 | 154 | #: ../qml/Installer.qml:165 155 | msgid "" 156 | "You are about to use an experimental Waydroid installer!
You can check " 157 | "if your device is supported on https://devices.ubuntu-touch.io" 159 | msgstr "" 160 | "Je staat op het punt gebruik te maken van de experimentele Waydroid-" 161 | "installatiewizard!
Je kunt op https://devices.ubuntu-touch.io zien of je apparaat ondersteund " 163 | "wordt." 164 | 165 | #: ../qml/Installer.qml:171 166 | msgid "I understand the risk" 167 | msgstr "Ik begrijp het risico" 168 | 169 | #: ../qml/Installer.qml:177 ../qml/Installer.qml:210 ../qml/Installer.qml:253 170 | #: ../qml/PasswordPrompt.qml:78 171 | msgid "Cancel" 172 | msgstr "Annuleren" 173 | 174 | #: ../qml/Installer.qml:194 175 | msgid "" 176 | "Waydroid doesn't officially support Android versions past 11 (yet). Your " 177 | "device can still run unofficial Android 13 images (with some additional " 178 | "bugs) from https://sourceforge.net/projects/aleasto-" 180 | "lineageos/files/LineageOS 20/waydroid_arm64 which Waydroid Helper can " 181 | "setup (excluding GAPPS).

Do you want to continue?" 182 | msgstr "" 183 | "Waydroid heeft nog geen ondersteuning voor Android-versies hoger dan 11. Je " 184 | "apparaat kan gebruikmaken van Android 13 (met enkele bugs) door middel van " 185 | "https://sourceforge.net/projects/aleasto-lineageos/files/" 187 | "LineageOS 20/waydroid_arm64, welke deze app voor je kan instellen (maar " 188 | "zonder GAPPS).

Wil je doorgaan?" 189 | 190 | #: ../qml/Installer.qml:200 191 | msgid "Yes" 192 | msgstr "Ja" 193 | 194 | #: ../qml/Installer.qml:239 195 | msgid "" 196 | "You can install a special version of Waydroid that comes with google apps. " 197 | "(I personally do not recommend this as it will result in worse privacy.)" 198 | msgstr "" 199 | "Je kunt een speciale versie installeren die standaard de Google-apps aan " 200 | "boord heeft. (niet aanbevolen in verband met Googles privacyproblematiek)" 201 | 202 | #: ../qml/Installer.qml:244 203 | msgid "Enable GAPPS" 204 | msgstr "Google-apps installeren" 205 | 206 | #: ../qml/Main.qml:92 waydroidhelper.desktop.in.h:1 207 | msgid "Waydroid Helper" 208 | msgstr "Waydroid-hulpapp" 209 | 210 | #: ../qml/Main.qml:103 211 | msgid "Help" 212 | msgstr "Hulp" 213 | 214 | #: ../qml/Main.qml:121 215 | msgid "Install Waydroid 📲>" 216 | msgstr "Waydroid installeren 📲>" 217 | 218 | #: ../qml/Main.qml:134 219 | msgid "Show/Hide apps 🙈>" 220 | msgstr "Apps tonen/verbergen 🙈>" 221 | 222 | #: ../qml/Main.qml:148 223 | msgid "Waydroid Stop app 🛑>" 224 | msgstr "App afsluiten 🛑>" 225 | 226 | #: ../qml/Main.qml:161 227 | msgid "Waydroid Help ❓>" 228 | msgstr "Hulppagina ❓>" 229 | 230 | #: ../qml/Main.qml:174 231 | msgid "Uninstall Waydroid 🗑>" 232 | msgstr "Waydroid verwijderen 🗑>" 233 | 234 | #: ../qml/Main.qml:192 235 | msgid "Show/Hide" 236 | msgstr "Tonen/Verbergen" 237 | 238 | #: ../qml/Main.qml:200 239 | msgid "Show/Hide apps" 240 | msgstr "Apps tonen/verbergen" 241 | 242 | #: ../qml/Main.qml:202 243 | msgid "" 244 | "Swipe on the listed apps to either hide them(bin) or show them(plus) in the " 245 | "Appdrawer. This will NOT install or uninstall the selected app. Reload the " 246 | "Appdrawer for the changes to take effect." 247 | msgstr "" 248 | "Veeg over een app om deze te verbergen (prullenbakpictogram) of tonen " 249 | "(pluspictogram) van/op de applijst. De app in kwestie wordt hierdoor NIET " 250 | "geïnstalleerd of verwijderd. Herlaad de applijst om het verschil te zien." 251 | 252 | #: ../qml/Main.qml:283 253 | msgid "Waydroid Stop" 254 | msgstr "WayDroid afbreken" 255 | 256 | #: ../qml/Main.qml:294 257 | msgid "" 258 | "The 'Waydroid Stop app' allows you to easily stop Waydroid without entering " 259 | "the terminal. Once you press 'Add' a new icon will appear in your appdrawer. " 260 | "Pressing this icon will stop Waydroid if it has crashed or anything else " 261 | "went wrong. If you open any of your Android apps, waydroid will start " 262 | "automaticly again." 263 | msgstr "" 264 | "Met de Waydroid-stopapp kun je Waydroid stoppen zonder in de terminal te " 265 | "duiken. Als je op ‘Toevoegen’ drukt, dan wordt er een pictogram getoond op " 266 | "de applijst. Druk hierop om Waydroid te stoppen als gevolg van een crash of " 267 | "een ander probleem. Waydroid wordt opnieuw gestart zodra je één van je " 268 | "Android-apps opent." 269 | 270 | #: ../qml/Main.qml:321 271 | msgid "Remove" 272 | msgstr "Verwijderen" 273 | 274 | #: ../qml/Main.qml:336 275 | msgid "Add" 276 | msgstr "Toevoegen" 277 | 278 | #: ../qml/Main.qml:350 279 | msgid "Waydroid Help" 280 | msgstr "Hulppagina" 281 | 282 | #: ../qml/Main.qml:360 283 | msgid "" 284 | "You need to run these commands in the terminal app. Tap a command to copy " 285 | "it to the clipboard.

" 286 | msgstr "" 287 | "Voer de volgende opdrachten uit in de terminal-app. Druk op een opdracht " 288 | "om deze te kopiëren naar het klembord.

" 289 | 290 | #: ../qml/Main.qml:382 291 | msgid "" 292 | "waydroid -h, --help show this help message and " 293 | "exit

waydroid -V, --version show program's " 294 | "version number and exit

waydroid -l LOG, --log LOG path to log file

waydroid --details-to-stdout print details (e.g. build output) " 297 | "to stdout, instead of writing to the log

waydroid -v, --verbose write even more to the logfiles (this may " 299 | "reduce performance)

waydroid -q, --quiet " 300 | "do not output any log messages" 301 | msgstr "" 302 | "waydroid -h, --help toon dit hulpbericht en sluit " 303 | "af

waydroid -V, --version toon het " 304 | "versienummer en sluit af

waydroid -l LOG, --log LOG locatie van het logboek

waydroid --details-to-stdout toon gedetailleerde informatie " 307 | "(zoals de compilatie-uitvoer) in stdout i.p.v. in het logboek

waydroid -v, --verbose schrijf gedetailleerde " 309 | "informatie naar het logboek (hierdoor kan prestatieverlies optreden) " 310 | "

waydroid -q, --quiet leg geen logboeken aan" 311 | 312 | #: ../qml/Main.qml:409 313 | msgid "" 314 | "You copied a command to your clipboard. You can now paste and use it in the " 315 | "terminal app." 316 | msgstr "" 317 | "Je hebt een opdracht gekopieerd. Open de terminalapp en plak daar de " 318 | "opdracht." 319 | 320 | #: ../qml/PasswordPrompt.qml:8 321 | msgid "Authorization" 322 | msgstr "Goedkeuring" 323 | 324 | #: ../qml/PasswordPrompt.qml:32 325 | msgid "Enter your passcode:" 326 | msgstr "Voer je toegangscode in:" 327 | 328 | #: ../qml/PasswordPrompt.qml:32 329 | msgid "Enter your password:" 330 | msgstr "Voer je wachtwoord in:" 331 | 332 | #: ../qml/PasswordPrompt.qml:39 333 | msgid "passcode" 334 | msgstr "Toegangscode" 335 | 336 | #: ../qml/PasswordPrompt.qml:39 337 | msgid "password" 338 | msgstr "Wachtwoord" 339 | 340 | #: ../qml/PasswordPrompt.qml:53 341 | msgid "Incorrect passcode" 342 | msgstr "De toegangscode is onjuist" 343 | 344 | #: ../qml/PasswordPrompt.qml:53 345 | msgid "Incorrect password" 346 | msgstr "Het wachtwoord is onjuist" 347 | 348 | #: ../qml/Uninstaller.qml:13 349 | msgid "Uninstall Waydroid" 350 | msgstr "WayDroid verwijderen" 351 | 352 | #: ../qml/Uninstaller.qml:23 353 | msgid "Press 'start' to uninstall Waydroid." 354 | msgstr "Druk op ‘Starten’ om Waydroid te verwijderen." 355 | 356 | #: ../qml/Uninstaller.qml:24 357 | msgid "Uninstallation starting" 358 | msgstr "Bezig met starten van verwijdering…" 359 | 360 | #: ../qml/Uninstaller.qml:25 361 | msgid "Stopping Waydroid container service" 362 | msgstr "Bezig met stoppen van Waydroid-containerdienst…" 363 | 364 | #: ../qml/Uninstaller.qml:26 365 | msgid "Cleaning up Waydroid images" 366 | msgstr "Bezig met opruimen van Waydroid-schijfkopieën…" 367 | 368 | #: ../qml/Uninstaller.qml:27 369 | msgid "Uninstallation complete!" 370 | msgstr "Het verwijderen is voltooid!" 371 | 372 | #: ../qml/Uninstaller.qml:110 373 | msgid "Wipe app data" 374 | msgstr "Appgegevens wissen" 375 | 376 | #~ msgid "Running" 377 | #~ msgstr "Actief" 378 | 379 | #~ msgid "" 380 | #~ "You are about to use an experimental Waydroid installer!
Supported " 381 | #~ "devices (✔️ = tested):
* VollaPhone ✔️ (X)
* Redmi Note 7 (Pro) " 382 | #~ "
* Redmi Note 9 (Pro/Pro Max/S)
* Fairphone 3(+)
* Pixel 3a " 383 | #~ "
There is absolutely no warranty for this to work! Do not use this " 384 | #~ "installer if you dont want to risk to brick your device (,which is highly " 385 | #~ "unlikely though)!" 386 | #~ msgstr "" 387 | #~ "Je staat op het punt om de experimentele WayDroid-installatiewizard te " 388 | #~ "starten!
Ondersteunde apparaten (✔️ = getest):
* VollaPhone ✔️ " 389 | #~ "(X)
* Redmi Note 7 (Pro)
* Redmi Note 9 (Pro/Pro Max/S)
* " 390 | #~ "Fairphone 3(+)
* Pixel 3a
Er is echter geen garantie dat " 391 | #~ "WayDroid gaat werken! Gebruik de installatiewizard NIET als je bang bent " 392 | #~ "voor een gebrickt apparaat (hoewel de kans heel erg klein is)!" 393 | -------------------------------------------------------------------------------- /po/pt_PT.po: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the waydroidhelper.aaronhafer package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: waydroidhelper.aaronhafer\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2023-04-07 08:47+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: Ivo Xavier \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: pt\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: ../qml/About.qml:8 ../qml/Main.qml:97 21 | msgid "About" 22 | msgstr "Acerca" 23 | 24 | #: ../qml/About.qml:32 25 | msgid "WayDroid Helper" 26 | msgstr "WayDroid Helper" 27 | 28 | #: ../qml/About.qml:50 29 | msgid "Version: " 30 | msgstr "Versão:" 31 | 32 | #: ../qml/About.qml:58 33 | msgid "A Tweak tool application for WayDroid on Ubuntu Touch." 34 | msgstr "Uma aplicação de ajustes de WayDroid para Ubuntu Touch." 35 | 36 | #. TRANSLATORS: Please make sure the URLs are correct 37 | #: ../qml/About.qml:67 38 | msgid "" 39 | "This program is free software: you can redistribute it and/or modify it " 40 | "under the terms of the GNU General Public License as published by the Free " 41 | "Software Foundation, either version 3 of the License, or (at your option) " 42 | "any later version. This program is distributed in the hope that it will be " 43 | "useful, but WITHOUT ANY WARRANTY; without even the implied warranty of " 44 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public " 46 | "License for more details." 47 | msgstr "" 48 | 49 | #: ../qml/About.qml:76 50 | msgid "DONATE" 51 | msgstr "DOAR" 52 | 53 | #: ../qml/About.qml:76 54 | msgid "ISSUES" 55 | msgstr "PROBLEMAS" 56 | 57 | #: ../qml/About.qml:76 58 | msgid "SOURCE" 59 | msgstr "CÓDIGO-FONTE" 60 | 61 | #: ../qml/About.qml:86 62 | msgid "Copyright" 63 | msgstr "Direitos de autor" 64 | 65 | #: ../qml/About.qml:95 66 | msgid "General contributions: " 67 | msgstr "Contribuições Gerais" 68 | 69 | #: ../qml/Installer.qml:12 70 | msgid "Install Waydroid" 71 | msgstr "Instalar WayDroid" 72 | 73 | #: ../qml/Installer.qml:18 74 | msgid "GAPPS" 75 | msgstr "GAPPS" 76 | 77 | #: ../qml/Installer.qml:32 78 | msgid "" 79 | "By pressing 'start' the installation will, well... start. The installer will " 80 | "let you know what it is currently doing. The installation might take a " 81 | "while. You can safely use other apps or turn off the screen, but don't close " 82 | "this one." 83 | msgstr "" 84 | "Ao pressionar 'iniciar' a instalação inicia. O instalador irá " 85 | "avisá-lo sobre o que está a ser feito. A instalação vai demorar " 86 | "um pouco. Você pode usar outras apps ou desligar o ecrã, mas não encerre " 87 | "o WayDroid Helper." 88 | 89 | #: ../qml/Installer.qml:33 90 | msgid "Installation starting" 91 | msgstr "Instalação a iniciar" 92 | 93 | #: ../qml/Installer.qml:34 94 | msgid "Preparing to download system image (with GAPPS)" 95 | msgstr "A preparar para transferir a imagem de sistema (com GAPPS)" 96 | 97 | #: ../qml/Installer.qml:35 98 | msgid "Preparing to download system image" 99 | msgstr "A preprar para transferir a imagem de sistema" 100 | 101 | #: ../qml/Installer.qml:36 102 | msgid "Downloading system image (with GAPPS)" 103 | msgstr "A transferir a imagem de sistema (com GAPPS)" 104 | 105 | #: ../qml/Installer.qml:37 106 | msgid "Downloading system image" 107 | msgstr "A transferir a imagem de sistema" 108 | 109 | #: ../qml/Installer.qml:38 110 | msgid "Downloading vendor image" 111 | msgstr "A transferir a imagem do fabricante" 112 | 113 | #: ../qml/Installer.qml:39 114 | msgid "Validating system image" 115 | msgstr "A validar o sistema de imagem" 116 | 117 | #: ../qml/Installer.qml:40 118 | msgid "Validating vendor image" 119 | msgstr "A validar a imagem do fabricante" 120 | 121 | #: ../qml/Installer.qml:41 122 | msgid "Extracting system image" 123 | msgstr "A extrair a imagem do sistema" 124 | 125 | #: ../qml/Installer.qml:42 126 | msgid "Extracting vendor image" 127 | msgstr "A extrair a imagem do fabricante" 128 | 129 | #: ../qml/Installer.qml:43 130 | msgid "Installation complete!" 131 | msgstr "Instalação completa!" 132 | 133 | #: ../qml/Installer.qml:134 ../qml/Main.qml:208 ../qml/Main.qml:414 134 | #: ../qml/PasswordPrompt.qml:58 ../qml/Uninstaller.qml:125 135 | msgid "Ok" 136 | msgstr "Ok" 137 | 138 | #: ../qml/Installer.qml:134 ../qml/Uninstaller.qml:125 139 | msgid "Start" 140 | msgstr "Iniciar" 141 | 142 | #: ../qml/Installer.qml:156 143 | msgid "" 144 | "
Fairphone 3/3
OnePlus 5/5T
OnePlus 6/6T
Pixel/Pixel " 145 | "XL
Pixel 2 XL
Pixel 3a
Poco F1
Redmi Note 7/7 Pro/9 Pro/9 Pro " 146 | "Max
Samsung Galaxy S10
SHIFT6mq
Vollaphone (X)
" 147 | msgstr "" 148 | 149 | #: ../qml/Installer.qml:156 150 | msgid "" 151 | "Other devices using Halium 9 or above may or may not work as well!
" 152 | "There is absolutely no warranty for this to work! Do not use this installer " 153 | "if you dont want to risk to brick your device permenantly (,which is highly " 154 | "unlikely though)!" 155 | msgstr "" 156 | "Outros dispositivos com Halium 9 ou acima podem ou não funcionar!
" 157 | "Não existe garantia absoluta que isto funcionará! Não utilize o WayDroid Helper " 158 | "se não pretender bloquear o seu dispositivo (sendo, contudo, " 159 | "pouco provável)!" 160 | 161 | #: ../qml/Installer.qml:156 162 | msgid "" 163 | "You are about to use an experimental Waydroid installer!
Supported " 164 | "devices:" 165 | msgstr "" 166 | "Está prestes a utilizar um instalador experimental do WayDroid!
Dispositivos " 167 | "suportados:" 168 | 169 | #: ../qml/Installer.qml:161 170 | msgid "I understand the risk" 171 | msgstr "Eu entendo o risco" 172 | 173 | #: ../qml/Installer.qml:167 ../qml/Installer.qml:210 174 | #: ../qml/PasswordPrompt.qml:78 175 | msgid "Cancel" 176 | msgstr "Cancelar" 177 | 178 | #: ../qml/Installer.qml:196 179 | msgid "" 180 | "You can install a special version of Waydroid that comes with google apps. " 181 | "(I personally do not recommend this as it will result in worse privacy.)" 182 | msgstr "" 183 | "Você pode instalar uma versão especial do WayDroid que tem as google apps. " 184 | "(Pessoalmente, não recomendo pois resulta em pior privacidade.)" 185 | #: ../qml/Installer.qml:201 186 | msgid "Enable GAPPS" 187 | msgstr "Ativar GAPPS" 188 | 189 | #: ../qml/Main.qml:92 waydroidhelper.desktop.in.h:1 190 | msgid "Waydroid Helper" 191 | msgstr "WayDroid Helper" 192 | 193 | #: ../qml/Main.qml:103 194 | msgid "Help" 195 | msgstr "Ajudar" 196 | 197 | #: ../qml/Main.qml:121 198 | msgid "Install Waydroid 📲>" 199 | msgstr "Instalar WayDroid 📲>" 200 | 201 | #: ../qml/Main.qml:134 202 | msgid "Show/Hide apps 🙈>" 203 | msgstr "Mostrar/Ocultar apps 🙈>" 204 | 205 | #: ../qml/Main.qml:148 206 | msgid "Waydroid Stop app 🛑>" 207 | msgstr "Parar WayDroid App 🛑>" 208 | 209 | #: ../qml/Main.qml:161 210 | msgid "Waydroid Help ❓>" 211 | msgstr "Ajuda Waydroid ❓>" 212 | 213 | #: ../qml/Main.qml:200 214 | msgid "Show/Hide apps" 215 | msgstr "Mostrar/Ocultar apps" 216 | 217 | #: ../qml/Main.qml:174 218 | msgid "Uninstall Waydroid 🗑>" 219 | msgstr "Desinstalar WayDroid 🗑>" 220 | 221 | #: ../qml/Main.qml:192 222 | msgid "Show/Hide" 223 | msgstr "Mostrar/Ocultar" 224 | 225 | #: ../qml/Main.qml:202 226 | msgid "" 227 | "Swipe on the listed apps to either hide them(bin) or show them(plus) in the " 228 | "Appdrawer. This will NOT install or uninstall the selected app. Reload the " 229 | "Appdrawer for the changes to take effect." 230 | msgstr "" 231 | "Deslize nas aplicações listadas para ocultá-las(bin) ou mostrá-las (mais) no " 232 | "Appdrawer. Isso NÃO instalará ou desinstalará a aplicação selecionada. Recarregue o " 233 | "Appdrawer para que as alterações entrem em vigor." 234 | 235 | #: ../qml/Main.qml:283 236 | msgid "Waydroid Stop" 237 | msgstr "Parar WayDroid" 238 | 239 | #: ../qml/Main.qml:294 240 | msgid "" 241 | "The 'Waydroid Stop app' allows you to easily stop Waydroid without entering " 242 | "the terminal. Once you press 'Add' a new icon will appear in your appdrawer. " 243 | "Pressing this icon will stop Waydroid if it has crashed or anything else " 244 | "went wrong. If you open any of your Android apps, waydroid will start " 245 | "automaticly again." 246 | msgstr "" 247 | "A aplicação 'Waydroid Stop' permite que você pare facilmente o WayDroid sem utilizar " 248 | "o terminal. Depois de pressionar 'Adicionar', um novo ícone aparecerá no AppDrawer. " 249 | "Pressionar este ícone irá parar o WayDroid se ele travar ou se qualquer outra coisa" 250 | "deu errado. Se você abrir qualquer um dos seus aplicativos Android, o WayDroid iniciará " 251 | "automaticamente de novo." 252 | 253 | #: ../qml/Main.qml:321 254 | msgid "Remove" 255 | msgstr "Remover" 256 | 257 | #: ../qml/Main.qml:336 258 | msgid "Add" 259 | msgstr "Adicionar" 260 | 261 | #: ../qml/Main.qml:350 262 | msgid "Waydroid Help" 263 | msgstr "Ajuda WayDroid" 264 | 265 | #: ../qml/Main.qml:360 266 | msgid "" 267 | "You need to run these commands in the terminal app. Tap a command to copy " 268 | "it to the clipboard.

" 269 | msgstr "" 270 | "Você precisa de executar estes comandos na app do terminal. Clique num comando para copiar " 271 | "para a área de transferências.

" 272 | 273 | #: ../qml/Main.qml:382 274 | msgid "" 275 | "waydroid -h, --help show this help message and " 276 | "exit

waydroid -V, --version show program's " 277 | "version number and exit

waydroid -l LOG, --log LOG path to log file

waydroid --details-to-stdout print details (e.g. build output) " 280 | "to stdout, instead of writing to the log

waydroid -v, --verbose write even more to the logfiles (this may " 282 | "reduce performance)

waydroid -q, --quiet " 283 | "do not output any log messages" 284 | msgstr "" 285 | 286 | #: ../qml/Main.qml:409 287 | msgid "" 288 | "You copied a command to your clipboard. You can now paste and use it in the " 289 | "terminal app." 290 | msgstr "" 291 | "Você copiou um comando para a área de transferências. Você pode agora colar e usar na " 292 | "app do terminal." 293 | 294 | #: ../qml/PasswordPrompt.qml:8 295 | msgid "Authorization" 296 | msgstr "Autorização" 297 | 298 | #: ../qml/PasswordPrompt.qml:32 299 | msgid "Enter your passcode:" 300 | msgstr "Introduza o seu código:" 301 | 302 | #: ../qml/PasswordPrompt.qml:32 303 | msgid "Enter your password:" 304 | msgstr "Introduza a sua palavra-passe" 305 | 306 | #: ../qml/PasswordPrompt.qml:39 307 | msgid "passcode" 308 | msgstr "código" 309 | 310 | #: ../qml/PasswordPrompt.qml:39 311 | msgid "password" 312 | msgstr "palavra-passe" 313 | 314 | #: ../qml/PasswordPrompt.qml:53 315 | msgid "Incorrect passcode" 316 | msgstr "Código incorreto" 317 | 318 | #: ../qml/PasswordPrompt.qml:53 319 | msgid "Incorrect password" 320 | msgstr "Palavra-passe incorreta" 321 | 322 | #: ../qml/Uninstaller.qml:13 323 | msgid "Uninstall Waydroid" 324 | msgstr "Desinstalar WayDroid" 325 | 326 | #: ../qml/Uninstaller.qml:23 327 | msgid "Press 'start' to uninstall Waydroid." 328 | msgstr "Pressione 'iniciar' para desinstalar o WayDroid" 329 | 330 | #: ../qml/Uninstaller.qml:24 331 | msgid "Uninstallation starting" 332 | msgstr "A inicar desistalação" 333 | 334 | #: ../qml/Uninstaller.qml:25 335 | msgid "Stopping Waydroid container service" 336 | msgstr "A parar o serviço de container do WayDroid" 337 | 338 | #: ../qml/Uninstaller.qml:26 339 | msgid "Cleaning up Waydroid images" 340 | msgstr "A limpar as imagens do WayDroid" 341 | 342 | #: ../qml/Uninstaller.qml:27 343 | msgid "Uninstallation complete!" 344 | msgstr "Desinstalação completa!" 345 | 346 | #: ../qml/Uninstaller.qml:110 347 | msgid "Wipe app data" 348 | msgstr "Limpar dados da app" 349 | -------------------------------------------------------------------------------- /po/waydroidhelper.aaronhafer.pot: -------------------------------------------------------------------------------- 1 | # SOME DESCRIPTIVE TITLE. 2 | # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER 3 | # This file is distributed under the same license as the waydroidhelper.aaronhafer package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: waydroidhelper.aaronhafer\n" 10 | "Report-Msgid-Bugs-To: \n" 11 | "POT-Creation-Date: 2024-03-27 23:26+0000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: ../qml/About.qml:8 ../qml/Main.qml:97 21 | msgid "About" 22 | msgstr "" 23 | 24 | #: ../qml/About.qml:32 25 | msgid "WayDroid Helper" 26 | msgstr "" 27 | 28 | #: ../qml/About.qml:50 29 | msgid "Version: " 30 | msgstr "" 31 | 32 | #: ../qml/About.qml:58 33 | msgid "A Tweak tool application for WayDroid on Ubuntu Touch." 34 | msgstr "" 35 | 36 | #. TRANSLATORS: Please make sure the URLs are correct 37 | #: ../qml/About.qml:67 38 | msgid "" 39 | "This program is free software: you can redistribute it and/or modify it " 40 | "under the terms of the GNU General Public License as published by the Free " 41 | "Software Foundation, either version 3 of the License, or (at your option) " 42 | "any later version. This program is distributed in the hope that it will be " 43 | "useful, but WITHOUT ANY WARRANTY; without even the implied warranty of " 44 | "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public " 46 | "License for more details." 47 | msgstr "" 48 | 49 | #: ../qml/About.qml:76 50 | msgid "DONATE" 51 | msgstr "" 52 | 53 | #: ../qml/About.qml:76 54 | msgid "ISSUES" 55 | msgstr "" 56 | 57 | #: ../qml/About.qml:76 58 | msgid "SOURCE" 59 | msgstr "" 60 | 61 | #: ../qml/About.qml:86 62 | msgid "Copyright" 63 | msgstr "" 64 | 65 | #: ../qml/About.qml:95 66 | msgid "General contributions: " 67 | msgstr "" 68 | 69 | #: ../qml/Installer.qml:12 70 | msgid "Install Waydroid" 71 | msgstr "" 72 | 73 | #: ../qml/Installer.qml:19 74 | msgid "GAPPS" 75 | msgstr "" 76 | 77 | #: ../qml/Installer.qml:34 78 | msgid "" 79 | "By pressing 'start' the installation will, well... start. The installer will " 80 | "let you know what it is currently doing. The installation might take a " 81 | "while. You can safely use other apps or turn off the screen, but don't close " 82 | "this one." 83 | msgstr "" 84 | 85 | #: ../qml/Installer.qml:35 86 | msgid "Installation starting" 87 | msgstr "" 88 | 89 | #: ../qml/Installer.qml:36 90 | msgid "Preparing to download system image (with GAPPS)" 91 | msgstr "" 92 | 93 | #: ../qml/Installer.qml:37 94 | msgid "Preparing to download system image" 95 | msgstr "" 96 | 97 | #: ../qml/Installer.qml:38 98 | msgid "Downloading system image (with GAPPS)" 99 | msgstr "" 100 | 101 | #: ../qml/Installer.qml:39 102 | msgid "Downloading system image" 103 | msgstr "" 104 | 105 | #: ../qml/Installer.qml:40 106 | msgid "Downloading vendor image" 107 | msgstr "" 108 | 109 | #: ../qml/Installer.qml:41 110 | msgid "Validating system image" 111 | msgstr "" 112 | 113 | #: ../qml/Installer.qml:42 114 | msgid "Validating vendor image" 115 | msgstr "" 116 | 117 | #: ../qml/Installer.qml:43 118 | msgid "Extracting system image" 119 | msgstr "" 120 | 121 | #: ../qml/Installer.qml:44 122 | msgid "Extracting vendor image" 123 | msgstr "" 124 | 125 | #: ../qml/Installer.qml:45 126 | msgid "Installation complete!" 127 | msgstr "" 128 | 129 | #: ../qml/Installer.qml:143 ../qml/Main.qml:208 ../qml/Main.qml:414 130 | #: ../qml/PasswordPrompt.qml:58 ../qml/Uninstaller.qml:125 131 | msgid "Ok" 132 | msgstr "" 133 | 134 | #: ../qml/Installer.qml:143 ../qml/Uninstaller.qml:125 135 | msgid "Start" 136 | msgstr "" 137 | 138 | #: ../qml/Installer.qml:165 139 | msgid "" 140 | "

There is absolutely no warranty for this to work! Do not use this " 141 | "installer if you dont want to risk to brick your device permenantly (,which " 142 | "is highly unlikely though)!" 143 | msgstr "" 144 | 145 | #: ../qml/Installer.qml:165 146 | msgid "" 147 | "You are about to use an experimental Waydroid installer!
You can check " 148 | "if your device is supported on https://devices.ubuntu-touch.io" 150 | msgstr "" 151 | 152 | #: ../qml/Installer.qml:171 153 | msgid "I understand the risk" 154 | msgstr "" 155 | 156 | #: ../qml/Installer.qml:177 ../qml/Installer.qml:210 ../qml/Installer.qml:253 157 | #: ../qml/PasswordPrompt.qml:78 158 | msgid "Cancel" 159 | msgstr "" 160 | 161 | #: ../qml/Installer.qml:194 162 | msgid "" 163 | "Waydroid doesn't officially support Android versions past 11 (yet). Your " 164 | "device can still run unofficial Android 13 images (with some additional " 165 | "bugs) from https://sourceforge.net/projects/aleasto-" 167 | "lineageos/files/LineageOS 20/waydroid_arm64 which Waydroid Helper can " 168 | "setup (excluding GAPPS).

Do you want to continue?" 169 | msgstr "" 170 | 171 | #: ../qml/Installer.qml:200 172 | msgid "Yes" 173 | msgstr "" 174 | 175 | #: ../qml/Installer.qml:239 176 | msgid "" 177 | "You can install a special version of Waydroid that comes with google apps. " 178 | "(I personally do not recommend this as it will result in worse privacy.)" 179 | msgstr "" 180 | 181 | #: ../qml/Installer.qml:244 182 | msgid "Enable GAPPS" 183 | msgstr "" 184 | 185 | #: ../qml/Main.qml:92 waydroidhelper.desktop.in.h:1 186 | msgid "Waydroid Helper" 187 | msgstr "" 188 | 189 | #: ../qml/Main.qml:103 190 | msgid "Help" 191 | msgstr "" 192 | 193 | #: ../qml/Main.qml:121 194 | msgid "Install Waydroid 📲>" 195 | msgstr "" 196 | 197 | #: ../qml/Main.qml:134 198 | msgid "Show/Hide apps 🙈>" 199 | msgstr "" 200 | 201 | #: ../qml/Main.qml:148 202 | msgid "Waydroid Stop app 🛑>" 203 | msgstr "" 204 | 205 | #: ../qml/Main.qml:161 206 | msgid "Waydroid Help ❓>" 207 | msgstr "" 208 | 209 | #: ../qml/Main.qml:174 210 | msgid "Uninstall Waydroid 🗑>" 211 | msgstr "" 212 | 213 | #: ../qml/Main.qml:192 214 | msgid "Show/Hide" 215 | msgstr "" 216 | 217 | #: ../qml/Main.qml:200 218 | msgid "Show/Hide apps" 219 | msgstr "" 220 | 221 | #: ../qml/Main.qml:202 222 | msgid "" 223 | "Swipe on the listed apps to either hide them(bin) or show them(plus) in the " 224 | "Appdrawer. This will NOT install or uninstall the selected app. Reload the " 225 | "Appdrawer for the changes to take effect." 226 | msgstr "" 227 | 228 | #: ../qml/Main.qml:283 229 | msgid "Waydroid Stop" 230 | msgstr "" 231 | 232 | #: ../qml/Main.qml:294 233 | msgid "" 234 | "The 'Waydroid Stop app' allows you to easily stop Waydroid without entering " 235 | "the terminal. Once you press 'Add' a new icon will appear in your appdrawer. " 236 | "Pressing this icon will stop Waydroid if it has crashed or anything else " 237 | "went wrong. If you open any of your Android apps, waydroid will start " 238 | "automaticly again." 239 | msgstr "" 240 | 241 | #: ../qml/Main.qml:321 242 | msgid "Remove" 243 | msgstr "" 244 | 245 | #: ../qml/Main.qml:336 246 | msgid "Add" 247 | msgstr "" 248 | 249 | #: ../qml/Main.qml:350 250 | msgid "Waydroid Help" 251 | msgstr "" 252 | 253 | #: ../qml/Main.qml:360 254 | msgid "" 255 | "You need to run these commands in the terminal app. Tap a command to copy " 256 | "it to the clipboard.

" 257 | msgstr "" 258 | 259 | #: ../qml/Main.qml:382 260 | msgid "" 261 | "waydroid -h, --help show this help message and " 262 | "exit

waydroid -V, --version show program's " 263 | "version number and exit

waydroid -l LOG, --log LOG path to log file

waydroid --details-to-stdout print details (e.g. build output) " 266 | "to stdout, instead of writing to the log

waydroid -v, --verbose write even more to the logfiles (this may " 268 | "reduce performance)

waydroid -q, --quiet " 269 | "do not output any log messages" 270 | msgstr "" 271 | 272 | #: ../qml/Main.qml:409 273 | msgid "" 274 | "You copied a command to your clipboard. You can now paste and use it in the " 275 | "terminal app." 276 | msgstr "" 277 | 278 | #: ../qml/PasswordPrompt.qml:8 279 | msgid "Authorization" 280 | msgstr "" 281 | 282 | #: ../qml/PasswordPrompt.qml:32 283 | msgid "Enter your passcode:" 284 | msgstr "" 285 | 286 | #: ../qml/PasswordPrompt.qml:32 287 | msgid "Enter your password:" 288 | msgstr "" 289 | 290 | #: ../qml/PasswordPrompt.qml:39 291 | msgid "passcode" 292 | msgstr "" 293 | 294 | #: ../qml/PasswordPrompt.qml:39 295 | msgid "password" 296 | msgstr "" 297 | 298 | #: ../qml/PasswordPrompt.qml:53 299 | msgid "Incorrect passcode" 300 | msgstr "" 301 | 302 | #: ../qml/PasswordPrompt.qml:53 303 | msgid "Incorrect password" 304 | msgstr "" 305 | 306 | #: ../qml/Uninstaller.qml:13 307 | msgid "Uninstall Waydroid" 308 | msgstr "" 309 | 310 | #: ../qml/Uninstaller.qml:23 311 | msgid "Press 'start' to uninstall Waydroid." 312 | msgstr "" 313 | 314 | #: ../qml/Uninstaller.qml:24 315 | msgid "Uninstallation starting" 316 | msgstr "" 317 | 318 | #: ../qml/Uninstaller.qml:25 319 | msgid "Stopping Waydroid container service" 320 | msgstr "" 321 | 322 | #: ../qml/Uninstaller.qml:26 323 | msgid "Cleaning up Waydroid images" 324 | msgstr "" 325 | 326 | #: ../qml/Uninstaller.qml:27 327 | msgid "Uninstallation complete!" 328 | msgstr "" 329 | 330 | #: ../qml/Uninstaller.qml:110 331 | msgid "Wipe app data" 332 | msgstr "" 333 | -------------------------------------------------------------------------------- /qml/About.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import Lomiri.Components 1.3 3 | 4 | Page { 5 | id: aboutPage 6 | 7 | header: PageHeader { 8 | title: i18n.tr("About") 9 | } 10 | 11 | ScrollView { 12 | id: scrollView 13 | anchors { 14 | top: aboutPage.header.bottom 15 | bottom: parent.bottom 16 | left: parent.left 17 | right: parent.right 18 | leftMargin: units.gu(2) 19 | rightMargin: units.gu(2) 20 | } 21 | 22 | clip: true 23 | 24 | Column { 25 | id: aboutColumn 26 | spacing: units.gu(2) 27 | width: scrollView.width 28 | 29 | Label { 30 | anchors.horizontalCenter: parent.horizontalCenter 31 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 32 | text: i18n.tr("WayDroid Helper") 33 | fontSize: "x-large" 34 | } 35 | 36 | LomiriShape { 37 | width: units.gu(12); height: units.gu(12) 38 | anchors.horizontalCenter: parent.horizontalCenter 39 | radius: "medium" 40 | image: Image { 41 | source: Qt.resolvedUrl("../assets/logo.png") 42 | } 43 | } 44 | 45 | Label { 46 | width: parent.width 47 | linkColor: LomiriColors.orange 48 | horizontalAlignment: Text.AlignHCenter 49 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 50 | text: i18n.tr("Version: ") + "%1".arg(Qt.application.version) 51 | } 52 | 53 | Label { 54 | width: parent.width 55 | linkColor: LomiriColors.orange 56 | horizontalAlignment: Text.AlignHCenter 57 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 58 | text: i18n.tr("A Tweak tool application for WayDroid on Ubuntu Touch.") 59 | } 60 | 61 | Label { 62 | width: parent.width 63 | linkColor: LomiriColors.orange 64 | horizontalAlignment: Text.AlignHCenter 65 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 66 | //TRANSLATORS: Please make sure the URLs are correct 67 | text: i18n.tr("This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 General Public License for more details.") 68 | onLinkActivated: Qt.openUrlExternally(link) 69 | } 70 | 71 | Label { 72 | width: parent.width 73 | linkColor: LomiriColors.orange 74 | horizontalAlignment: Text.AlignHCenter 75 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 76 | text: "" + i18n.tr("SOURCE") + " | " + i18n.tr("ISSUES") + " | " + i18n.tr("DONATE") + "" 77 | onLinkActivated: Qt.openUrlExternally(link) 78 | } 79 | 80 | Label { 81 | width: parent.width 82 | linkColor: LomiriColors.orange 83 | horizontalAlignment: Text.AlignHCenter 84 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 85 | style: Font.Bold 86 | text: i18n.tr("Copyright") + " (c) 2021 - 2022 Aaron Hafer" 87 | } 88 | 89 | Label { 90 | width: parent.width 91 | linkColor: LomiriColors.orange 92 | horizontalAlignment: Text.AlignHCenter 93 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 94 | style: Font.Bold 95 | text: i18n.tr("General contributions: ") + "Rudi Timmermans" 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /qml/Installer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.7 2 | import Lomiri.Components 1.3 3 | import QtQuick.Layouts 1.3 4 | import Qt.labs.settings 1.0 5 | import io.thp.pyotherside 1.4 6 | import Lomiri.Components.Popups 1.3 7 | 8 | Page { 9 | id: installerPage 10 | header: PageHeader { 11 | id: header 12 | title: i18n.tr("Install Waydroid") 13 | opacity: 1 14 | trailingActionBar { 15 | actions: [ 16 | Action { 17 | id: gappsAction 18 | iconName: "google-plus-symbolic" 19 | text: i18n.tr("GAPPS") 20 | enabled: !running 21 | onTriggered: PopupUtils.open(gapps) 22 | } 23 | ] 24 | } 25 | } 26 | 27 | property var gAPPS: false 28 | property var needsCustomImages: false 29 | property bool completed: false 30 | property bool running: false 31 | 32 | property string state: "initial" 33 | property var states: new Map([ 34 | [ "initial", i18n.tr("By pressing 'start' the installation will, well... start. The installer will let you know what it is currently doing. The installation might take a while. You can safely use other apps or turn off the screen, but don't close this one.") ], 35 | [ "starting", i18n.tr("Installation starting") ], 36 | [ "dl.init.gapps", i18n.tr("Preparing to download system image (with GAPPS)") ], 37 | [ "dl.init.vanilla", i18n.tr("Preparing to download system image") ], 38 | [ "dl.gapps", i18n.tr("Downloading system image (with GAPPS)") ], 39 | [ "dl.vanilla", i18n.tr("Downloading system image") ], 40 | [ "dl.vendor", i18n.tr("Downloading vendor image") ], 41 | [ "validate.system", i18n.tr("Validating system image") ], 42 | [ "validate.vendor", i18n.tr("Validating vendor image") ], 43 | [ "extract.system", i18n.tr("Extracting system image") ], 44 | [ "extract.vendor", i18n.tr("Extracting vendor image") ], 45 | [ "complete", i18n.tr("Installation complete!") ], 46 | ]) 47 | 48 | function startInstallation(password) { 49 | installerPage.running = true; 50 | root.setAppLifecycleExemption(); 51 | python.call('installer.install', [ password, gAPPS, needsCustomImages ]); 52 | } 53 | 54 | function showPasswordPrompt() { 55 | if (root.inputMethodHints === null) { 56 | startInstallation(''); 57 | return; 58 | } 59 | 60 | PopupUtils.open(passwordPrompt); 61 | } 62 | 63 | Connections { 64 | target: python 65 | 66 | onState: { // (string id, bool hasProgress) 67 | if (!installerPage.states.has(id)) { 68 | console.log('unknown state', id); 69 | return; 70 | } 71 | 72 | if (id === installerPage.state && hasProgress === !progress.indeterminate) { 73 | return; 74 | } 75 | 76 | progress.indeterminate = !hasProgress; 77 | installerPage.state = id; 78 | 79 | if (id === "complete") { 80 | installerPage.completed = true; 81 | installerPage.running = false; 82 | root.unsetAppLifecycleExemption(); 83 | } 84 | } 85 | 86 | onDownloadProgress: { // (real current, real target, real speed, string unit) 87 | progress.maximumValue = target 88 | progress.value = current; 89 | unit = unit === 'kbps' ? 'KB/s' : 'MB/s'; 90 | if (speed > 1000 && unit === 'KB/s') { 91 | speed = speed / 1000; 92 | unit = 'MB/s' 93 | } 94 | 95 | progressText.text = `${current.toFixed(2)} MB/${target.toFixed(2)} MB (${speed.toFixed(2)} ${unit})`; 96 | } 97 | } 98 | 99 | ColumnLayout { 100 | anchors { 101 | horizontalCenter: parent.horizontalCenter 102 | verticalCenter: parent.verticalCenter 103 | } 104 | spacing: units.gu(2) 105 | 106 | Component.onCompleted: { 107 | PopupUtils.open(dialogInstall); 108 | python.call('installer.needs_custom_images', [], function (needsCustom) { 109 | if (needsCustom) { 110 | PopupUtils.open(dialogCustom); 111 | } 112 | }); 113 | } 114 | 115 | Label { 116 | id: content 117 | text: installerPage.states.get(installerPage.state) 118 | horizontalAlignment: Text.AlignHCenter 119 | wrapMode: Text.Wrap 120 | Layout.alignment: Qt.AlignHCenter 121 | Layout.preferredWidth: installerPage.width / 1.5 122 | } 123 | 124 | ProgressBar { 125 | id: progress 126 | visible: running 127 | indeterminate: true 128 | value: 0 129 | Layout.alignment: Qt.AlignHCenter 130 | } 131 | 132 | Label { 133 | id: progressText 134 | visible: running 135 | opacity: progress.indeterminate ? 0 : 1 136 | Layout.alignment: Qt.AlignHCenter 137 | } 138 | 139 | Button { 140 | id: startButton 141 | visible: !running 142 | color: theme.palette.normal.positive 143 | text: installerPage.completed ? i18n.tr("Ok") : i18n.tr("Start") 144 | Layout.alignment: Qt.AlignHCenter 145 | 146 | onClicked: { 147 | if (!installerPage.completed) { 148 | showPasswordPrompt(); 149 | return; 150 | } 151 | 152 | pageStack.pop(); 153 | } 154 | } 155 | } 156 | 157 | Component { 158 | id: dialogInstall 159 | 160 | Dialog { 161 | id: dialogueInstall 162 | title: "Disclaimer!" 163 | 164 | Label { 165 | text: i18n.tr("You are about to use an experimental Waydroid installer!
You can check if your device is supported on https://devices.ubuntu-touch.io") + i18n.tr("

There is absolutely no warranty for this to work! Do not use this installer if you dont want to risk to brick your device permenantly (,which is highly unlikely though)!") 166 | wrapMode: Text.Wrap 167 | onLinkActivated: Qt.openUrlExternally(link) 168 | } 169 | 170 | Button { 171 | text: i18n.tr ("I understand the risk") 172 | color: theme.palette.normal.negative 173 | onClicked: PopupUtils.close(dialogueInstall) 174 | } 175 | 176 | Button { 177 | text: i18n.tr("Cancel") 178 | onClicked: { 179 | PopupUtils.close(dialogueInstall); 180 | pageStack.pop(); 181 | } 182 | } 183 | } 184 | } 185 | 186 | Component { 187 | id: dialogCustom 188 | 189 | Dialog { 190 | id: dialogueCustom 191 | title: "Disclaimer!" 192 | 193 | Label { 194 | text: i18n.tr("Waydroid doesn't officially support Android versions past 11 (yet). Your device can still run unofficial Android 13 images (with some additional bugs) from https://sourceforge.net/projects/aleasto-lineageos/files/LineageOS 20/waydroid_arm64 which Waydroid Helper can setup (excluding GAPPS).

Do you want to continue?") 195 | wrapMode: Text.Wrap 196 | onLinkActivated: Qt.openUrlExternally(link) 197 | } 198 | 199 | Button { 200 | text: i18n.tr("Yes") 201 | color: theme.palette.normal.negative 202 | onClicked: { 203 | needsCustomImages = true; 204 | gappsAction.visible = false; 205 | PopupUtils.close(dialogueCustom); 206 | } 207 | } 208 | 209 | Button { 210 | text: i18n.tr("Cancel") 211 | onClicked: { 212 | PopupUtils.close(dialogueCustom); 213 | pageStack.pop(); 214 | } 215 | } 216 | } 217 | } 218 | 219 | Component { 220 | id: passwordPrompt 221 | 222 | PasswordPrompt { 223 | id: passPrompt 224 | 225 | onPassword: { 226 | startInstallation(password); 227 | } 228 | } 229 | } 230 | 231 | Component { 232 | id: gapps 233 | 234 | Dialog { 235 | id: gappsPrompt 236 | title: "Google Apps" 237 | 238 | Label { 239 | text: i18n.tr("You can install a special version of Waydroid that comes with google apps. (I personally do not recommend this as it will result in worse privacy.)") 240 | wrapMode: Text.Wrap 241 | } 242 | 243 | Button { 244 | text: i18n.tr("Enable GAPPS") 245 | color: theme.palette.normal.focus 246 | onClicked: { 247 | gAPPS = true; 248 | PopupUtils.close(gappsPrompt); 249 | } 250 | } 251 | 252 | Button { 253 | text: i18n.tr("Cancel") 254 | onClicked: { 255 | PopupUtils.close(gappsPrompt); 256 | } 257 | } 258 | } 259 | } 260 | 261 | Python { 262 | id: python 263 | 264 | signal state(string id, bool hasProgress) 265 | signal downloadProgress(real current, real target, real speed, string unit) 266 | 267 | Component.onCompleted: { 268 | python.setHandler('state', state); 269 | python.setHandler('downloadProgress', downloadProgress); 270 | } 271 | 272 | onError: { 273 | console.log('python error:', traceback); 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /qml/PasswordPrompt.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.7 2 | import Lomiri.Components 1.3 3 | import io.thp.pyotherside 1.4 4 | import Lomiri.Components.Popups 1.3 5 | 6 | Dialog { 7 | id: passPrompt 8 | title: i18n.tr("Authorization") 9 | 10 | signal password(string password) 11 | signal cancel 12 | property bool blocked: false 13 | 14 | QtObject { 15 | id: d 16 | 17 | function checkPassword(password) { 18 | return new Promise((resolve, reject) => { 19 | python.call('pam.authenticate', [ "phablet", password ], (result) => { 20 | if (!result) { 21 | reject(); 22 | return; 23 | } 24 | 25 | resolve(); 26 | }); 27 | }); 28 | } 29 | } 30 | 31 | Label { 32 | text: root.isPasswordNumeric ? i18n.tr("Enter your passcode:") : i18n.tr("Enter your password:") 33 | wrapMode: Text.Wrap 34 | } 35 | 36 | TextField { 37 | id: password 38 | readOnly: blocked 39 | placeholderText: root.isPasswordNumeric ? i18n.tr("passcode") : i18n.tr("password") 40 | echoMode: TextInput.Password 41 | inputMethodHints: root.inputMethodHints 42 | maximumLength: root.isPasswordNumeric ? 12 : 32767 43 | onDisplayTextChanged: { 44 | if (password.text.length > 0) { 45 | wrongPasswordHint.visible = false; 46 | } 47 | } 48 | } 49 | 50 | Label { 51 | id: wrongPasswordHint 52 | color: theme.palette.normal.negative 53 | text: root.isPasswordNumeric ? i18n.tr("Incorrect passcode") : i18n.tr("Incorrect password") 54 | visible: false 55 | } 56 | 57 | Button { 58 | text: i18n.tr("Ok") 59 | enabled: !blocked 60 | color: theme.palette.normal.positive 61 | onClicked: { 62 | blocked = true; 63 | d.checkPassword(password.text) 64 | .then(() => { 65 | PopupUtils.close(passPrompt); 66 | passPrompt.password(password.text); 67 | password.text = ''; 68 | }) 69 | .catch(() => { 70 | wrongPasswordHint.visible = true; 71 | password.text = ''; 72 | blocked = false; 73 | }); 74 | } 75 | } 76 | 77 | Button { 78 | text: i18n.tr("Cancel") 79 | enabled: !blocked 80 | onClicked: { 81 | passPrompt.cancel(); 82 | PopupUtils.close(passPrompt); 83 | } 84 | } 85 | 86 | Python { 87 | id: python 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /qml/Uninstaller.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.7 2 | import Lomiri.Components 1.3 3 | //import QtQuick.Controls 2.2 4 | import QtQuick.Layouts 1.3 5 | import Qt.labs.settings 1.0 6 | import io.thp.pyotherside 1.3 7 | import Lomiri.Components.Popups 1.3 8 | 9 | Page { 10 | id: uninstallerPage 11 | header: PageHeader { 12 | id: header 13 | title: i18n.tr("Uninstall Waydroid") 14 | opacity: 1 15 | } 16 | 17 | property bool completed: false 18 | property bool running: false 19 | property alias wipeData: wipe.checked 20 | 21 | property string state: "initial" 22 | property var states: new Map([ 23 | [ "initial", i18n.tr("Press 'start' to uninstall Waydroid.") ], 24 | [ "starting", i18n.tr("Uninstallation starting") ], 25 | [ "container", i18n.tr("Stopping Waydroid container service") ], 26 | [ "cleanup", i18n.tr("Cleaning up Waydroid images") ], 27 | [ "complete", i18n.tr("Uninstallation complete!") ], 28 | ]) 29 | 30 | function startUninstallation(password) { 31 | uninstallerPage.running = true; 32 | root.setAppLifecycleExemption(); 33 | python.call('installer.uninstall', [ password, wipeData ]); 34 | } 35 | 36 | function showPasswordPrompt() { 37 | if (root.inputMethodHints === null) { 38 | startUninstallation(''); 39 | return; 40 | } 41 | 42 | PopupUtils.open(passwordPrompt); 43 | } 44 | 45 | Connections { 46 | target: python 47 | 48 | onState: { // (string id, bool hasProgress) 49 | if (!uninstallerPage.states.has(id)) { 50 | console.log('unknown state', id); 51 | return; 52 | } 53 | 54 | if (id === uninstallerPage.state && hasProgress === !progress.indeterminate) { 55 | return; 56 | } 57 | 58 | progress.indeterminate = !hasProgress; 59 | uninstallerPage.state = id; 60 | 61 | if (id === "complete") { 62 | uninstallerPage.completed = true; 63 | uninstallerPage.running = false; 64 | root.unsetAppLifecycleExemption(); 65 | } 66 | } 67 | } 68 | 69 | ColumnLayout { 70 | anchors { 71 | horizontalCenter: parent.horizontalCenter 72 | verticalCenter: parent.verticalCenter 73 | } 74 | spacing: units.gu(3) 75 | 76 | Label { 77 | id: content 78 | text: uninstallerPage.states.get(uninstallerPage.state) 79 | horizontalAlignment: Text.AlignHCenter 80 | wrapMode: Text.Wrap 81 | Layout.alignment: Qt.AlignHCenter 82 | Layout.preferredWidth: uninstallerPage.width / 1.5 83 | } 84 | 85 | ProgressBar { 86 | id: progress 87 | visible: running 88 | indeterminate: true 89 | value: 0 90 | Layout.alignment: Qt.AlignHCenter 91 | } 92 | 93 | Label { 94 | id: progressText 95 | visible: running 96 | opacity: progress.indeterminate ? 0 : 1 97 | Layout.alignment: Qt.AlignHCenter 98 | } 99 | 100 | Row { 101 | visible: !running && !uninstallerPage.completed 102 | spacing: units.gu(1) 103 | Layout.alignment: Qt.AlignHCenter 104 | 105 | CheckBox { 106 | id: wipe 107 | } 108 | 109 | Label { 110 | text: i18n.tr("Wipe app data") 111 | 112 | MouseArea { 113 | anchors.fill: parent 114 | onClicked: { 115 | wipe.checked = !wipe.checked; 116 | } 117 | } 118 | } 119 | } 120 | 121 | Button { 122 | id: startButton 123 | visible: !running 124 | color: theme.palette.normal.positive 125 | text: uninstallerPage.completed ? i18n.tr("Ok") : i18n.tr("Start") 126 | Layout.alignment: Qt.AlignHCenter 127 | 128 | onClicked: { 129 | if (!uninstallerPage.completed) { 130 | showPasswordPrompt(); 131 | return; 132 | } 133 | 134 | pageStack.pop(); 135 | } 136 | } 137 | } 138 | 139 | Component { 140 | id: passwordPrompt 141 | 142 | PasswordPrompt { 143 | onPassword: { 144 | startUninstallation(password); 145 | } 146 | } 147 | } 148 | 149 | Python { 150 | id: python 151 | 152 | signal state(string id, bool hasProgress) 153 | 154 | Component.onCompleted: { 155 | python.setHandler('state', state); 156 | } 157 | 158 | onError: { 159 | console.log('python error:', traceback); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /qml/modules/CenteredLabel.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import Lomiri.Components 1.3 3 | 4 | Label { 5 | anchors.horizontalCenter: parent.horizontalCenter 6 | width: Math.min(units.gu(80), parent.width - gnalMargins * 3) 7 | horizontalAlignment: Text.AlignHCenter 8 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 9 | } 10 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aarontheissueguy/WaydroidHelper/26800434fb1be15a5bd87cfa5ec31be2f03aaf21/src/__init__.py -------------------------------------------------------------------------------- /src/installer.py: -------------------------------------------------------------------------------- 1 | import pyotherside 2 | import time 3 | import os 4 | import threading 5 | import sys 6 | sys.path.append('deps') 7 | sys.path.append('../deps') 8 | import pexpect 9 | import subprocess 10 | import urllib.request 11 | import shutil 12 | 13 | import password_type 14 | 15 | class Installer: 16 | click_rootdir = os.getcwd() 17 | 18 | def needs_custom_images(self) -> bool: 19 | if os.uname().machine != "aarch64": # images only compiled for arm64 20 | return False 21 | 22 | if shutil.which("getprop") is None: 23 | return False 24 | 25 | try: 26 | vndk = int(subprocess.check_output(["getprop", "ro.vndk.version"])) 27 | except ValueError: 28 | return False 29 | 30 | if vndk <= 30: # only concerns Android 12, 12L & 13 atm 31 | return False 32 | 33 | try: 34 | c = urllib.request.urlopen("https://ota.waydro.id/vendor/waydroid_arm64/HALIUM_13.json") 35 | c.close() 36 | return False 37 | except urllib.error.HTTPError as e: 38 | return e.code == 404 39 | 40 | def get_password_type(self): 41 | return password_type.get_password_type() 42 | 43 | def install(self, password, gAPPS, needsCustomImages): 44 | os.chdir("/home/phablet") 45 | 46 | #Starting bash and getting root privileges 47 | print("Starting bash and getting root privileges") 48 | pyotherside.send('state', 'starting', False) 49 | child = pexpect.spawn('bash') 50 | child.logfile_read = sys.stdout.buffer 51 | child.expect(r'\$') 52 | child.sendline('sudo -s') 53 | if password != '': 54 | child.expect('[p/P]ass.*') 55 | child.sendline(str(password)) 56 | child.expect('root.*') 57 | 58 | #Initializing waydroid (downloading lineage) 59 | def download(): 60 | if needsCustomImages: 61 | print("Initializing waydroid with custom images (downloading lineage)") 62 | pyotherside.send('state', 'dl.init.vanilla', True) 63 | child.sendline(f"python3 {self.click_rootdir}/src/waydroid-custom-init") 64 | elif gAPPS == True: 65 | print("Initializing waydroid with GAPPS (downloading lineage)") 66 | pyotherside.send('state', 'dl.init.gapps', True) 67 | child.sendline("waydroid init -s GAPPS") 68 | else: 69 | print("Initializing waydroid (downloading lineage)") 70 | pyotherside.send('state', 'dl.init.vanilla', True) 71 | child.sendline("waydroid init") 72 | 73 | def dlstatus(): 74 | print("Download status running") 75 | downloaded = [] 76 | startedDownload = False 77 | 78 | def wait_for_extract(image): 79 | if not image in downloaded: 80 | pyotherside.send('state', 'validate.' + image, False) 81 | child.expect("Extracting to") 82 | pyotherside.send('state', 'extract.' + image, False) 83 | downloaded.append(image) 84 | 85 | while True: 86 | if not startedDownload: 87 | index = child.expect(['Already initialized', pexpect.EOF, pexpect.TIMEOUT], timeout=1) 88 | if index == 0: 89 | # already initialized 90 | break 91 | 92 | index = child.expect(['\r\[Downloading\]\s+([\d\.]+) MB/([\d\.]+) MB\s+([\d\.]+) ([km]bps)\(approx.\)$', pexpect.EOF, pexpect.TIMEOUT], timeout=1) 93 | if index == 0: 94 | startedDownload = True 95 | if 'system' in downloaded: 96 | pyotherside.send('state', 'dl.vendor', True) 97 | else: 98 | pyotherside.send('state', 'dl.gapps' if gAPPS == True else 'dl.vanilla', True) 99 | 100 | progress = float(child.match.group(1)) 101 | target = float(child.match.group(2)) 102 | speed = float(child.match.group(3)) 103 | unit = child.match.group(4) 104 | 105 | pyotherside.send('downloadProgress', progress, target, speed, unit) 106 | 107 | index = child.expect(["Validating system image", pexpect.EOF, pexpect.TIMEOUT], timeout=0.5) 108 | if index == 0: 109 | wait_for_extract('system') 110 | index = child.expect(["Validating vendor image", pexpect.EOF, pexpect.TIMEOUT], timeout=0.5) 111 | if index == 0: 112 | wait_for_extract('vendor') 113 | 114 | if 'system' in downloaded and 'vendor' in downloaded: 115 | break 116 | 117 | trd1 = threading.Thread(target=download) 118 | trd2 = threading.Thread(target=dlstatus) 119 | 120 | trd1.start() 121 | trd2.start() 122 | 123 | #wait for the threads to finish 124 | trd1.join() 125 | trd2.join() 126 | child.expect("root.*", timeout=300) 127 | child.close() 128 | 129 | pyotherside.send('state', 'complete', False) 130 | 131 | return "" 132 | 133 | def uninstall(self, password, wipe=False): 134 | os.chdir("/home/phablet") 135 | 136 | #Starting bash and getting root privileges 137 | print("Starting bash and getting root privileges") 138 | pyotherside.send('state', 'starting', False) 139 | child = pexpect.spawn('bash') 140 | child.logfile_read = sys.stdout.buffer 141 | child.expect(r'\$') 142 | child.sendline('sudo -s') 143 | if password != '': 144 | child.expect('[p/P]ass.*') 145 | child.sendline(str(password)) 146 | child.expect('root.*') 147 | 148 | #Stop Waydroid Container 149 | print("stopping waydroid container") 150 | pyotherside.send('state', 'container', False) 151 | child.sendline("systemctl stop waydroid-container") 152 | child.expect("root.*", timeout=180) 153 | 154 | #do cleanup 155 | print("cleaning") 156 | pyotherside.send('state', 'cleanup', False) 157 | child.sendline("rm -rf /var/lib/waydroid/*") 158 | child.expect('root.*') 159 | if os.path.isdir("/etc/waydroid-extra"): 160 | child.sendline("rm -rf {/userdata/system-data,}/etc/waydroid-extra/*") 161 | child.expect('root.*') 162 | child.sendline("rm -f /home/phablet/.local/share/applications/Waydroid.desktop") 163 | child.expect('root.*') 164 | 165 | if wipe: 166 | child.sendline("rm -rf /home/phablet/.local/share/waydroid") 167 | child.expect('root.*') 168 | child.sendline("rm -rf /home/phablet/.local/share/applications/waydroid.*.desktop") 169 | child.expect('root.*') 170 | child.sendline("rm -f /home/phablet/.local/share/applications/stop-waydroid.desktop") 171 | child.expect('root.*') 172 | 173 | pyotherside.send('state', 'complete', False) 174 | child.close() 175 | 176 | installer = Installer() 177 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Copyright (C) 2021 Aaron Hafer 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; version 3. 7 | 8 | waydroidhelper is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . 15 | ''' 16 | import os, re 17 | import configparser 18 | 19 | class Appdrawer: 20 | def return_apps(self): 21 | wdapplist = os.listdir("/home/phablet/.local/share/applications") 22 | return [i for i in wdapplist if i.startswith("waydroid.") and i.endswith(".desktop")] 23 | 24 | def clean(self): 25 | wdapplist = self.return_apps() 26 | cleannames = [] 27 | 28 | for i in wdapplist: 29 | abs_path = os.path.join("/home/phablet/.local/share/applications/", i) 30 | if not os.path.isfile(abs_path): 31 | pass 32 | with open(abs_path, "r") as f: 33 | re_name = re.search(r"Name=(.*)", f.read()) 34 | if re_name: 35 | appname = re_name.group(1) 36 | cleannames.append(appname) 37 | 38 | return sorted(cleannames) 39 | 40 | def clean_to_path(self, appname): 41 | wdapplist = self.return_apps() 42 | path = None 43 | for i in wdapplist: 44 | abs_path = os.path.join("/home/phablet/.local/share/applications/", i) 45 | if not os.path.isfile(abs_path): 46 | pass 47 | with open(abs_path, "r") as f: 48 | re_name = re.search(r"Name=%s" % appname, f.read()) 49 | if re_name: 50 | path = i 51 | break 52 | return path 53 | 54 | def show(self, appname): 55 | path = self.clean_to_path(appname) 56 | abs_path = os.path.join("/home/phablet/.local/share/applications/", path) 57 | if not os.path.isfile(abs_path): 58 | return 59 | desktop = configparser.ConfigParser() 60 | desktop.optionxform = str # Keep case of keys 61 | desktop.read(abs_path) 62 | if desktop["Desktop Entry"].getboolean("NoDisplay", fallback=False): 63 | desktop["Desktop Entry"]["NoDisplay"] = "false" 64 | with open(abs_path, "w") as f: 65 | desktop.write(f, space_around_delimiters=False) 66 | 67 | def hide(self, appname): 68 | path = self.clean_to_path(appname) 69 | abs_path = os.path.join("/home/phablet/.local/share/applications/", path) 70 | if not os.path.isfile(abs_path): 71 | return 72 | desktop = configparser.ConfigParser() 73 | desktop.optionxform = str # Keep case of keys 74 | desktop.read(abs_path) 75 | if not desktop["Desktop Entry"].getboolean("NoDisplay", fallback=False): 76 | desktop["Desktop Entry"]["NoDisplay"] = "true" 77 | with open(abs_path, "w") as f: 78 | desktop.write(f, space_around_delimiters=False) 79 | 80 | appdrawer = Appdrawer() 81 | 82 | class StopApp: 83 | def create(self): 84 | with open("/home/phablet/.local/share/applications/stop-waydroid.desktop", "w") as f: 85 | f.write("[Desktop Entry]\nType=Application\nName=Waydroid Stop\nExec=waydroid session stop\nIcon=/usr/share/icons/hicolor/512x512/apps/waydroid.png") 86 | def remove(self): 87 | if os.path.isfile("/home/phablet/.local/share/applications/stop-waydroid.desktop"): 88 | os.remove("/home/phablet/.local/share/applications/stop-waydroid.desktop") 89 | stopapp = StopApp() 90 | -------------------------------------------------------------------------------- /src/pam.py: -------------------------------------------------------------------------------- 1 | # Modified 2013-2014 Leon Weber : 2 | # See README.md for changelog 3 | # 4 | # Original author: 5 | # (c) 2007 Chris AtLee 6 | # Licensed under the MIT license: 7 | # http://www.opensource.org/licenses/mit-license.php 8 | """ 9 | PAM module for python 10 | Provides an authenticate function that will allow the caller to authenticate 11 | a user against the Pluggable Authentication Modules (PAM) on the system. 12 | Implemented using ctypes, so no compilation is necessary. 13 | """ 14 | __all__ = ['authenticate'] 15 | 16 | # just a file due to the complexities of installing Python modules on UT 17 | 18 | from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, byref, sizeof 19 | from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int 20 | from ctypes.util import find_library 21 | import sys 22 | 23 | libpam = CDLL(find_library("pam")) 24 | libc = CDLL(find_library("c")) 25 | 26 | calloc = libc.calloc 27 | calloc.restype = c_void_p 28 | calloc.argtypes = [c_uint, c_uint] 29 | 30 | strdup = libc.strdup 31 | strdup.argstypes = [c_char_p] 32 | strdup.restype = POINTER(c_char) # NOT c_char_p !!!! 33 | 34 | # Various constants 35 | PAM_PROMPT_ECHO_OFF = 1 36 | PAM_PROMPT_ECHO_ON = 2 37 | PAM_ERROR_MSG = 3 38 | PAM_TEXT_INFO = 4 39 | 40 | PAM_REINITIALIZE_CRED = 0x0008 # This constant is libpam-specific. 41 | 42 | 43 | class PamHandle(Structure): 44 | """wrapper class for pam_handle_t""" 45 | _fields_ = [ 46 | ("handle", c_void_p) 47 | ] 48 | 49 | def __init__(self): 50 | Structure.__init__(self) 51 | self.handle = 0 52 | 53 | 54 | class PamMessage(Structure): 55 | """wrapper class for pam_message structure""" 56 | _fields_ = [ 57 | ("msg_style", c_int), 58 | ("msg", c_char_p), 59 | ] 60 | 61 | def __repr__(self): 62 | return "" % (self.msg_style, self.msg) 63 | 64 | 65 | class PamResponse(Structure): 66 | """wrapper class for pam_response structure""" 67 | _fields_ = [ 68 | ("resp", c_char_p), 69 | ("resp_retcode", c_int), 70 | ] 71 | 72 | def __repr__(self): 73 | return "" % (self.resp_retcode, self.resp) 74 | 75 | conv_func = CFUNCTYPE(c_int, c_int, POINTER(POINTER(PamMessage)), 76 | POINTER(POINTER(PamResponse)), c_void_p) 77 | 78 | 79 | class PamConv(Structure): 80 | """wrapper class for pam_conv structure""" 81 | _fields_ = [ 82 | ("conv", conv_func), 83 | ("appdata_ptr", c_void_p) 84 | ] 85 | 86 | pam_start = libpam.pam_start 87 | pam_start.restype = c_int 88 | pam_start.argtypes = [c_char_p, c_char_p, POINTER(PamConv), 89 | POINTER(PamHandle)] 90 | 91 | pam_authenticate = libpam.pam_authenticate 92 | pam_authenticate.restype = c_int 93 | pam_authenticate.argtypes = [PamHandle, c_int] 94 | 95 | pam_setcred = libpam.pam_setcred 96 | pam_setcred.restype = c_int 97 | pam_setcred.argtypes = [PamHandle, c_int] 98 | 99 | pam_end = libpam.pam_end 100 | pam_end.restype = c_int 101 | pam_end.argtypes = [PamHandle, c_int] 102 | 103 | 104 | def authenticate(username, password, service='login', encoding='utf-8', 105 | resetcred=True): 106 | """Returns True if the given username and password authenticate for the 107 | given service. Returns False otherwise. 108 | ``username``: the username to authenticate 109 | ``password``: the password in plain text 110 | ``service``: the PAM service to authenticate against. 111 | Defaults to 'login' 112 | The above parameters can be strings or bytes. If they are strings, 113 | they will be encoded using the encoding given by: 114 | ``encoding``: the encoding to use for the above parameters if they 115 | are given as strings. Defaults to 'utf-8' 116 | ``resetcred``: Use the pam_setcred() function to 117 | reinitialize the credentials. 118 | Defaults to 'True'. 119 | """ 120 | 121 | if sys.version_info >= (3,): 122 | if isinstance(username, str): 123 | username = username.encode(encoding) 124 | if isinstance(password, str): 125 | password = password.encode(encoding) 126 | if isinstance(service, str): 127 | service = service.encode(encoding) 128 | 129 | @conv_func 130 | def my_conv(n_messages, messages, p_response, app_data): 131 | """Simple conversation function that responds to any 132 | prompt where the echo is off with the supplied password""" 133 | # Create an array of n_messages response objects 134 | addr = calloc(n_messages, sizeof(PamResponse)) 135 | p_response[0] = cast(addr, POINTER(PamResponse)) 136 | for i in range(n_messages): 137 | if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF: 138 | pw_copy = strdup(password) 139 | p_response.contents[i].resp = cast(pw_copy, c_char_p) 140 | p_response.contents[i].resp_retcode = 0 141 | return 0 142 | 143 | handle = PamHandle() 144 | conv = PamConv(my_conv, 0) 145 | retval = pam_start(service, username, byref(conv), byref(handle)) 146 | 147 | if retval != 0: 148 | # TODO: This is not an authentication error, something 149 | # has gone wrong starting up PAM 150 | return False 151 | 152 | retval = pam_authenticate(handle, 0) 153 | auth_success = (retval == 0) 154 | 155 | # Re-initialize credentials (for Kerberos users, etc) 156 | # Don't check return code of pam_setcred(), it shouldn't matter 157 | # if this fails 158 | if auth_success and resetcred: 159 | retval = pam_setcred(handle, PAM_REINITIALIZE_CRED) 160 | 161 | pam_end(handle, retval) 162 | 163 | return auth_success 164 | 165 | if __name__ == "__main__": 166 | import getpass 167 | print(authenticate(getpass.getuser(), getpass.getpass())) 168 | -------------------------------------------------------------------------------- /src/password_type.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Module for getting user password type from the Accounts service 3 | ''' 4 | 5 | ''' 6 | Copyright (C) 2022 Maciej Sopylo 7 | 8 | This program is free software: you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; version 3. 11 | 12 | waydroidhelper is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | ''' 20 | __all__ = ['get_password_type'] 21 | 22 | import dbus 23 | from enum import Enum 24 | 25 | 26 | class PasswordMode(Enum): 27 | """ 28 | Password mode 29 | """ 30 | REGULAR = 0 31 | """ 32 | Password is set 33 | """ 34 | SET_AT_LOGIN = 1 35 | """ 36 | Password will be set at login 37 | """ 38 | NONE = 2 39 | """ 40 | No password 41 | """ 42 | 43 | 44 | class PasswordDisplayHint(Enum): 45 | """ 46 | Which display hint to use for a prompt 47 | """ 48 | KEYBOARD = 0 49 | """ 50 | Full keyboard 51 | """ 52 | NUMERIC = 1 53 | """ 54 | Just numbers 55 | """ 56 | 57 | 58 | class PasswordType(Enum): 59 | """ 60 | Password type 61 | """ 62 | KEYBOARD = 0 63 | """ 64 | Any text 65 | """ 66 | NUMERIC = 1 67 | """ 68 | Numbers only 69 | """ 70 | NONE = 99 71 | """ 72 | No password 73 | """ 74 | UNKNOWN = 100 75 | """ 76 | Unknown 77 | """ 78 | 79 | 80 | def get_password_type(): 81 | """ 82 | Get a password type from the Accounts service 83 | 84 | Returns 85 | ------- 86 | PasswordType 87 | password type depending on user's settings 88 | """ 89 | bus = dbus.SystemBus() 90 | 91 | try: 92 | accounts = bus.get_object( 93 | 'org.freedesktop.Accounts', '/org/freedesktop/Accounts') 94 | path = accounts.FindUserByName( 95 | 'phablet', dbus_interface='org.freedesktop.Accounts') 96 | 97 | user = bus.get_object('org.freedesktop.Accounts', path) 98 | password_mode = PasswordMode( 99 | user.Get( 100 | 'org.freedesktop.Accounts.User', 101 | 'PasswordMode', 102 | dbus_interface='org.freedesktop.DBus.Properties' 103 | ) 104 | ) 105 | 106 | if password_mode == PasswordMode.NONE: 107 | return PasswordType.NONE 108 | elif password_mode == PasswordMode.SET_AT_LOGIN: 109 | return PasswordType.UNKNOWN 110 | 111 | password_hint = PasswordDisplayHint( 112 | user.Get( 113 | 'com.lomiri.AccountsService.SecurityPrivacy', 114 | 'PasswordDisplayHint', 115 | dbus_interface='org.freedesktop.DBus.Properties' 116 | ) 117 | ) 118 | 119 | if password_hint == PasswordDisplayHint.KEYBOARD: 120 | return PasswordType.KEYBOARD 121 | elif password_hint == PasswordDisplayHint.NUMERIC: 122 | return PasswordType.NUMERIC 123 | finally: 124 | bus.close() 125 | 126 | return PasswordType.UNKNOWN 127 | -------------------------------------------------------------------------------- /src/waydroid-custom-init: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import os 4 | import subprocess 5 | import shutil 6 | sys.path.insert(1, "/usr/lib/waydroid") 7 | import tools as waydroid 8 | 9 | 10 | is_bindmnt_setup = os.path.ismount("/etc/waydroid-extra") 11 | wrapper_script_exists = os.path.exists("/usr/local/bin/waydroid") 12 | 13 | 14 | # make / rw 15 | was_rw_root = False 16 | if not (is_bindmnt_setup or wrapper_script_exists): 17 | root = os.statvfs('/') 18 | if root.f_flag & os.ST_RDONLY: 19 | subprocess.run(["mount", "-o", "remount,rw", "/"]) 20 | else: 21 | was_rw_root = True 22 | 23 | 24 | if not is_bindmnt_setup: 25 | # use userdata as backing for /etc/waydroid-extra 26 | os.makedirs("/userdata/system-data/etc/waydroid-extra", exist_ok=True) 27 | os.makedirs("/etc/waydroid-extra", exist_ok=True) 28 | subprocess.run(["mount", "-o", "bind", "/userdata/system-data/etc/waydroid-extra", "/etc/waydroid-extra"]) 29 | 30 | # make bind-mount survive reboots 31 | with open("/etc/system-image/writable-paths", "a") as f: 32 | f.write("/etc/waydroid-extra auto persistent none none\n") 33 | 34 | 35 | if not wrapper_script_exists: 36 | # workaround having to "double-launch" every UI action 37 | with open("/usr/local/bin/waydroid", "w") as f: 38 | f.write("""\ 39 | #!/bin/sh 40 | case "$1" in 41 | show-full-ui) DOUBLE_LAUNCH=1 ;; 42 | app) 43 | case "$2" in 44 | launch|intent) DOUBLE_LAUNCH=1 ;; 45 | esac 46 | esac 47 | 48 | if [ "$(pgrep -f '/usr/bin/waydroid (show-full-ui|app (launch|intent))')" ]; then 49 | unset DOUBLE_LAUNCH 50 | fi 51 | 52 | [ -z "$DOUBLE_LAUNCH" ] && exec /usr/bin/waydroid "$@" 53 | 54 | /usr/bin/waydroid show-full-ui & 55 | sleep 1 56 | /usr/bin/waydroid show-full-ui 57 | wait 58 | """) 59 | os.chmod("/usr/local/bin/waydroid", 0o755) 60 | 61 | 62 | # we're done messing with rootfs 63 | if not was_rw_root: 64 | subprocess.run(["mount", "-o", "remount,ro", "/"]) 65 | 66 | 67 | # prep for using waydroid download func similar to tools/__init__.py 68 | args = waydroid.helpers.arguments() 69 | args.work = waydroid.config.defaults["work"] 70 | args.sudo_timer = True 71 | args.timeout = 1800 72 | args.log = args.work + "/waydroid.log" 73 | waydroid.helpers.logging.init(args) 74 | 75 | 76 | if os.path.exists("/etc/waydroid-extra/images/system.img"): 77 | print("Already initialized") 78 | sys.exit(0) 79 | 80 | 81 | os.makedirs("/etc/waydroid-extra/images", exist_ok=True) 82 | custom_images_url = "https://sourceforge.net/projects/aleasto-lineageos/files/LineageOS%2020/waydroid_arm64" 83 | for img in ["system", "vendor"]: 84 | downloaded_img = waydroid.helpers.http.download(args, f"{custom_images_url}/{img}.img/download", f"{img}.img", cache=False) 85 | print(f"Validating {img} image") # stub 86 | print("Extracting to /etc/waydroid-extra/images") 87 | shutil.move(downloaded_img, f"/etc/waydroid-extra/images/{img}.img") 88 | 89 | 90 | # start using downloaded custom images 91 | sys.exit(subprocess.run(["waydroid", "init", "-f"]).returncode) 92 | -------------------------------------------------------------------------------- /waydroidhelper.apparmor: -------------------------------------------------------------------------------- 1 | { 2 | "template": "unconfined", 3 | "policy_groups": [], 4 | "policy_version": 20.04 5 | } 6 | -------------------------------------------------------------------------------- /waydroidhelper.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | _Name=Waydroid Helper 3 | Exec=qmlscene %U qml/Main.qml 4 | Icon=assets/logo.png 5 | Terminal=false 6 | Type=Application 7 | X-Ubuntu-Touch=true 8 | --------------------------------------------------------------------------------