├── Evaluation └── README.md ├── Pilot Study ├── Bugs-visual-cue.png ├── GitHub_Bug_Report_all.xlsx ├── Pilot-study.png └── README.md ├── README.md ├── VisionDroid Code ├── AndroidManifest.xml ├── GUIGet │ ├── build.gradle │ ├── build │ │ └── intermediates │ │ │ └── lint-cache │ │ │ ├── maven.google │ │ │ ├── com │ │ │ │ └── android │ │ │ │ │ └── support │ │ │ │ │ ├── constraint │ │ │ │ │ └── group-index.xml │ │ │ │ │ ├── group-index.xml │ │ │ │ │ └── test │ │ │ │ │ ├── espresso │ │ │ │ │ └── group-index.xml │ │ │ │ │ └── group-index.xml │ │ │ └── master-index.xml │ │ │ └── sdk-registry.xml │ │ │ └── sdk-registry.xml │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── local.properties │ └── settings.gradle ├── GptModel.py ├── README.md ├── Scene_1 │ └── annotated_image │ │ └── example.png ├── SparkApi.py ├── SparkModel.py ├── action.py ├── assets │ └── workflow.png ├── cmd.py ├── detector.py ├── extract_activities.py ├── graph.py ├── info.py ├── main.py ├── process_image.py ├── prompt.py └── venv │ ├── Scripts │ ├── __pycache__ │ │ ├── pywin32_postinstall.cpython-39.pyc │ │ ├── pywin32_testall.cpython-39.pyc │ │ └── readelf.cpython-39.pyc │ ├── activate │ ├── activate.bat │ ├── activate.fish │ ├── activate.nu │ ├── activate.ps1 │ ├── activate_this.py │ ├── deactivate.bat │ ├── deactivate.nu │ ├── f2py.exe │ ├── normalizer.exe │ ├── openai.exe │ ├── pip-3.9.exe │ ├── pip.exe │ ├── pip3.9.exe │ ├── pip3.exe │ ├── pydoc.bat │ ├── python.exe │ ├── pythonw.exe │ ├── pywin32_postinstall.py │ ├── pywin32_testall.py │ ├── readelf.py │ ├── tqdm.exe │ ├── uiautomator2.exe │ ├── wheel-3.9.exe │ ├── wheel.exe │ ├── wheel3.9.exe │ └── wheel3.exe │ └── pyvenv.cfg └── workflow.png /Evaluation/README.md: -------------------------------------------------------------------------------- 1 | # Evaluation 2 | 3 | 4 | The apks confirmed or fixed from usefulness evaluation. 5 | 6 | Because the Google Play requires that individuals cannot upload apk without permission. You can download them on Google play through the information in the table. 7 | 8 | **ID** | **App name** | **Category** | **Download** | **Status** 9 | :-: | :-: | :-: | :-: | :-: 10 | 1 | MOMPA | Music | 500M+ | fixed 11 | 2 | Calendar | Tool | 100M+ | fixed 12 | 3 | DigiCal | Tool | 100M+ | fixed 13 | 4 | Expensify | Finance | 100M+ | fixed 14 | 5 | TrackWallet | Finance | 10M+ | confirmed 15 | 6 | Roadie | Travel | 10M+ | confirmed 16 | 7 | ISAY | Commun | 10M+ | fixed 17 | 8 | EasyBudget | Finance | 10M+ | confirmed 18 | 9 | Iplan | Travel | 10M+ | fixed 19 | 10 | MediaFire | Product | 5M+ | confirmed 20 | 11 | SmartGuide | Navig | 500K+ | fixed 21 | 12 | MMDR | Utilities | 500K+ | confirmed 22 | 13 | Sygic | Travel | 500K+ | fixed 23 | 14 | Fair | Health | 500K+ | confirmed 24 | 15 | ClassySha | Tool | 500K+ | fixed 25 | 16 | Linphone | Commun | 50K+ | confirmed 26 | 17 | Rocket | Finance | 50K+ | confirmed 27 | 18 | Monefy | Finance | 50K+ | fixed 28 | 19 | Origin | Finance | 50K+ | confirmed 29 | 30 | -------------------------------------------------------------------------------- /Pilot Study/Bugs-visual-cue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/Pilot Study/Bugs-visual-cue.png -------------------------------------------------------------------------------- /Pilot Study/GitHub_Bug_Report_all.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/Pilot Study/GitHub_Bug_Report_all.xlsx -------------------------------------------------------------------------------- /Pilot Study/Pilot-study.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/Pilot Study/Pilot-study.png -------------------------------------------------------------------------------- /Pilot Study/README.md: -------------------------------------------------------------------------------- 1 | # Pilot Study 2 | 3 | ## Data 4 | 1. GitHub bug reports in `GitHub_Bug_Report_all.xlsx` 5 | 2. Our experimental dataset is collected from one of the largest open-source Android app hosting websites (GitHub), which has a great number of Android bug reports. 6 | 3. We employ the following criteria for app selection: more than 1K downloads in Google Play (popular), a public issue tracking system (traceable), and more than three years of development history (trustworthy). 7 | 8 | ## Bug categories 9 | 1. Intra-page non-crash functional bug denotes these bugs only related to a single GUI page, mainly involving the issues about information display as shown in Figure. 10 | 2. Inter-page non-crash functional bug denotes these bugs related to multiple GUI pages, i.e., involving a testing sequence to trigger it, with details in Figure. 11 | 12 | ![structure](./Pilot-study.png) 13 | 14 | ## Bug Examples 15 | 1. Intra-page non-crash functional bug examples as shown in Figure. 16 | ![structure](./Bugs-visual-cue.png) 17 | 2. Inter-page non-crash functional bug examples as shown in Figure. 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VisionDroid 2 | 3 | VisionDroid, a vison-driven approach for detecting non-crash functional bugs with MLLM. It begins by extracting GUI text information and aligning it with GUI screenshots to form a vision prompt, enabling MLLM to understand GUI context. Second, the function-aware explorer utilizes the MLLM for GUI page exploration. It maintains a testing history comprising both the higher-level catalogue of testing progress and a detailed snapshot of each performed action, to enhance the exploration to be deeper and function-oriented. Third, the logic-aware bug detector breaks the entire exploration sequence into logically cohesive parts that mirror the app’s logical operational sequences. Based on them, the MLLM detects the bugs potentially by checking whether the GUI information changes between page transitions align with the processing logic. 4 | 5 | ## structure 6 | 7 | ![structure](./workflow.png) 8 | 9 | # VisionDroid Source code 10 | 11 | ## How to use 12 | 1. Generate Your API Key: Before we start working with the GPT-4 API, we need to login into OpenAI account and generate our API keys. 13 | `openai.api_key = "XXXXXXX"` 14 | 2. Installing the library: To work with the GPT-4 API, first, we have to install the openai library by running the following command. 15 | 3. Using “ChatCompletion” gpt-4-vision-preview, which is the same model used by GPT-4. 16 | 17 | `from openai import OpenAI` 18 | 19 | `client = OpenAI()` 20 | 21 | `response = client.chat.completions.create(` 22 | 23 | `model="gpt-4-vision-preview",` 24 | 25 | `messages=[` 26 | 27 | `{` 28 | 29 | `"role": "user",` 30 | 31 | `"content": [` 32 | 33 | `{"type": "text", "text": "What’s in this image?"},` 34 | 35 | `{ 36 | 37 | ` "type": "image_url",` 38 | 39 | `"image_url": {` 40 | 41 | ` "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg",' 42 | 43 | `},` 44 | 45 | `},` 46 | 47 | `],` 48 | 49 | `}` 50 | 51 | `],` 52 | 53 | `max_tokens=300,` 54 | 55 | `)` 56 | 57 | 58 | ### Requirements 59 | * Android emulator 60 | * Ubuntu or Windows 61 | * Appium Desktop Client: [link](https://github.com/appium/appium-desktop/releases/tag/v1.22.3-4) 62 | * Python 3.7 63 | * apkutils==0.10.2 64 | * Appium-Python-Client==1.3.0 65 | * Levenshtein==0.18.1 66 | * lxml==4.8.0 67 | * opencv-python==4.5.5.64 68 | * sentence-transformers==1.0.3 69 | * torch==1.6.0 70 | * torchvision==0.7.0 71 | 72 | Use the gpt-4. 73 | 74 | 75 | 76 | 77 | ## This example introduces how to use ChatGPT 78 | 79 | [a-simple-guide-to-gpt-4-api-with-python](https://platform.openai.com/docs/guides/vision) 80 | 81 | 82 | ## LLM Test 83 | 84 | USE GPT-4 App Testing 85 | 86 | ![structure](./workflow.png) 87 | 88 | ### usage 89 | 90 | Find app, change `cmd.py` `PACKAGE = "xxx"` as package name 91 | 92 | Package name in `adb shell dumpsys activity activities | grep mControlTarget=Window` 93 | 94 | Opensource app, find `AndroidManifest.xml` change `/main/app/src/main/AndroidManifest.xml` 95 | 96 | 97 | ### Http 98 | 99 | `GptModel.py` 100 | 101 | ``` 102 | import os 103 | os.environ['HTTP_PROXY'] = 'http://127.0.0.1:10809' 104 | os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:10809' 105 | ``` 106 | 107 | 108 | ### How to set up a proxy for OpenAI's API in Python? 109 | 110 | https://github.com/Onelinerhub/onelinerhub/blob/main//python-openai/how-to-set-up-a-proxy-for-openai-s-api-in-python.md 111 | -------------------------------------------------------------------------------- /VisionDroid Code/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:4.2.0' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | maven { url 'https://jitpack.io' } 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/build/intermediates/lint-cache/maven.google/com/android/support/constraint/group-index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/build/intermediates/lint-cache/maven.google/com/android/support/group-index.xml: -------------------------------------------------------------------------------- 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 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/build/intermediates/lint-cache/maven.google/com/android/support/test/espresso/group-index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/build/intermediates/lint-cache/maven.google/com/android/support/test/group-index.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/build/intermediates/lint-cache/maven.google/master-index.xml: -------------------------------------------------------------------------------- 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 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/build/intermediates/lint-cache/sdk-registry.xml/sdk-registry.xml: -------------------------------------------------------------------------------- 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 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/GUIGet/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/local.properties: -------------------------------------------------------------------------------- 1 | ## This file must *NOT* be checked into Version Control Systems, 2 | # as it contains information specific to your local configuration. 3 | # 4 | # Location of the SDK. This is only used by Gradle. 5 | # For customization when using a Version Control System, please read the 6 | # header note. 7 | #Tue Nov 08 20:01:02 CST 2022 8 | sdk.dir=C\:\\Users\\IcyFeather\\AppData\\Local\\Android\\Sdk 9 | -------------------------------------------------------------------------------- /VisionDroid Code/GUIGet/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /VisionDroid Code/GptModel.py: -------------------------------------------------------------------------------- 1 | from openai import OpenAI 2 | 3 | import os 4 | os.environ['HTTP_PROXY'] = 'http://127.0.0.1:10809' 5 | os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:10809' 6 | 7 | 8 | class GptModel: 9 | client = OpenAI( 10 | api_key="xxxxxx", 11 | ) 12 | 13 | def chat(self, question: str): 14 | chat_completion = self.client.chat.completions.create( 15 | messages=[ 16 | { 17 | "role": "user", 18 | "content": question, 19 | } 20 | ], 21 | model="gpt-xxx", 22 | ) 23 | 24 | return chat_completion.choices[0].message.content 25 | -------------------------------------------------------------------------------- /VisionDroid Code/README.md: -------------------------------------------------------------------------------- 1 | 2 | # VisionDroid Source code 3 | 4 | ## How to use 5 | 1. Generate Your API Key: Before we start working with the GPT-4 API, we need to login into OpenAI account and generate our API keys. 6 | `openai.api_key = "XXXXXXX"` 7 | 2. Installing the library: To work with the GPT-4 API, first, we have to install the openai library by running the following command. 8 | 3. Using “ChatCompletion” gpt-4-vision-preview, which is the same model used by GPT-4. 9 | 10 | `from openai import OpenAI` 11 | 12 | `client = OpenAI()` 13 | 14 | `response = client.chat.completions.create(` 15 | 16 | `model="gpt-4-vision-preview",` 17 | 18 | `messages=[` 19 | 20 | `{` 21 | 22 | `"role": "user",` 23 | 24 | `"content": [` 25 | 26 | `{"type": "text", "text": "What’s in this image?"},` 27 | 28 | `{ 29 | 30 | ` "type": "image_url",` 31 | 32 | `"image_url": {` 33 | 34 | ` "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg",' 35 | 36 | `},` 37 | 38 | `},` 39 | 40 | `],` 41 | 42 | `}` 43 | 44 | `],` 45 | 46 | `max_tokens=300,` 47 | 48 | `)` 49 | 50 | 51 | ### Requirements 52 | * Android emulator 53 | * Ubuntu or Windows 54 | * Appium Desktop Client: [link](https://github.com/appium/appium-desktop/releases/tag/v1.22.3-4) 55 | * Python 3.7 56 | * apkutils==0.10.2 57 | * Appium-Python-Client==1.3.0 58 | * Levenshtein==0.18.1 59 | * lxml==4.8.0 60 | * opencv-python==4.5.5.64 61 | * sentence-transformers==1.0.3 62 | * torch==1.6.0 63 | * torchvision==0.7.0 64 | 65 | Use the gpt-4. 66 | 67 | 68 | ## structure 69 | 70 | ![structure](./assets/workflow.png) 71 | 72 | ## This example introduces how to use ChatGPT 73 | 74 | [a-simple-guide-to-gpt-4-api-with-python](https://platform.openai.com/docs/guides/vision) 75 | 76 | 77 | ## LLM Test 78 | 79 | USE GPT-4 App Testing 80 | 81 | ![structure](./assets/workflow.png) 82 | 83 | ### usage 84 | 85 | Find app, change `cmd.py` `PACKAGE = "xxx"` as package name 86 | 87 | Package name in `adb shell dumpsys activity activities | grep mControlTarget=Window` 88 | 89 | Opensource app, find `AndroidManifest.xml` change `/main/app/src/main/AndroidManifest.xml` 90 | 91 | 92 | ### Http 93 | 94 | `GptModel.py` 95 | 96 | ``` 97 | import os 98 | os.environ['HTTP_PROXY'] = 'http://127.0.0.1:10809' 99 | os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:10809' 100 | ``` 101 | 102 | 103 | ### How to set up a proxy for OpenAI's API in Python? 104 | 105 | https://github.com/Onelinerhub/onelinerhub/blob/main//python-openai/how-to-set-up-a-proxy-for-openai-s-api-in-python.md 106 | -------------------------------------------------------------------------------- /VisionDroid Code/Scene_1/annotated_image/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/Scene_1/annotated_image/example.png -------------------------------------------------------------------------------- /VisionDroid Code/SparkApi.py: -------------------------------------------------------------------------------- 1 | import _thread as thread 2 | import base64 3 | import datetime 4 | import hashlib 5 | import hmac 6 | import json 7 | from urllib.parse import urlparse 8 | import ssl 9 | from datetime import datetime 10 | from time import mktime 11 | from urllib.parse import urlencode 12 | from wsgiref.handlers import format_date_time 13 | 14 | import websocket 15 | 16 | answer = "" 17 | 18 | 19 | class Ws_Param(object): 20 | def __init__(self, APPID, APIKey, APISecret, Spark_url): 21 | self.APPID = APPID 22 | self.APIKey = APIKey 23 | self.APISecret = APISecret 24 | self.host = urlparse(Spark_url).netloc 25 | self.path = urlparse(Spark_url).path 26 | self.Spark_url = Spark_url 27 | 28 | def create_url(self): 29 | now = datetime.now() 30 | date = format_date_time(mktime(now.timetuple())) 31 | 32 | signature_origin = "host: " + self.host + "\n" 33 | signature_origin += "date: " + date + "\n" 34 | signature_origin += "GET " + self.path + " HTTP/1.1" 35 | 36 | signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'), 37 | digestmod=hashlib.sha256).digest() 38 | 39 | signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding='utf-8') 40 | 41 | authorization_origin = f'api_key="{self.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha_base64}"' 42 | 43 | authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8') 44 | 45 | v = { 46 | "authorization": authorization, 47 | "date": date, 48 | "host": self.host 49 | } 50 | url = self.Spark_url + '?' + urlencode(v) 51 | return url 52 | 53 | 54 | def on_error(ws, error): 55 | print("### error:", error) 56 | 57 | 58 | def on_close(ws, one, two): 59 | print(" ") 60 | 61 | 62 | def on_open(ws): 63 | thread.start_new_thread(run, (ws,)) 64 | 65 | 66 | def run(ws, *args): 67 | data = json.dumps(gen_params(appid=ws.appid, domain=ws.domain, question=ws.question)) 68 | ws.send(data) 69 | 70 | 71 | def on_message(ws, message): 72 | # print(message) 73 | data = json.loads(message) 74 | code = data['header']['code'] 75 | if code != 0: 76 | print(f'error: {code}, {data}') 77 | ws.close() 78 | else: 79 | choices = data["payload"]["choices"] 80 | status = choices["status"] 81 | content = choices["text"][0]["content"] 82 | print(content, end="") 83 | global answer 84 | answer += content 85 | # print(1) 86 | if status == 2: 87 | ws.close() 88 | 89 | 90 | def gen_params(appid, domain, question): 91 | """ 92 | appid 93 | """ 94 | data = { 95 | "header": { 96 | "app_id": appid, 97 | "uid": "1234" 98 | }, 99 | "parameter": { 100 | "chat": { 101 | "domain": domain, 102 | "temperature": 0.5, 103 | "max_tokens": 2048 104 | } 105 | }, 106 | "payload": { 107 | "message": { 108 | "text": question 109 | } 110 | } 111 | } 112 | return data 113 | 114 | 115 | def main(appid, api_key, api_secret, Spark_url, domain, question): 116 | wsParam = Ws_Param(appid, api_key, api_secret, Spark_url) 117 | websocket.enableTrace(False) 118 | wsUrl = wsParam.create_url() 119 | ws = websocket.WebSocketApp(wsUrl, on_message=on_message, on_error=on_error, on_close=on_close, on_open=on_open) 120 | ws.appid = appid 121 | ws.question = question 122 | ws.domain = domain 123 | ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE}) 124 | -------------------------------------------------------------------------------- /VisionDroid Code/SparkModel.py: -------------------------------------------------------------------------------- 1 | import SparkApi 2 | 3 | appid = "XXXX" 4 | api_secret = "XXXXX" 5 | api_key = "XXXXX" 6 | 7 | domain = "XXX" 8 | Spark_url = "XXXXX" 9 | 10 | 11 | class Spark: 12 | def __init__(self): 13 | self.text = [] 14 | 15 | def _add_to_text(self, role, content): 16 | json_con = {"role": role, "content": content} 17 | self.text.append(json_con) 18 | return self.text 19 | 20 | @staticmethod 21 | def _get_length(text): 22 | length = 0 23 | for content in text: 24 | temp = content["content"] 25 | temp_len = len(temp) 26 | length += temp_len 27 | return length 28 | 29 | @staticmethod 30 | def _check_len(text): 31 | while Spark._get_length(text) > 8000: 32 | del text[0] 33 | return text 34 | 35 | def chat(self, in_text): 36 | question = Spark._check_len(self._add_to_text("user", in_text)) 37 | SparkApi.answer = "" 38 | SparkApi.main(appid, api_key, api_secret, Spark_url, domain, question) 39 | self._add_to_text("assistant", SparkApi.answer) 40 | return SparkApi.answer 41 | 42 | 43 | if __name__ == '__main__': 44 | s = Spark() 45 | while True: 46 | in_text = input("\n" + "XXX:") 47 | print("XXX:", end="") 48 | out = s.chat(in_text) 49 | -------------------------------------------------------------------------------- /VisionDroid Code/action.py: -------------------------------------------------------------------------------- 1 | from info import Widget 2 | import cmd 3 | 4 | 5 | def click(widget: Widget): 6 | cmd.click_node(widget.node) 7 | 8 | 9 | def back(): 10 | cmd.execute_adb_cmd("input keyevent 4") 11 | 12 | 13 | -------------------------------------------------------------------------------- /VisionDroid Code/assets/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/assets/workflow.png -------------------------------------------------------------------------------- /VisionDroid Code/cmd.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | import subprocess 5 | 6 | import cv2 7 | import uiautomator2 as u2 8 | import xmltodict 9 | 10 | PACKAGE = "xxxx" 11 | 12 | 13 | def layout_to_json(): 14 | d = u2.connect() 15 | page_source = d.dump_hierarchy(compressed=True, pretty=True) 16 | data_dict = xmltodict.parse(page_source) 17 | return data_dict 18 | 19 | 20 | def _get_desc(node): 21 | text = node['@text'] 22 | content = node['@content-desc'] 23 | resource_id = node['@resource-id'] 24 | if text != "": 25 | desc = text 26 | elif content != "": 27 | desc = content 28 | elif resource_id != "": 29 | desc = resource_id.split('/')[-1] 30 | desc = desc.replace('_', ' ') 31 | else: 32 | desc = "" 33 | return desc 34 | 35 | 36 | def _node_check(node, is_farther_clickable): 37 | # package 38 | if node['@package'] != PACKAGE: 39 | return False, False 40 | 41 | # click 42 | is_clickable = node['@clickable'] == "true" or is_farther_clickable 43 | if is_clickable: 44 | node['@clickable'] = "true" 45 | else: 46 | node['@clickable'] = "false" 47 | 48 | # fill 49 | is_editable = ('@class' in node) and (node['@class'] == 'android.widget.EditText' or 50 | node['@class'] == 'android.widget.AutoCompleteTextView') 51 | if is_editable: 52 | node['@editable'] = "true" 53 | else: 54 | node['@editable'] = "false" 55 | 56 | # desc 57 | node["@desc"] = _get_desc(node) 58 | is_described = node["@desc"] != "" 59 | if not is_described: 60 | # desc 61 | return is_clickable, False 62 | 63 | return is_clickable, is_described 64 | 65 | 66 | def get_nodes(hierarchy): 67 | info_nodes = [] 68 | 69 | def dfs(node, is_farther_clickable=False): 70 | if 'node' not in node: 71 | return 72 | node = node['node'] 73 | if type(node) is dict: 74 | is_clickable, is_described = _node_check(node, is_farther_clickable) 75 | if is_described: 76 | info_nodes.append(node) 77 | dfs(node, is_farther_clickable or is_clickable) 78 | else: 79 | for idx in range(len(node)): 80 | is_clickable, is_described = _node_check(node[idx], is_farther_clickable) 81 | if is_described: 82 | info_nodes.append(node[idx]) 83 | dfs(node[idx], is_farther_clickable or is_clickable) 84 | 85 | dfs(hierarchy['hierarchy']) 86 | return info_nodes 87 | 88 | 89 | def get_key_nodes(): 90 | hierarchy = layout_to_json() 91 | # with open("dump.test.json", "r") as f: 92 | # hierarchy = json.load(f) 93 | return get_nodes(hierarchy) 94 | 95 | 96 | def get_bounds(bounds): 97 | a, b, c, d = [int(i) for i in re.findall(r"(\d+)", bounds)] 98 | return a, b, c, d 99 | 100 | 101 | def click_node(target): 102 | pos_x_start, pos_y_start, pos_x_end, pos_y_end = get_bounds(target['@bounds']) 103 | cmd = "adb shell input tap {} {}".format(str((pos_x_start + pos_x_end) // 2), str((pos_y_start + pos_y_end) // 2)) 104 | os.system(cmd) 105 | 106 | 107 | def execute_adb_cmd(cmd): 108 | return subprocess.getoutput("D:\\Android\\android-sdk\\platform-tools\\adb.exe shell " + cmd) 109 | 110 | 111 | if __name__ == '__main__': 112 | k = get_key_nodes() 113 | pass 114 | -------------------------------------------------------------------------------- /VisionDroid Code/detector.py: -------------------------------------------------------------------------------- 1 | import requests, time 2 | import base64, os 3 | import fire 4 | 5 | def getPrompt(): 6 | prompt = ("The common categories of inter-page no-crash bugs include: data operation failure, media control not responding, numerical calculation errors, setting and configuration failures, navigation and linking errors. We provide the example of inter-page bug descriptions: (1) The personal income addition function has failed to add, and the a bug path is as follows: \n" 7 | + "1) Please be a tester to test the [XXX] App. It has the following activities, including [XX, XXX]. The operable components on the image have been marked with red boxes. \n" 8 | + "2) The image shows a test sequence arranged in order from left to right and from top to bottom. Each screenshot is labeled with a different colored bounding box indicating the action of the operation (red as click, blue as input,...). Below the screenshot is the corresponding function name. I have numbered each operable component in order from top to bottom and left to right. The number corresponding to the first component of each row is marked in the upper left corner of the row, and the other numbers in the row can be deduced in sequence.\n" 9 | + "Output Example:\n" 10 | + "1) Does the current page meet expectations? No. If not, the component number that does not meet expectations: (xx). \nIs there a bug on the page? Yes If yes, the component number with the bug: xx.\n" 11 | + "2) Component number requiring next step operation: xx; Text of the component in the screenshot: xx; Operation status: Click; Current page: xxx; Operation expectation: After clicking xx, it is expected to enter the xx page.\n" 12 | + "3) Component number: xx; Text of the component in the screenshot: xx; Operation status: Input; Input content: xx; Current page: xxx; Operation expectation: After clicking xx, it is expected to enter the xx page.\n" 13 | + "Please analyze each step in the test sequence in the image based on the description and examples of the bugs, predict whether the page that transits after each step meets your expectation, use this to determine whether there are any bugs, and point out the bug page." 14 | ) 15 | 16 | return prompt 17 | 18 | 19 | def get_response_from_lm(images): 20 | api_key = "xxxxx" 21 | 22 | def encode_image(image_path): 23 | with open(image_path, "rb") as image_file: 24 | return base64.b64encode(image_file.read()).decode('utf-8') 25 | 26 | prompt = getPrompt() 27 | msg = [{ 28 | "type": "text", 29 | "text": prompt 30 | }] 31 | for image in images: 32 | image_path = image 33 | base64_image = encode_image(image_path) 34 | msg_cur = { 35 | "type": "image_url", 36 | "image_url": { 37 | "url": f"data:image/jpeg;base64,{base64_image}" 38 | } 39 | } 40 | msg.append(msg_cur) 41 | 42 | headers = { 43 | "Content-Type": "application/json", 44 | "Authorization": f"Bearer {api_key}" 45 | } 46 | 47 | payload = { 48 | "model": "gpt-4-xxxx", 49 | "messages": [ 50 | { 51 | "role": "user", 52 | "content": msg 53 | } 54 | ], 55 | "max_tokens": 3000 56 | } 57 | response = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=payload) 58 | outputs = response.json()['choices'][0]['message']['content'].strip() 59 | # print(response.json()) 60 | if 'choices' not in response.json(): 61 | print('Sleep!') 62 | time.sleep(15 * 60) 63 | # print('Model Output: ', outputs) 64 | 65 | return outputs 66 | 67 | def postprocess(content): 68 | import re 69 | c_list = content.split('\n\n') 70 | res_dict = dict() 71 | for item in c_list: 72 | item = item.strip() 73 | if item.startswith('1)'): 74 | if 'yes' in item.lower(): 75 | res_dict['bug'] = -1 76 | else: 77 | numbers = re.findall(r'\d+', item) 78 | bug_num = numbers[-1] 79 | res_dict['bug'] = bug_num 80 | elif item.startswith('2)'): 81 | numbers = re.findall(r'\d+', item) 82 | action_num = numbers[-1] 83 | res_dict['action'] = action_num 84 | elif item.startswith('3)'): 85 | numbers = re.findall(r'\d+', item) 86 | action_num = numbers[-1] 87 | res_dict['next_page'] = action_num 88 | 89 | return res_dict 90 | 91 | def filter(actions, end_num, start_num=1): 92 | new_actions = [] 93 | for item in actions: 94 | if item[1] >= start_num and item[1] <= end_num: 95 | new_actions.append(item) 96 | 97 | return new_actions 98 | 99 | def community(nodes, edges): 100 | import networkx as nx 101 | import community 102 | G = nx.Graph() 103 | sub_dict = dict() 104 | 105 | G.add_nodes_from([1, 2, 3, 4, 5]) 106 | G.add_edges_from([(1, 2), (1, 3), (2, 3), (2, 4), (3, 4), (4, 5)]) 107 | partition = community.best_partition(G) 108 | for k in partition.keys(): 109 | v = partition[k] 110 | if v not in sub_dict.keys(): 111 | sub_dict[v] = [k] 112 | else: 113 | sub_dict[v].append(k) 114 | 115 | return sub_dict 116 | 117 | # dir = 'Scene_1/annotated_image' 118 | # for image in os.listdir(dir): 119 | 120 | def run(): 121 | 122 | dir_path = 'Scene_1/annotated_image' 123 | 124 | memory = [] 125 | memory_action = [] 126 | images = [] 127 | num = 1 128 | for image in os.listdir(dir_path): 129 | image = os.path.join(dir_path, image) 130 | print('Image: ', image) 131 | memory.append(num) 132 | images.append(image) 133 | num += 1 134 | 135 | file_path = image 136 | 137 | output = '' 138 | times = 0 139 | while '\n' not in output and times < 5: 140 | output = get_response_from_lm([file_path]) 141 | times += 1 142 | # print(output) 143 | 144 | res_dict = postprocess(output) 145 | if 'next_page' in res_dict.keys(): 146 | next = int(res_dict['next_page']) 147 | memory_action.append((num, next)) 148 | 149 | print(res_dict) 150 | print('Memory: ', memory) 151 | memory_action = filter(memory_action, num-1) 152 | print('Action: ', memory_action) 153 | sub_dict = community(memory, memory_action) 154 | for k in sub_dict.keys(): 155 | sub_list = sub_dict[k] 156 | sub_img = [] 157 | for num in sub_list: 158 | img_path = images[num-1] 159 | sub_img.append(img_path) 160 | 161 | output = '' 162 | times = 0 163 | while '\n' not in output and times < 5: 164 | output = get_response_from_lm(sub_img) 165 | times += 1 166 | # print(output) 167 | 168 | res_dict = postprocess(output) 169 | if 'next_page' in res_dict.keys(): 170 | next = int(res_dict['next_page']) 171 | memory_action.append((num, next)) 172 | 173 | print(res_dict) 174 | 175 | 176 | if __name__ == '__main__': 177 | fire.Fire(run) 178 | -------------------------------------------------------------------------------- /VisionDroid Code/extract_activities.py: -------------------------------------------------------------------------------- 1 | """ 2 | filepath: app code app/src/main/AndroidManifest.xml 3 | function: xml activities info 4 | """ 5 | import xml.etree.ElementTree as ET 6 | 7 | 8 | def get_all_activities(file_path): 9 | tree = ET.parse(file_path) 10 | root = tree.getroot() 11 | app = root.find('application') 12 | activities = [] 13 | 14 | for e in app.iter('activity'): 15 | if e.find('intent-filter') is not None: 16 | activities.append(e.attrib['{http://schemas.android.com/apk/res/android}name'].split('.')[-1].replace('Activity', '')) 17 | return activities 18 | 19 | 20 | if __name__ == '__main__': 21 | activities = get_all_activities("./AndroidManifest.xml") 22 | pass 23 | -------------------------------------------------------------------------------- /VisionDroid Code/graph.py: -------------------------------------------------------------------------------- 1 | import os 2 | import cv2 3 | from cmd import execute_adb_cmd, get_bounds 4 | import uiautomator2 as u2 5 | 6 | screenshot_dir = 'screenshot' 7 | page_hierarchy_dir = 'page' 8 | os.makedirs(screenshot_dir, exist_ok=True) 9 | 10 | 11 | def clear_directories(): 12 | directories = ['screenshot', 'page'] 13 | files = ['path.txt'] 14 | 15 | for directory in directories: 16 | for root, dirs, files in os.walk(directory): 17 | for file in files: 18 | file_path = os.path.join(root, file) 19 | os.remove(file_path) 20 | 21 | for file in files: 22 | if os.path.exists(file): 23 | os.remove(file) 24 | 25 | os.makedirs(screenshot_dir, exist_ok=True) 26 | os.makedirs(page_hierarchy_dir, exist_ok=True) 27 | 28 | 29 | def save_path(page_info, action_cnt, selected_widget=None): 30 | path_file = None 31 | try: 32 | path_file = open("path.txt", "a+", errors='ignore') 33 | path_file.write(f" {page_info.activity_name}({action_cnt}) ") 34 | if selected_widget is None: 35 | path_file.write(f" -> BACK\n") 36 | else: 37 | path_file.write(f" -> {selected_widget.widget_id}\n") 38 | finally: 39 | if path_file is not None: 40 | path_file.close() 41 | 42 | 43 | def save_screenshot(action_cnt, widget): 44 | path = screenshot_dir + "/" + action_cnt + ".jpg" 45 | execute_adb_cmd("/system/bin/screencap -p /sdcard/screenshot.png") 46 | cmd = f"adb pull /sdcard/screenshot.png {path}" 47 | os.system(cmd) 48 | 49 | # widget 50 | if widget is not None: 51 | image = cv2.imread(f"{path}") 52 | pos_x_start, pos_y_start, pos_x_end, pos_y_end = get_bounds(widget.node['@bounds']) 53 | cv2.rectangle(image, (pos_x_start, pos_y_start), (pos_x_end, pos_y_end), (0, 0, 255), 4) 54 | cv2.imwrite(f"{path}", image) 55 | 56 | 57 | def save_page_hierarchy(action_cnt): 58 | path = page_hierarchy_dir + "/" + action_cnt + ".xml" 59 | d = u2.connect() 60 | with open(path, 'w', encoding='utf-8') as hierarchy_file: 61 | page_source = d.dump_hierarchy(compressed=True, pretty=True) 62 | hierarchy_file.write(page_source) 63 | -------------------------------------------------------------------------------- /VisionDroid Code/info.py: -------------------------------------------------------------------------------- 1 | from dataclasses import field, dataclass 2 | from typing import Dict, Set 3 | 4 | import cmd 5 | import re 6 | 7 | import extract_activities 8 | 9 | XML_PATH = "./AndroidManifest.xml" 10 | THRESHOLD = 0.8 11 | 12 | 13 | @dataclass 14 | class Global: 15 | app_name: str = field( 16 | default=None, metadata={"desc": "app name"} 17 | ) 18 | activities: Set[str] = field( 19 | default_factory=set, metadata={"desc": "activities"} 20 | ) 21 | priority: Dict = field( 22 | default_factory=dict, metadata={"desc": "priority"} 23 | ) 24 | 25 | 26 | def get_global_info(opensource=False): 27 | global_info = Global() 28 | global_info.app_name = cmd.PACKAGE 29 | # Activity 30 | ret = cmd.execute_adb_cmd("dumpsys activity activities | grep mControlTarget=Window") 31 | results = [i for i in re.findall(r"/(.+?)}", ret)] 32 | for act in results: 33 | global_info.activities.add(act) 34 | # clear duplicated elements 35 | global_info.activities = list(set(global_info.activities)) 36 | if opensource: 37 | # Activity 38 | global_info.activities = extract_activities.get_all_activities(XML_PATH) 39 | # priority 40 | for act in global_info.activities: 41 | global_info.priority[act] = "1" 42 | return global_info 43 | 44 | 45 | @dataclass 46 | class Page: 47 | activity_name: str = field( 48 | default=None, 49 | metadata={"desc": "activity"} 50 | ) 51 | layouts: dict = field( 52 | default_factory=dict, 53 | metadata={"desc": "widget"} 54 | ) 55 | visit_times: int = field( 56 | default=1, 57 | metadata={"desc": "visit"} 58 | ) 59 | is_first_page: bool = field( 60 | default=False, 61 | metadata={"desc": "first page"} 62 | ) 63 | 64 | def visit(self): 65 | self.visit_times += 1 66 | 67 | @staticmethod 68 | def sim_ratio(page1, page2): 69 | if page1.activity_name != page2.activity_name: 70 | return 0.0 71 | else: 72 | sim = 0.0 73 | times = 0 74 | for key in page1.layouts: 75 | times += 1 76 | widget1 = page1.layouts[key] 77 | if key not in page2.layouts: 78 | continue 79 | widget2 = page2.layouts[key] 80 | sim += Widget.sim_ratio(widget1, widget2) 81 | for key in page2.layouts: 82 | times += 1 83 | widget2 = page2.layouts[key] 84 | if key not in page1.layouts: 85 | continue 86 | widget1 = page1.layouts[key] 87 | sim += Widget.sim_ratio(widget1, widget2) 88 | if times == 0: 89 | return 0 90 | return sim / times 91 | 92 | 93 | all_pages = [] 94 | 95 | 96 | def page_db_fetch(page_info): 97 | for page in all_pages: 98 | if Page.sim_ratio(page_info, page) > THRESHOLD: 99 | page.visit() 100 | page.layouts = page_info.layouts 101 | return page 102 | all_pages.append(page_info) 103 | return page_info 104 | 105 | 106 | def get_page_info(global_info, prev_page_info): 107 | if global_info is None: 108 | return None 109 | page_info = Page() 110 | ret = cmd.execute_adb_cmd("dumpsys activity activities | grep mControlTarget=Window") 111 | results = [i for i in re.findall(r"/(.+?)}", ret)] 112 | if len(results) == 0: 113 | page_info.activity_name = prev_page_info.activity_name 114 | else: 115 | page_info.activity_name = results[0] 116 | if prev_page_info is None: 117 | page_info.is_first_page = True 118 | info_nodes = cmd.get_key_nodes() 119 | all_desc = set() 120 | for i in info_nodes: 121 | if i["@desc"] in all_desc: 122 | continue 123 | all_desc.add(i["@desc"]) 124 | 125 | widget_info = Widget() 126 | widget_info.widget_id = i["@desc"] 127 | widget_info.widget_type = i["@class"].split()[-1] 128 | if i["@clickable"] == "true": 129 | widget_info.widget_act = "click" 130 | elif i["@editable"] == "true": 131 | widget_info.widget_act = "edit" 132 | widget_info.node = i 133 | page_info.layouts[widget_info.widget_id] = widget_info 134 | return page_info 135 | 136 | 137 | @dataclass 138 | class Widget: 139 | widget_type: str = field( 140 | default=None, 141 | metadata={"desc": "parent"} 142 | ) 143 | widget_id: str = field( 144 | default=None, 145 | metadata={"desc": "ID"} 146 | ) 147 | widget_act: str = field( 148 | default=None, 149 | metadata={"desc": "action"} 150 | ) 151 | node: Dict = field( 152 | default=None, 153 | metadata={"desc": "root"} 154 | ) 155 | 156 | @staticmethod 157 | def sim_ratio(widget1, widget2): 158 | if widget1.widget_id != widget2.widget_id or widget1.widget_type != widget2.widget_type or widget1.widget_act != widget2.widget_act: 159 | return 0.0 160 | else: 161 | return 1.0 162 | 163 | 164 | if __name__ == '__main__': 165 | test = get_global_info() 166 | print(test) -------------------------------------------------------------------------------- /VisionDroid Code/main.py: -------------------------------------------------------------------------------- 1 | from SparkModel import Spark 2 | from GptModel import GptModel 3 | from action import click, back 4 | from prompt import generate_prompt, generate_global_prompt 5 | import info 6 | import time 7 | from graph import save_screenshot, save_path, save_page_hierarchy, clear_directories 8 | 9 | # Use Spark 10 | # s = Spark() 11 | 12 | s = GptModel() 13 | 14 | if __name__ == '__main__': 15 | global_info = info.get_global_info(opensource=False) 16 | prompt_global = generate_global_prompt(global_info) 17 | status = s.chat(prompt_global) 18 | page_info = None 19 | action_cnt = 0 20 | clear_directories() 21 | while True: 22 | page_info = info.get_page_info(global_info, page_info) 23 | save_page_hierarchy(str(action_cnt + 1)) 24 | prompt, opt_key_mapping = generate_prompt(info.page_db_fetch(page_info)) 25 | print(prompt) 26 | output = s.chat(prompt) 27 | print("============") 28 | print(output) 29 | print("============") 30 | # Action: 1 31 | output = output.lower().replace("action:", "").strip() 32 | choice = int(output.split(".")[0]) - 1 33 | 34 | print(choice) 35 | print("============") 36 | if choice == len(opt_key_mapping): 37 | save_screenshot(str(action_cnt + 1), None) 38 | save_path(page_info, str(action_cnt)) 39 | back() 40 | else: 41 | save_screenshot(str(action_cnt + 1), page_info.layouts[opt_key_mapping[choice]]) 42 | save_path(page_info, str(action_cnt), page_info.layouts[opt_key_mapping[choice]]) 43 | click(page_info.layouts[opt_key_mapping[choice]]) 44 | time.sleep(2.0) 45 | action_cnt += 1 46 | 47 | -------------------------------------------------------------------------------- /VisionDroid Code/process_image.py: -------------------------------------------------------------------------------- 1 | from xml.etree import ElementTree as ET 2 | import numpy as np 3 | from matplotlib import pyplot as plt 4 | import cv2 5 | import re 6 | 7 | # Function to recursively extract bounds of enabled elements 8 | def extract_enabled_bounds(node, f_click=False): 9 | bounds = [] 10 | # Check if the node is enabled 11 | if node.attrib.get('clickable') == 'true': 12 | # Extract the 'bounds' attribute 13 | if len(node) == 1: 14 | bound = node[0].attrib.get('bounds') 15 | if bound: 16 | bounds.append(bound) 17 | else: 18 | bound = node.attrib.get('bounds') 19 | if bound: 20 | bounds.append(bound) 21 | 22 | 23 | # Recursively check for any child nodes 24 | for child in node: 25 | bounds.extend(extract_enabled_bounds(child, f_click)) 26 | 27 | return bounds 28 | 29 | def sortBounds(bounds): 30 | sorted_data = sorted(bounds, key=lambda x: (x[1], x[0])) 31 | 32 | return sorted_data 33 | 34 | # Function to draw rectangles on the image based on the bounds 35 | def draw_bounds(image, bounds_list): 36 | num = 0 37 | last_y = -1 38 | for coords in bounds_list: 39 | num += 1 40 | # Extracting the coordinates from the bounds string 41 | # coords = list(map(int, re.findall(r'\d+', bound_str))) 42 | # Since coords are in the form [x1,y1,x2,y2], we unpack them 43 | x1, y1, x2, y2 = coords 44 | # Draw a rectangle on the image 45 | cv2.rectangle(image, (x1, y1), (x2, y2), (0, 0, 255), 2) # Blue color 46 | 47 | if y1 != last_y: 48 | font = cv2.FONT_HERSHEY_SIMPLEX 49 | font_scale = 1 50 | font_color = (255, 0, 0) # Green color 51 | cv2.putText(image, str(num), (x1, y1 + 30), font, font_scale, font_color, 2) 52 | last_y = y1 53 | 54 | return image 55 | 56 | def calculate_area(box): 57 | # bound 58 | return (box[2] - box[0]) * (box[3] - box[1]) 59 | 60 | def is_fully_overlapped(box1, box2): 61 | # box1 and box2 62 | return ((box1[0] <= box2[0] and box1[1] <= box2[1] and box1[2] >= box2[2] and box1[3] >= box2[3]) or 63 | (box2[0] <= box1[0] and box2[1] <= box1[1] and box2[2] >= box1[2] and box2[3] >= box1[3])) 64 | 65 | def calculate_overlap_area(box1, box2): 66 | # overlap 67 | overlap_width = max(0, min(box1[2], box2[2]) - max(box1[0], box2[0])) 68 | overlap_height = max(0, min(box1[3], box2[3]) - max(box1[1], box2[1])) 69 | return overlap_width * overlap_height 70 | 71 | def is_large_overlap(box1, box2, threshold=0.3): 72 | # overlap 73 | overlap_area = calculate_overlap_area(box1, box2) 74 | area1 = calculate_area(box1) 75 | area2 = calculate_area(box2) 76 | 77 | # area 78 | return overlap_area > threshold * area1 or overlap_area > threshold * area2 79 | 80 | def remove_larger_overlap(boxes): 81 | i = 0 82 | while i < len(boxes): 83 | removed = False 84 | for j in range(i + 1, len(boxes)): 85 | if is_fully_overlapped(boxes[i], boxes[j]): 86 | # if is_overlap(boxes[i], boxes[j]): 87 | # overlap 88 | if calculate_area(boxes[i]) > calculate_area(boxes[j]): 89 | del boxes[i] 90 | else: 91 | del boxes[j] 92 | removed = True 93 | break # delete 94 | if not removed: 95 | i += 1 # move 96 | return boxes 97 | 98 | def shift_box(box, dx, dy): 99 | # bound 100 | return [box[0] + dx, box[1] + dy, box[2] + dx, box[3] + dy] 101 | 102 | def resolve_overlap(boxes): 103 | for i in range(len(boxes)): 104 | for j in range(len(boxes)): 105 | if i != j and is_large_overlap(boxes[i], boxes[j]) and is_fully_overlapped(boxes[i], boxes[j]) == False: 106 | # print('OOOKKKK') 107 | # print('box i', boxes[i]) 108 | # print('box j', boxes[j]) 109 | # bound 110 | # right 111 | if boxes[i][0] < boxes[j][0] or boxes[i][1] < boxes[j][1]: 112 | # boxes[j] 113 | shift_x = max(0, boxes[i][2] - boxes[j][0]) 114 | shift_y = max(0, boxes[i][3] - boxes[j][1]) 115 | # boxes[j] = shift_box(boxes[j], shift_x, shift_y) 116 | boxes[j] = shift_box(boxes[j], 0, shift_y) 117 | else: 118 | # boxes[i] 119 | shift_x = max(0, boxes[j][2] - boxes[i][0]) 120 | shift_y = max(0, boxes[j][3] - boxes[i][1]) 121 | # boxes[i] = shift_box(boxes[i], shift_x, shift_y) 122 | boxes[i] = shift_box(boxes[i], 0, shift_y) 123 | # print('box i', boxes[i]) 124 | # print('box j', boxes[j]) 125 | return boxes 126 | 127 | for i in range(6): 128 | # Parse the XML content 129 | print('Num: ', i + 1) 130 | tree = ET.parse(f"Scene_1/hierarchy_files/{i+1}.xml") 131 | root = tree.getroot() 132 | 133 | # Extracting bounds from the root node 134 | enabled_bounds = extract_enabled_bounds(root) 135 | bounds = [] 136 | for bound_str in enabled_bounds: 137 | coords = list(map(int, re.findall(r'\d+', bound_str))) 138 | # Since coords are in the form [x1,y1,x2,y2], we unpack them 139 | # x1, y1, x2, y2 = coords 140 | bounds.append(coords) 141 | print(len(bounds)) 142 | # print(bounds) # Displaying the first 10 for brevity 143 | 144 | # enabled_bounds = remove_larger_overlap(bounds) 145 | enabled_bounds = bounds 146 | enabled_bounds = sortBounds(enabled_bounds) 147 | print(enabled_bounds) 148 | 149 | t = len(enabled_bounds) 150 | for m in range(t): 151 | # print('m: ', m) 152 | if enabled_bounds[0][0] == 0 and enabled_bounds[0][1] == 0: 153 | enabled_bounds = enabled_bounds[1:] 154 | else: 155 | break 156 | print(enabled_bounds) 157 | 158 | enabled_bounds = resolve_overlap(enabled_bounds) 159 | 160 | # print(enabled_bounds) 161 | 162 | # Load the image 163 | image_path = f'Scene_1/screenshots/{i+1}.jpg' 164 | image = cv2.imread(image_path) 165 | 166 | # Drawing the bounds on the image 167 | image_with_bounds = draw_bounds(image, enabled_bounds) 168 | 169 | # Save the image with drawn bounds 170 | output_path = f'Scene_1/annotated_image/{i+1}.jpg' 171 | cv2.imwrite(output_path, image_with_bounds) 172 | 173 | print('ok!') 174 | -------------------------------------------------------------------------------- /VisionDroid Code/prompt.py: -------------------------------------------------------------------------------- 1 | from info import Global, Page 2 | 3 | few_shot = """ 4 | Now that you are an automated testing program for Android software,\ 5 | what you have to do is test the functionality of the software as completely as possible and check for any problems.\ 6 | I will tell you the information of the current program interface.\ 7 | by asking questions, and you will tell me the next step of the test by answering.\ 8 | When you encounter components with similar names,\ 9 | you can look at them as the same category and test one or more of them.\ 10 | When you encounter many operation options, you tend to click on them from smallest to largest,\ 11 | and tend to click on the component with "menu button" in its name.\ 12 | Now that you are an automated testing program for Android software,\ 13 | what you have to do is test the functionality of the software as completely as possible and check for any problems.\ 14 | I will tell you the information of the current program interface by asking questions,\ 15 | and you will tell me the next step of the test by answering. 16 | """ 17 | 18 | 19 | def generate_global_prompt(global_info: Global): 20 | prompt = few_shot 21 | prompt += "We want to test the {} App, which has {} main function pages, namely: ".format(global_info.app_name, 22 | len(global_info.activities)) 23 | 24 | # not open source, so there is no activity priority sequence 25 | 26 | # for activity in global_info.activities: 27 | # prompt += '"{}", '.format(activity) 28 | # prompt = prompt[:-2] 29 | # prompt += '.\nThe recommended test sequence is: ' 30 | 31 | for priority in global_info.priority: 32 | prompt += '"{}", '.format(priority) 33 | 34 | prompt += "If you're clear, please answer \"OK\"." 35 | return prompt 36 | 37 | 38 | def generate_prompt(page_info: Page): 39 | prompt = "" 40 | prompt += 'The function UI page we are currently testing is "{}".\n'.format(page_info.activity_name) 41 | prompt += 'The number of exploration recorded on the current page is {}.\n'.format(page_info.visit_times) 42 | 43 | prompt += "Do not select the same choice as before. Now we can do these:\n" 44 | 45 | opt_key_mapping = [] 46 | opt_id = 1 47 | for widget_key in page_info.layouts.keys(): 48 | if page_info.layouts[widget_key].widget_act != 'click': 49 | continue 50 | prompt += '{}. click "{}"\n'.format(str(opt_id), widget_key) 51 | opt_key_mapping.append(widget_key) 52 | opt_id += 1 53 | if not page_info.is_first_page: 54 | prompt += "{}. return to previous page\n".format(str(opt_id)) 55 | prompt += "Give a number of a choice above only, such as \"1. action\".\n" 56 | return prompt, opt_key_mapping 57 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/__pycache__/pywin32_postinstall.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/__pycache__/pywin32_postinstall.cpython-39.pyc -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/__pycache__/pywin32_testall.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/__pycache__/pywin32_testall.cpython-39.pyc -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/__pycache__/readelf.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/__pycache__/readelf.cpython-39.pyc -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/activate: -------------------------------------------------------------------------------- 1 | # This file must be used with "source bin/activate" *from bash* 2 | # you cannot run it directly 3 | 4 | 5 | if [ "${BASH_SOURCE-}" = "$0" ]; then 6 | echo "You must source this script: \$ source $0" >&2 7 | exit 33 8 | fi 9 | 10 | deactivate () { 11 | unset -f pydoc >/dev/null 2>&1 || true 12 | 13 | # reset old environment variables 14 | # ! [ -z ${VAR+_} ] returns true if VAR is declared at all 15 | if ! [ -z "${_OLD_VIRTUAL_PATH:+_}" ] ; then 16 | PATH="$_OLD_VIRTUAL_PATH" 17 | export PATH 18 | unset _OLD_VIRTUAL_PATH 19 | fi 20 | if ! [ -z "${_OLD_VIRTUAL_PYTHONHOME+_}" ] ; then 21 | PYTHONHOME="$_OLD_VIRTUAL_PYTHONHOME" 22 | export PYTHONHOME 23 | unset _OLD_VIRTUAL_PYTHONHOME 24 | fi 25 | 26 | # The hash command must be called to get it to forget past 27 | # commands. Without forgetting past commands the $PATH changes 28 | # we made may not be respected 29 | hash -r 2>/dev/null 30 | 31 | if ! [ -z "${_OLD_VIRTUAL_PS1+_}" ] ; then 32 | PS1="$_OLD_VIRTUAL_PS1" 33 | export PS1 34 | unset _OLD_VIRTUAL_PS1 35 | fi 36 | 37 | unset VIRTUAL_ENV 38 | if [ ! "${1-}" = "nondestructive" ] ; then 39 | # Self destruct! 40 | unset -f deactivate 41 | fi 42 | } 43 | 44 | # unset irrelevant variables 45 | deactivate nondestructive 46 | 47 | VIRTUAL_ENV='D:\Projects\hintdroid-v1\venv' 48 | if ([ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ]) && $(command -v cygpath &> /dev/null) ; then 49 | VIRTUAL_ENV=$(cygpath -u "$VIRTUAL_ENV") 50 | fi 51 | export VIRTUAL_ENV 52 | 53 | _OLD_VIRTUAL_PATH="$PATH" 54 | PATH="$VIRTUAL_ENV/Scripts:$PATH" 55 | export PATH 56 | 57 | # unset PYTHONHOME if set 58 | if ! [ -z "${PYTHONHOME+_}" ] ; then 59 | _OLD_VIRTUAL_PYTHONHOME="$PYTHONHOME" 60 | unset PYTHONHOME 61 | fi 62 | 63 | if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ] ; then 64 | _OLD_VIRTUAL_PS1="${PS1-}" 65 | if [ "x" != x ] ; then 66 | PS1="() ${PS1-}" 67 | else 68 | PS1="(`basename \"$VIRTUAL_ENV\"`) ${PS1-}" 69 | fi 70 | export PS1 71 | fi 72 | 73 | # Make sure to unalias pydoc if it's already there 74 | alias pydoc 2>/dev/null >/dev/null && unalias pydoc || true 75 | 76 | pydoc () { 77 | python -m pydoc "$@" 78 | } 79 | 80 | # The hash command must be called to get it to forget past 81 | # commands. Without forgetting past commands the $PATH changes 82 | # we made may not be respected 83 | hash -r 2>/dev/null 84 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/activate.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set "VIRTUAL_ENV=D:\Projects\hintdroid-v1\venv" 4 | 5 | if defined _OLD_VIRTUAL_PROMPT ( 6 | set "PROMPT=%_OLD_VIRTUAL_PROMPT%" 7 | ) else ( 8 | if not defined PROMPT ( 9 | set "PROMPT=$P$G" 10 | ) 11 | if not defined VIRTUAL_ENV_DISABLE_PROMPT ( 12 | set "_OLD_VIRTUAL_PROMPT=%PROMPT%" 13 | ) 14 | ) 15 | if not defined VIRTUAL_ENV_DISABLE_PROMPT ( 16 | if "" NEQ "" ( 17 | set "PROMPT=() %PROMPT%" 18 | ) else ( 19 | for %%d in ("%VIRTUAL_ENV%") do set "PROMPT=(%%~nxd) %PROMPT%" 20 | ) 21 | ) 22 | 23 | REM Don't use () to avoid problems with them in %PATH% 24 | if defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME 25 | set "_OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%" 26 | :ENDIFVHOME 27 | 28 | set PYTHONHOME= 29 | 30 | REM if defined _OLD_VIRTUAL_PATH ( 31 | if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH1 32 | set "PATH=%_OLD_VIRTUAL_PATH%" 33 | :ENDIFVPATH1 34 | REM ) else ( 35 | if defined _OLD_VIRTUAL_PATH goto ENDIFVPATH2 36 | set "_OLD_VIRTUAL_PATH=%PATH%" 37 | :ENDIFVPATH2 38 | 39 | set "PATH=%VIRTUAL_ENV%\Scripts;%PATH%" 40 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/activate.fish: -------------------------------------------------------------------------------- 1 | # This file must be used using `source bin/activate.fish` *within a running fish ( http://fishshell.com ) session*. 2 | # Do not run it directly. 3 | 4 | function _bashify_path -d "Converts a fish path to something bash can recognize" 5 | set fishy_path $argv 6 | set bashy_path $fishy_path[1] 7 | for path_part in $fishy_path[2..-1] 8 | set bashy_path "$bashy_path:$path_part" 9 | end 10 | echo $bashy_path 11 | end 12 | 13 | function _fishify_path -d "Converts a bash path to something fish can recognize" 14 | echo $argv | tr ':' '\n' 15 | end 16 | 17 | function deactivate -d 'Exit virtualenv mode and return to the normal environment.' 18 | # reset old environment variables 19 | if test -n "$_OLD_VIRTUAL_PATH" 20 | # https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling 21 | if test (echo $FISH_VERSION | head -c 1) -lt 3 22 | set -gx PATH (_fishify_path "$_OLD_VIRTUAL_PATH") 23 | else 24 | set -gx PATH $_OLD_VIRTUAL_PATH 25 | end 26 | set -e _OLD_VIRTUAL_PATH 27 | end 28 | 29 | if test -n "$_OLD_VIRTUAL_PYTHONHOME" 30 | set -gx PYTHONHOME "$_OLD_VIRTUAL_PYTHONHOME" 31 | set -e _OLD_VIRTUAL_PYTHONHOME 32 | end 33 | 34 | if test -n "$_OLD_FISH_PROMPT_OVERRIDE" 35 | and functions -q _old_fish_prompt 36 | # Set an empty local `$fish_function_path` to allow the removal of `fish_prompt` using `functions -e`. 37 | set -l fish_function_path 38 | 39 | # Erase virtualenv's `fish_prompt` and restore the original. 40 | functions -e fish_prompt 41 | functions -c _old_fish_prompt fish_prompt 42 | functions -e _old_fish_prompt 43 | set -e _OLD_FISH_PROMPT_OVERRIDE 44 | end 45 | 46 | set -e VIRTUAL_ENV 47 | 48 | if test "$argv[1]" != 'nondestructive' 49 | # Self-destruct! 50 | functions -e pydoc 51 | functions -e deactivate 52 | functions -e _bashify_path 53 | functions -e _fishify_path 54 | end 55 | end 56 | 57 | # Unset irrelevant variables. 58 | deactivate nondestructive 59 | 60 | set -gx VIRTUAL_ENV 'D:\Projects\hintdroid-v1\venv' 61 | 62 | # https://github.com/fish-shell/fish-shell/issues/436 altered PATH handling 63 | if test (echo $FISH_VERSION | head -c 1) -lt 3 64 | set -gx _OLD_VIRTUAL_PATH (_bashify_path $PATH) 65 | else 66 | set -gx _OLD_VIRTUAL_PATH $PATH 67 | end 68 | set -gx PATH "$VIRTUAL_ENV"'/Scripts' $PATH 69 | 70 | # Unset `$PYTHONHOME` if set. 71 | if set -q PYTHONHOME 72 | set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME 73 | set -e PYTHONHOME 74 | end 75 | 76 | function pydoc 77 | python -m pydoc $argv 78 | end 79 | 80 | if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" 81 | # Copy the current `fish_prompt` function as `_old_fish_prompt`. 82 | functions -c fish_prompt _old_fish_prompt 83 | 84 | function fish_prompt 85 | # Run the user's prompt first; it might depend on (pipe)status. 86 | set -l prompt (_old_fish_prompt) 87 | 88 | # Prompt override provided? 89 | # If not, just prepend the environment name. 90 | if test -n '' 91 | printf '(%s) ' '' 92 | else 93 | printf '(%s) ' (basename "$VIRTUAL_ENV") 94 | end 95 | 96 | string join -- \n $prompt # handle multi-line prompts 97 | end 98 | 99 | set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" 100 | end 101 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/activate.nu: -------------------------------------------------------------------------------- 1 | # Setting all environment variables for the venv 2 | let path-name = (if ((sys).host.name == "Windows") { "Path" } { "PATH" }) 3 | let virtual-env = "D:\Projects\hintdroid-v1\venv" 4 | let bin = "Scripts" 5 | let path-sep = ";" 6 | 7 | let old-path = ($nu.path | str collect ($path-sep)) 8 | 9 | let venv-path = ([$virtual-env $bin] | path join) 10 | let new-path = ($nu.path | prepend $venv-path | str collect ($path-sep)) 11 | 12 | # environment variables that will be batched loaded to the virtual env 13 | let new-env = ([ 14 | [name, value]; 15 | [$path-name $new-path] 16 | [_OLD_VIRTUAL_PATH $old-path] 17 | [VIRTUAL_ENV $virtual-env] 18 | ]) 19 | 20 | load-env $new-env 21 | 22 | # Creating the new prompt for the session 23 | let virtual_prompt = (if ("" != "") { 24 | "() " 25 | } { 26 | (build-string '(' ($virtual-env | path basename) ') ') 27 | } 28 | ) 29 | 30 | # If there is no default prompt, then only the env is printed in the prompt 31 | let new_prompt = (if ( config | select prompt | empty? ) { 32 | ($"build-string '($virtual_prompt)'") 33 | } { 34 | ($"build-string '($virtual_prompt)' (config get prompt | str find-replace "build-string" "")") 35 | }) 36 | let-env PROMPT_COMMAND = $new_prompt 37 | 38 | # We are using alias as the function definitions because only aliases can be 39 | # removed from the scope 40 | alias pydoc = python -m pydoc 41 | alias deactivate = source "D:\Projects\hintdroid-v1\venv\Scripts\deactivate.nu" 42 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/activate.ps1: -------------------------------------------------------------------------------- 1 | $script:THIS_PATH = $myinvocation.mycommand.path 2 | $script:BASE_DIR = Split-Path (Resolve-Path "$THIS_PATH/..") -Parent 3 | 4 | function global:deactivate([switch] $NonDestructive) { 5 | if (Test-Path variable:_OLD_VIRTUAL_PATH) { 6 | $env:PATH = $variable:_OLD_VIRTUAL_PATH 7 | Remove-Variable "_OLD_VIRTUAL_PATH" -Scope global 8 | } 9 | 10 | if (Test-Path function:_old_virtual_prompt) { 11 | $function:prompt = $function:_old_virtual_prompt 12 | Remove-Item function:\_old_virtual_prompt 13 | } 14 | 15 | if ($env:VIRTUAL_ENV) { 16 | Remove-Item env:VIRTUAL_ENV -ErrorAction SilentlyContinue 17 | } 18 | 19 | if (!$NonDestructive) { 20 | # Self destruct! 21 | Remove-Item function:deactivate 22 | Remove-Item function:pydoc 23 | } 24 | } 25 | 26 | function global:pydoc { 27 | python -m pydoc $args 28 | } 29 | 30 | # unset irrelevant variables 31 | deactivate -nondestructive 32 | 33 | $VIRTUAL_ENV = $BASE_DIR 34 | $env:VIRTUAL_ENV = $VIRTUAL_ENV 35 | 36 | New-Variable -Scope global -Name _OLD_VIRTUAL_PATH -Value $env:PATH 37 | 38 | $env:PATH = "$env:VIRTUAL_ENV/Scripts;" + $env:PATH 39 | if (!$env:VIRTUAL_ENV_DISABLE_PROMPT) { 40 | function global:_old_virtual_prompt { 41 | "" 42 | } 43 | $function:_old_virtual_prompt = $function:prompt 44 | 45 | if ("" -ne "") { 46 | function global:prompt { 47 | # Add the custom prefix to the existing prompt 48 | $previous_prompt_value = & $function:_old_virtual_prompt 49 | ("() " + $previous_prompt_value) 50 | } 51 | } 52 | else { 53 | function global:prompt { 54 | # Add a prefix to the current prompt, but don't discard it. 55 | $previous_prompt_value = & $function:_old_virtual_prompt 56 | $new_prompt_value = "($( Split-Path $env:VIRTUAL_ENV -Leaf )) " 57 | ($new_prompt_value + $previous_prompt_value) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/activate_this.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Activate virtualenv for current interpreter: 3 | 4 | Use exec(open(this_file).read(), {'__file__': this_file}). 5 | 6 | This can be used when you must use an existing Python interpreter, not the virtualenv bin/python. 7 | """ 8 | import os 9 | import site 10 | import sys 11 | 12 | try: 13 | abs_file = os.path.abspath(__file__) 14 | except NameError: 15 | raise AssertionError("You must use exec(open(this_file).read(), {'__file__': this_file}))") 16 | 17 | bin_dir = os.path.dirname(abs_file) 18 | base = bin_dir[: -len("Scripts") - 1] # strip away the bin part from the __file__, plus the path separator 19 | 20 | # prepend bin to PATH (this file is inside the bin directory) 21 | os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep)) 22 | os.environ["VIRTUAL_ENV"] = base # virtual env is right above bin directory 23 | 24 | # add the virtual environments libraries to the host python import mechanism 25 | prev_length = len(sys.path) 26 | for lib in "..\Lib\site-packages".split(os.pathsep): 27 | path = os.path.realpath(os.path.join(bin_dir, lib)) 28 | site.addsitedir(path.decode("utf-8") if "" else path) 29 | sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length] 30 | 31 | sys.real_prefix = sys.prefix 32 | sys.prefix = base 33 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/deactivate.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set VIRTUAL_ENV= 4 | 5 | REM Don't use () to avoid problems with them in %PATH% 6 | if not defined _OLD_VIRTUAL_PROMPT goto ENDIFVPROMPT 7 | set "PROMPT=%_OLD_VIRTUAL_PROMPT%" 8 | set _OLD_VIRTUAL_PROMPT= 9 | :ENDIFVPROMPT 10 | 11 | if not defined _OLD_VIRTUAL_PYTHONHOME goto ENDIFVHOME 12 | set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%" 13 | set _OLD_VIRTUAL_PYTHONHOME= 14 | :ENDIFVHOME 15 | 16 | if not defined _OLD_VIRTUAL_PATH goto ENDIFVPATH 17 | set "PATH=%_OLD_VIRTUAL_PATH%" 18 | set _OLD_VIRTUAL_PATH= 19 | :ENDIFVPATH 20 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/deactivate.nu: -------------------------------------------------------------------------------- 1 | # Setting the old path 2 | let path-name = (if ((sys).host.name == "Windows") { "Path" } { "PATH" }) 3 | let-env $path-name = $nu.env._OLD_VIRTUAL_PATH 4 | 5 | # Unleting the environment variables that were created when activating the env 6 | unlet-env VIRTUAL_ENV 7 | unlet-env _OLD_VIRTUAL_PATH 8 | unlet-env PROMPT_COMMAND 9 | 10 | unalias pydoc 11 | unalias deactivate 12 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/f2py.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/f2py.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/normalizer.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/normalizer.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/openai.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/openai.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/pip-3.9.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/pip-3.9.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/pip.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/pip.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/pip3.9.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/pip3.9.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/pip3.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/pip3.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/pydoc.bat: -------------------------------------------------------------------------------- 1 | python.exe -m pydoc %* 2 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/python.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/python.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/pythonw.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/pythonw.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/pywin32_postinstall.py: -------------------------------------------------------------------------------- 1 | # postinstall script for pywin32 2 | # 3 | # copies PyWinTypesxx.dll and PythonCOMxx.dll into the system directory, 4 | # and creates a pth file 5 | import os 6 | import sys 7 | import glob 8 | import shutil 9 | import sysconfig 10 | 11 | try: 12 | import winreg as winreg 13 | except: 14 | import winreg 15 | 16 | # Send output somewhere so it can be found if necessary... 17 | import tempfile 18 | 19 | tee_f = open(os.path.join(tempfile.gettempdir(), "pywin32_postinstall.log"), "w") 20 | 21 | 22 | class Tee: 23 | def __init__(self, file): 24 | self.f = file 25 | 26 | def write(self, what): 27 | if self.f is not None: 28 | try: 29 | self.f.write(what.replace("\n", "\r\n")) 30 | except IOError: 31 | pass 32 | tee_f.write(what) 33 | 34 | def flush(self): 35 | if self.f is not None: 36 | try: 37 | self.f.flush() 38 | except IOError: 39 | pass 40 | tee_f.flush() 41 | 42 | 43 | # For some unknown reason, when running under bdist_wininst we will start up 44 | # with sys.stdout as None but stderr is hooked up. This work-around allows 45 | # bdist_wininst to see the output we write and display it at the end of 46 | # the install. 47 | if sys.stdout is None: 48 | sys.stdout = sys.stderr 49 | 50 | sys.stderr = Tee(sys.stderr) 51 | sys.stdout = Tee(sys.stdout) 52 | 53 | com_modules = [ 54 | # module_name, class_names 55 | ("win32com.servers.interp", "Interpreter"), 56 | ("win32com.servers.dictionary", "DictionaryPolicy"), 57 | ("win32com.axscript.client.pyscript", "PyScript"), 58 | ] 59 | 60 | # Is this a 'silent' install - ie, avoid all dialogs. 61 | # Different than 'verbose' 62 | silent = 0 63 | 64 | # Verbosity of output messages. 65 | verbose = 1 66 | 67 | root_key_name = "Software\\Python\\PythonCore\\" + sys.winver 68 | 69 | try: 70 | # When this script is run from inside the bdist_wininst installer, 71 | # file_created() and directory_created() are additional builtin 72 | # functions which write lines to Python23\pywin32-install.log. This is 73 | # a list of actions for the uninstaller, the format is inspired by what 74 | # the Wise installer also creates. 75 | file_created 76 | is_bdist_wininst = True 77 | except NameError: 78 | is_bdist_wininst = False # we know what it is not - but not what it is :) 79 | 80 | def file_created(file): 81 | pass 82 | 83 | def directory_created(directory): 84 | pass 85 | 86 | def get_root_hkey(): 87 | try: 88 | winreg.OpenKey( 89 | winreg.HKEY_LOCAL_MACHINE, root_key_name, 0, winreg.KEY_CREATE_SUB_KEY 90 | ) 91 | return winreg.HKEY_LOCAL_MACHINE 92 | except OSError: 93 | # Either not exist, or no permissions to create subkey means 94 | # must be HKCU 95 | return winreg.HKEY_CURRENT_USER 96 | 97 | 98 | try: 99 | create_shortcut 100 | except NameError: 101 | # Create a function with the same signature as create_shortcut provided 102 | # by bdist_wininst 103 | def create_shortcut( 104 | path, description, filename, arguments="", workdir="", iconpath="", iconindex=0 105 | ): 106 | import pythoncom 107 | from win32com.shell import shell 108 | 109 | ilink = pythoncom.CoCreateInstance( 110 | shell.CLSID_ShellLink, 111 | None, 112 | pythoncom.CLSCTX_INPROC_SERVER, 113 | shell.IID_IShellLink, 114 | ) 115 | ilink.SetPath(path) 116 | ilink.SetDescription(description) 117 | if arguments: 118 | ilink.SetArguments(arguments) 119 | if workdir: 120 | ilink.SetWorkingDirectory(workdir) 121 | if iconpath or iconindex: 122 | ilink.SetIconLocation(iconpath, iconindex) 123 | # now save it. 124 | ipf = ilink.QueryInterface(pythoncom.IID_IPersistFile) 125 | ipf.Save(filename, 0) 126 | 127 | # Support the same list of "path names" as bdist_wininst. 128 | def get_special_folder_path(path_name): 129 | from win32com.shell import shell, shellcon 130 | 131 | for maybe in """ 132 | CSIDL_COMMON_STARTMENU CSIDL_STARTMENU CSIDL_COMMON_APPDATA 133 | CSIDL_LOCAL_APPDATA CSIDL_APPDATA CSIDL_COMMON_DESKTOPDIRECTORY 134 | CSIDL_DESKTOPDIRECTORY CSIDL_COMMON_STARTUP CSIDL_STARTUP 135 | CSIDL_COMMON_PROGRAMS CSIDL_PROGRAMS CSIDL_PROGRAM_FILES_COMMON 136 | CSIDL_PROGRAM_FILES CSIDL_FONTS""".split(): 137 | if maybe == path_name: 138 | csidl = getattr(shellcon, maybe) 139 | return shell.SHGetSpecialFolderPath(0, csidl, False) 140 | raise ValueError("%s is an unknown path ID" % (path_name,)) 141 | 142 | 143 | def CopyTo(desc, src, dest): 144 | import win32api, win32con 145 | 146 | while 1: 147 | try: 148 | win32api.CopyFile(src, dest, 0) 149 | return 150 | except win32api.error as details: 151 | if details.winerror == 5: # access denied - user not admin. 152 | raise 153 | if silent: 154 | # Running silent mode - just re-raise the error. 155 | raise 156 | full_desc = ( 157 | "Error %s\n\n" 158 | "If you have any Python applications running, " 159 | "please close them now\nand select 'Retry'\n\n%s" 160 | % (desc, details.strerror) 161 | ) 162 | rc = win32api.MessageBox( 163 | 0, full_desc, "Installation Error", win32con.MB_ABORTRETRYIGNORE 164 | ) 165 | if rc == win32con.IDABORT: 166 | raise 167 | elif rc == win32con.IDIGNORE: 168 | return 169 | # else retry - around we go again. 170 | 171 | 172 | # We need to import win32api to determine the Windows system directory, 173 | # so we can copy our system files there - but importing win32api will 174 | # load the pywintypes.dll already in the system directory preventing us 175 | # from updating them! 176 | # So, we pull the same trick pywintypes.py does, but it loads from 177 | # our pywintypes_system32 directory. 178 | def LoadSystemModule(lib_dir, modname): 179 | # See if this is a debug build. 180 | import importlib.util, importlib.machinery 181 | 182 | suffix = "_d" if "_d.pyd" in importlib.machinery.EXTENSION_SUFFIXES else "" 183 | filename = "%s%d%d%s.dll" % ( 184 | modname, 185 | sys.version_info[0], 186 | sys.version_info[1], 187 | suffix, 188 | ) 189 | filename = os.path.join(lib_dir, "pywin32_system32", filename) 190 | loader = importlib.machinery.ExtensionFileLoader(modname, filename) 191 | spec = importlib.machinery.ModuleSpec(name=modname, loader=loader, origin=filename) 192 | mod = importlib.util.module_from_spec(spec) 193 | spec.loader.exec_module(mod) 194 | 195 | 196 | def SetPyKeyVal(key_name, value_name, value): 197 | root_hkey = get_root_hkey() 198 | root_key = winreg.OpenKey(root_hkey, root_key_name) 199 | try: 200 | my_key = winreg.CreateKey(root_key, key_name) 201 | try: 202 | winreg.SetValueEx(my_key, value_name, 0, winreg.REG_SZ, value) 203 | if verbose: 204 | print("-> %s\\%s[%s]=%r" % (root_key_name, key_name, value_name, value)) 205 | finally: 206 | my_key.Close() 207 | finally: 208 | root_key.Close() 209 | 210 | 211 | def UnsetPyKeyVal(key_name, value_name, delete_key=False): 212 | root_hkey = get_root_hkey() 213 | root_key = winreg.OpenKey(root_hkey, root_key_name) 214 | try: 215 | my_key = winreg.OpenKey(root_key, key_name, 0, winreg.KEY_SET_VALUE) 216 | try: 217 | winreg.DeleteValue(my_key, value_name) 218 | if verbose: 219 | print("-> DELETE %s\\%s[%s]" % (root_key_name, key_name, value_name)) 220 | finally: 221 | my_key.Close() 222 | if delete_key: 223 | winreg.DeleteKey(root_key, key_name) 224 | if verbose: 225 | print("-> DELETE %s\\%s" % (root_key_name, key_name)) 226 | except OSError as why: 227 | winerror = getattr(why, "winerror", why.errno) 228 | if winerror != 2: # file not found 229 | raise 230 | finally: 231 | root_key.Close() 232 | 233 | 234 | def RegisterCOMObjects(register=True): 235 | import win32com.server.register 236 | 237 | if register: 238 | func = win32com.server.register.RegisterClasses 239 | else: 240 | func = win32com.server.register.UnregisterClasses 241 | flags = {} 242 | if not verbose: 243 | flags["quiet"] = 1 244 | for module, klass_name in com_modules: 245 | __import__(module) 246 | mod = sys.modules[module] 247 | flags["finalize_register"] = getattr(mod, "DllRegisterServer", None) 248 | flags["finalize_unregister"] = getattr(mod, "DllUnregisterServer", None) 249 | klass = getattr(mod, klass_name) 250 | func(klass, **flags) 251 | 252 | 253 | def RegisterHelpFile(register=True, lib_dir=None): 254 | if lib_dir is None: 255 | lib_dir = sysconfig.get_paths()["platlib"] 256 | if register: 257 | # Register the .chm help file. 258 | chm_file = os.path.join(lib_dir, "PyWin32.chm") 259 | if os.path.isfile(chm_file): 260 | # This isn't recursive, so if 'Help' doesn't exist, we croak 261 | SetPyKeyVal("Help", None, None) 262 | SetPyKeyVal("Help\\Pythonwin Reference", None, chm_file) 263 | return chm_file 264 | else: 265 | print("NOTE: PyWin32.chm can not be located, so has not " "been registered") 266 | else: 267 | UnsetPyKeyVal("Help\\Pythonwin Reference", None, delete_key=True) 268 | return None 269 | 270 | 271 | def RegisterPythonwin(register=True, lib_dir=None): 272 | """Add (or remove) Pythonwin to context menu for python scripts. 273 | ??? Should probably also add Edit command for pys files also. 274 | Also need to remove these keys on uninstall, but there's no function 275 | like file_created to add registry entries to uninstall log ??? 276 | """ 277 | import os 278 | 279 | if lib_dir is None: 280 | lib_dir = sysconfig.get_paths()["platlib"] 281 | classes_root = get_root_hkey() 282 | ## Installer executable doesn't seem to pass anything to postinstall script indicating if it's a debug build, 283 | pythonwin_exe = os.path.join(lib_dir, "Pythonwin", "Pythonwin.exe") 284 | pythonwin_edit_command = pythonwin_exe + ' -edit "%1"' 285 | 286 | keys_vals = [ 287 | ( 288 | "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\Pythonwin.exe", 289 | "", 290 | pythonwin_exe, 291 | ), 292 | ( 293 | "Software\\Classes\\Python.File\\shell\\Edit with Pythonwin", 294 | "command", 295 | pythonwin_edit_command, 296 | ), 297 | ( 298 | "Software\\Classes\\Python.NoConFile\\shell\\Edit with Pythonwin", 299 | "command", 300 | pythonwin_edit_command, 301 | ), 302 | ] 303 | 304 | try: 305 | if register: 306 | for key, sub_key, val in keys_vals: 307 | ## Since winreg only uses the character Api functions, this can fail if Python 308 | ## is installed to a path containing non-ascii characters 309 | hkey = winreg.CreateKey(classes_root, key) 310 | if sub_key: 311 | hkey = winreg.CreateKey(hkey, sub_key) 312 | winreg.SetValueEx(hkey, None, 0, winreg.REG_SZ, val) 313 | hkey.Close() 314 | else: 315 | for key, sub_key, val in keys_vals: 316 | try: 317 | if sub_key: 318 | hkey = winreg.OpenKey(classes_root, key) 319 | winreg.DeleteKey(hkey, sub_key) 320 | hkey.Close() 321 | winreg.DeleteKey(classes_root, key) 322 | except OSError as why: 323 | winerror = getattr(why, "winerror", why.errno) 324 | if winerror != 2: # file not found 325 | raise 326 | finally: 327 | # tell windows about the change 328 | from win32com.shell import shell, shellcon 329 | 330 | shell.SHChangeNotify( 331 | shellcon.SHCNE_ASSOCCHANGED, shellcon.SHCNF_IDLIST, None, None 332 | ) 333 | 334 | 335 | def get_shortcuts_folder(): 336 | if get_root_hkey() == winreg.HKEY_LOCAL_MACHINE: 337 | try: 338 | fldr = get_special_folder_path("CSIDL_COMMON_PROGRAMS") 339 | except OSError: 340 | # No CSIDL_COMMON_PROGRAMS on this platform 341 | fldr = get_special_folder_path("CSIDL_PROGRAMS") 342 | else: 343 | # non-admin install - always goes in this user's start menu. 344 | fldr = get_special_folder_path("CSIDL_PROGRAMS") 345 | 346 | try: 347 | install_group = winreg.QueryValue( 348 | get_root_hkey(), root_key_name + "\\InstallPath\\InstallGroup" 349 | ) 350 | except OSError: 351 | vi = sys.version_info 352 | install_group = "Python %d.%d" % (vi[0], vi[1]) 353 | return os.path.join(fldr, install_group) 354 | 355 | 356 | # Get the system directory, which may be the Wow64 directory if we are a 32bit 357 | # python on a 64bit OS. 358 | def get_system_dir(): 359 | import win32api # we assume this exists. 360 | 361 | try: 362 | import pythoncom 363 | import win32process 364 | from win32com.shell import shell, shellcon 365 | 366 | try: 367 | if win32process.IsWow64Process(): 368 | return shell.SHGetSpecialFolderPath(0, shellcon.CSIDL_SYSTEMX86) 369 | return shell.SHGetSpecialFolderPath(0, shellcon.CSIDL_SYSTEM) 370 | except (pythoncom.com_error, win32process.error): 371 | return win32api.GetSystemDirectory() 372 | except ImportError: 373 | return win32api.GetSystemDirectory() 374 | 375 | 376 | def fixup_dbi(): 377 | # We used to have a dbi.pyd with our .pyd files, but now have a .py file. 378 | # If the user didn't uninstall, they will find the .pyd which will cause 379 | # problems - so handle that. 380 | import win32api, win32con 381 | 382 | pyd_name = os.path.join(os.path.dirname(win32api.__file__), "dbi.pyd") 383 | pyd_d_name = os.path.join(os.path.dirname(win32api.__file__), "dbi_d.pyd") 384 | py_name = os.path.join(os.path.dirname(win32con.__file__), "dbi.py") 385 | for this_pyd in (pyd_name, pyd_d_name): 386 | this_dest = this_pyd + ".old" 387 | if os.path.isfile(this_pyd) and os.path.isfile(py_name): 388 | try: 389 | if os.path.isfile(this_dest): 390 | print( 391 | "Old dbi '%s' already exists - deleting '%s'" 392 | % (this_dest, this_pyd) 393 | ) 394 | os.remove(this_pyd) 395 | else: 396 | os.rename(this_pyd, this_dest) 397 | print("renamed '%s'->'%s.old'" % (this_pyd, this_pyd)) 398 | file_created(this_pyd + ".old") 399 | except os.error as exc: 400 | print("FAILED to rename '%s': %s" % (this_pyd, exc)) 401 | 402 | 403 | def install(lib_dir): 404 | import traceback 405 | 406 | # The .pth file is now installed as a regular file. 407 | # Create the .pth file in the site-packages dir, and use only relative paths 408 | # We used to write a .pth directly to sys.prefix - clobber it. 409 | if os.path.isfile(os.path.join(sys.prefix, "pywin32.pth")): 410 | os.unlink(os.path.join(sys.prefix, "pywin32.pth")) 411 | # The .pth may be new and therefore not loaded in this session. 412 | # Setup the paths just in case. 413 | for name in "win32 win32\\lib Pythonwin".split(): 414 | sys.path.append(os.path.join(lib_dir, name)) 415 | # It is possible people with old versions installed with still have 416 | # pywintypes and pythoncom registered. We no longer need this, and stale 417 | # entries hurt us. 418 | for name in "pythoncom pywintypes".split(): 419 | keyname = "Software\\Python\\PythonCore\\" + sys.winver + "\\Modules\\" + name 420 | for root in winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER: 421 | try: 422 | winreg.DeleteKey(root, keyname + "\\Debug") 423 | except WindowsError: 424 | pass 425 | try: 426 | winreg.DeleteKey(root, keyname) 427 | except WindowsError: 428 | pass 429 | LoadSystemModule(lib_dir, "pywintypes") 430 | LoadSystemModule(lib_dir, "pythoncom") 431 | import win32api 432 | 433 | # and now we can get the system directory: 434 | files = glob.glob(os.path.join(lib_dir, "pywin32_system32\\*.*")) 435 | if not files: 436 | raise RuntimeError("No system files to copy!!") 437 | # Try the system32 directory first - if that fails due to "access denied", 438 | # it implies a non-admin user, and we use sys.prefix 439 | for dest_dir in [get_system_dir(), sys.prefix]: 440 | # and copy some files over there 441 | worked = 0 442 | try: 443 | for fname in files: 444 | base = os.path.basename(fname) 445 | dst = os.path.join(dest_dir, base) 446 | CopyTo("installing %s" % base, fname, dst) 447 | if verbose: 448 | print("Copied %s to %s" % (base, dst)) 449 | # Register the files with the uninstaller 450 | file_created(dst) 451 | worked = 1 452 | # Nuke any other versions that may exist - having 453 | # duplicates causes major headaches. 454 | bad_dest_dirs = [ 455 | os.path.join(sys.prefix, "Library\\bin"), 456 | os.path.join(sys.prefix, "Lib\\site-packages\\win32"), 457 | ] 458 | if dest_dir != sys.prefix: 459 | bad_dest_dirs.append(sys.prefix) 460 | for bad_dest_dir in bad_dest_dirs: 461 | bad_fname = os.path.join(bad_dest_dir, base) 462 | if os.path.exists(bad_fname): 463 | # let exceptions go here - delete must succeed 464 | os.unlink(bad_fname) 465 | if worked: 466 | break 467 | except win32api.error as details: 468 | if details.winerror == 5: 469 | # access denied - user not admin - try sys.prefix dir, 470 | # but first check that a version doesn't already exist 471 | # in that place - otherwise that one will still get used! 472 | if os.path.exists(dst): 473 | msg = ( 474 | "The file '%s' exists, but can not be replaced " 475 | "due to insufficient permissions. You must " 476 | "reinstall this software as an Administrator" % dst 477 | ) 478 | print(msg) 479 | raise RuntimeError(msg) 480 | continue 481 | raise 482 | else: 483 | raise RuntimeError( 484 | "You don't have enough permissions to install the system files" 485 | ) 486 | 487 | # Pythonwin 'compiles' config files - record them for uninstall. 488 | pywin_dir = os.path.join(lib_dir, "Pythonwin", "pywin") 489 | for fname in glob.glob(os.path.join(pywin_dir, "*.cfg")): 490 | file_created(fname[:-1] + "c") # .cfg->.cfc 491 | 492 | # Register our demo COM objects. 493 | try: 494 | try: 495 | RegisterCOMObjects() 496 | except win32api.error as details: 497 | if details.winerror != 5: # ERROR_ACCESS_DENIED 498 | raise 499 | print("You do not have the permissions to install COM objects.") 500 | print("The sample COM objects were not registered.") 501 | except Exception: 502 | print("FAILED to register the Python COM objects") 503 | traceback.print_exc() 504 | 505 | # There may be no main Python key in HKCU if, eg, an admin installed 506 | # python itself. 507 | winreg.CreateKey(get_root_hkey(), root_key_name) 508 | 509 | chm_file = None 510 | try: 511 | chm_file = RegisterHelpFile(True, lib_dir) 512 | except Exception: 513 | print("Failed to register help file") 514 | traceback.print_exc() 515 | else: 516 | if verbose: 517 | print("Registered help file") 518 | 519 | # misc other fixups. 520 | fixup_dbi() 521 | 522 | # Register Pythonwin in context menu 523 | try: 524 | RegisterPythonwin(True, lib_dir) 525 | except Exception: 526 | print("Failed to register pythonwin as editor") 527 | traceback.print_exc() 528 | else: 529 | if verbose: 530 | print("Pythonwin has been registered in context menu") 531 | 532 | # Create the win32com\gen_py directory. 533 | make_dir = os.path.join(lib_dir, "win32com", "gen_py") 534 | if not os.path.isdir(make_dir): 535 | if verbose: 536 | print("Creating directory %s" % (make_dir,)) 537 | directory_created(make_dir) 538 | os.mkdir(make_dir) 539 | 540 | try: 541 | # create shortcuts 542 | # CSIDL_COMMON_PROGRAMS only available works on NT/2000/XP, and 543 | # will fail there if the user has no admin rights. 544 | fldr = get_shortcuts_folder() 545 | # If the group doesn't exist, then we don't make shortcuts - its 546 | # possible that this isn't a "normal" install. 547 | if os.path.isdir(fldr): 548 | dst = os.path.join(fldr, "PythonWin.lnk") 549 | create_shortcut( 550 | os.path.join(lib_dir, "Pythonwin\\Pythonwin.exe"), 551 | "The Pythonwin IDE", 552 | dst, 553 | "", 554 | sys.prefix, 555 | ) 556 | file_created(dst) 557 | if verbose: 558 | print("Shortcut for Pythonwin created") 559 | # And the docs. 560 | if chm_file: 561 | dst = os.path.join(fldr, "Python for Windows Documentation.lnk") 562 | doc = "Documentation for the PyWin32 extensions" 563 | create_shortcut(chm_file, doc, dst) 564 | file_created(dst) 565 | if verbose: 566 | print("Shortcut to documentation created") 567 | else: 568 | if verbose: 569 | print("Can't install shortcuts - %r is not a folder" % (fldr,)) 570 | except Exception as details: 571 | print(details) 572 | 573 | # importing win32com.client ensures the gen_py dir created - not strictly 574 | # necessary to do now, but this makes the installation "complete" 575 | try: 576 | import win32com.client # noqa 577 | except ImportError: 578 | # Don't let this error sound fatal 579 | pass 580 | print("The pywin32 extensions were successfully installed.") 581 | 582 | if is_bdist_wininst: 583 | # Open a web page with info about the .exe installers being deprecated. 584 | import webbrowser 585 | 586 | try: 587 | webbrowser.open("https://mhammond.github.io/pywin32_installers.html") 588 | except webbrowser.Error: 589 | print("Please visit https://mhammond.github.io/pywin32_installers.html") 590 | 591 | 592 | def uninstall(lib_dir): 593 | # First ensure our system modules are loaded from pywin32_system, so 594 | # we can remove the ones we copied... 595 | LoadSystemModule(lib_dir, "pywintypes") 596 | LoadSystemModule(lib_dir, "pythoncom") 597 | 598 | try: 599 | RegisterCOMObjects(False) 600 | except Exception as why: 601 | print("Failed to unregister COM objects: %s" % (why,)) 602 | 603 | try: 604 | RegisterHelpFile(False, lib_dir) 605 | except Exception as why: 606 | print("Failed to unregister help file: %s" % (why,)) 607 | else: 608 | if verbose: 609 | print("Unregistered help file") 610 | 611 | try: 612 | RegisterPythonwin(False, lib_dir) 613 | except Exception as why: 614 | print("Failed to unregister Pythonwin: %s" % (why,)) 615 | else: 616 | if verbose: 617 | print("Unregistered Pythonwin") 618 | 619 | try: 620 | # remove gen_py directory. 621 | gen_dir = os.path.join(lib_dir, "win32com", "gen_py") 622 | if os.path.isdir(gen_dir): 623 | shutil.rmtree(gen_dir) 624 | if verbose: 625 | print("Removed directory %s" % (gen_dir,)) 626 | 627 | # Remove pythonwin compiled "config" files. 628 | pywin_dir = os.path.join(lib_dir, "Pythonwin", "pywin") 629 | for fname in glob.glob(os.path.join(pywin_dir, "*.cfc")): 630 | os.remove(fname) 631 | 632 | # The dbi.pyd.old files we may have created. 633 | try: 634 | os.remove(os.path.join(lib_dir, "win32", "dbi.pyd.old")) 635 | except os.error: 636 | pass 637 | try: 638 | os.remove(os.path.join(lib_dir, "win32", "dbi_d.pyd.old")) 639 | except os.error: 640 | pass 641 | 642 | except Exception as why: 643 | print("Failed to remove misc files: %s" % (why,)) 644 | 645 | try: 646 | fldr = get_shortcuts_folder() 647 | for link in ("PythonWin.lnk", "Python for Windows Documentation.lnk"): 648 | fqlink = os.path.join(fldr, link) 649 | if os.path.isfile(fqlink): 650 | os.remove(fqlink) 651 | if verbose: 652 | print("Removed %s" % (link,)) 653 | except Exception as why: 654 | print("Failed to remove shortcuts: %s" % (why,)) 655 | # Now remove the system32 files. 656 | files = glob.glob(os.path.join(lib_dir, "pywin32_system32\\*.*")) 657 | # Try the system32 directory first - if that fails due to "access denied", 658 | # it implies a non-admin user, and we use sys.prefix 659 | try: 660 | for dest_dir in [get_system_dir(), sys.prefix]: 661 | # and copy some files over there 662 | worked = 0 663 | for fname in files: 664 | base = os.path.basename(fname) 665 | dst = os.path.join(dest_dir, base) 666 | if os.path.isfile(dst): 667 | try: 668 | os.remove(dst) 669 | worked = 1 670 | if verbose: 671 | print("Removed file %s" % (dst)) 672 | except Exception: 673 | print("FAILED to remove %s" % (dst,)) 674 | if worked: 675 | break 676 | except Exception as why: 677 | print("FAILED to remove system files: %s" % (why,)) 678 | 679 | 680 | # NOTE: If this script is run from inside the bdist_wininst created 681 | # binary installer or uninstaller, the command line args are either 682 | # '-install' or '-remove'. 683 | 684 | # Important: From inside the binary installer this script MUST NOT 685 | # call sys.exit() or raise SystemExit, otherwise not only this script 686 | # but also the installer will terminate! (Is there a way to prevent 687 | # this from the bdist_wininst C code?) 688 | 689 | 690 | def verify_destination(location): 691 | if not os.path.isdir(location): 692 | raise argparse.ArgumentTypeError('Path "{}" does not exist!'.format(location)) 693 | return location 694 | 695 | 696 | def main(): 697 | import argparse 698 | 699 | parser = argparse.ArgumentParser( 700 | formatter_class=argparse.RawDescriptionHelpFormatter, 701 | description="""A post-install script for the pywin32 extensions. 702 | 703 | * Typical usage: 704 | 705 | > python pywin32_postinstall.py -install 706 | 707 | If you installed pywin32 via a .exe installer, this should be run 708 | automatically after installation, but if it fails you can run it again. 709 | 710 | If you installed pywin32 via PIP, you almost certainly need to run this to 711 | setup the environment correctly. 712 | 713 | Execute with script with a '-install' parameter, to ensure the environment 714 | is setup correctly. 715 | """, 716 | ) 717 | parser.add_argument( 718 | "-install", 719 | default=False, 720 | action="store_true", 721 | help="Configure the Python environment correctly for pywin32.", 722 | ) 723 | parser.add_argument( 724 | "-remove", 725 | default=False, 726 | action="store_true", 727 | help="Try and remove everything that was installed or copied.", 728 | ) 729 | parser.add_argument( 730 | "-wait", 731 | type=int, 732 | help="Wait for the specified process to terminate before starting.", 733 | ) 734 | parser.add_argument( 735 | "-silent", 736 | default=False, 737 | action="store_true", 738 | help='Don\'t display the "Abort/Retry/Ignore" dialog for files in use.', 739 | ) 740 | parser.add_argument( 741 | "-quiet", 742 | default=False, 743 | action="store_true", 744 | help="Don't display progress messages.", 745 | ) 746 | parser.add_argument( 747 | "-destination", 748 | default=sysconfig.get_paths()["platlib"], 749 | type=verify_destination, 750 | help="Location of the PyWin32 installation", 751 | ) 752 | 753 | args = parser.parse_args() 754 | 755 | if not args.quiet: 756 | print("Parsed arguments are: {}".format(args)) 757 | 758 | if not args.install ^ args.remove: 759 | parser.error("You need to either choose to -install or -remove!") 760 | 761 | if args.wait is not None: 762 | try: 763 | os.waitpid(args.wait, 0) 764 | except os.error: 765 | # child already dead 766 | pass 767 | 768 | silent = args.silent 769 | verbose = not args.quiet 770 | 771 | if args.install: 772 | install(args.destination) 773 | 774 | if args.remove: 775 | if not is_bdist_wininst: 776 | uninstall(args.destination) 777 | 778 | 779 | if __name__ == "__main__": 780 | main() 781 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/pywin32_testall.py: -------------------------------------------------------------------------------- 1 | """A test runner for pywin32""" 2 | import sys 3 | import os 4 | import site 5 | import subprocess 6 | 7 | # locate the dirs based on where this script is - it may be either in the 8 | # source tree, or in an installed Python 'Scripts' tree. 9 | this_dir = os.path.dirname(__file__) 10 | site_packages = [ 11 | site.getusersitepackages(), 12 | ] + site.getsitepackages() 13 | 14 | failures = [] 15 | 16 | # Run a test using subprocess and wait for the result. 17 | # If we get an returncode != 0, we know that there was an error, but we don't 18 | # abort immediately - we run as many tests as we can. 19 | def run_test(script, cmdline_extras): 20 | dirname, scriptname = os.path.split(script) 21 | # some tests prefer to be run from their directory. 22 | cmd = [sys.executable, "-u", scriptname] + cmdline_extras 23 | result = subprocess.run(cmd, check=False, cwd=dirname) 24 | print("*** Test script '%s' exited with %s" % (script, result.returncode)) 25 | sys.stdout.flush() 26 | if result.returncode: 27 | failures.append(script) 28 | 29 | 30 | def find_and_run(possible_locations, extras): 31 | for maybe in possible_locations: 32 | if os.path.isfile(maybe): 33 | run_test(maybe, extras) 34 | break 35 | else: 36 | raise RuntimeError( 37 | "Failed to locate a test script in one of %s" % possible_locations 38 | ) 39 | 40 | 41 | def main(): 42 | import argparse 43 | 44 | code_directories = [this_dir] + site_packages 45 | 46 | parser = argparse.ArgumentParser( 47 | description="A script to trigger tests in all subprojects of PyWin32." 48 | ) 49 | parser.add_argument( 50 | "-no-user-interaction", 51 | default=False, 52 | action="store_true", 53 | help="(This is now the default - use `-user-interaction` to include them)", 54 | ) 55 | 56 | parser.add_argument( 57 | "-user-interaction", 58 | action="store_true", 59 | help="Include tests which require user interaction", 60 | ) 61 | 62 | parser.add_argument( 63 | "-skip-adodbapi", 64 | default=False, 65 | action="store_true", 66 | help="Skip the adodbapi tests; useful for CI where there's no provider", 67 | ) 68 | 69 | args, remains = parser.parse_known_args() 70 | 71 | # win32 72 | maybes = [ 73 | os.path.join(directory, "win32", "test", "testall.py") 74 | for directory in code_directories 75 | ] 76 | extras = [] 77 | if args.user_interaction: 78 | extras += "-user-interaction" 79 | extras.extend(remains) 80 | 81 | find_and_run(maybes, extras) 82 | 83 | # win32com 84 | maybes = [ 85 | os.path.join(directory, "win32com", "test", "testall.py") 86 | for directory in [ 87 | os.path.join(this_dir, "com"), 88 | ] 89 | + site_packages 90 | ] 91 | extras = remains + ["1"] # only run "level 1" tests in CI 92 | find_and_run(maybes, extras) 93 | 94 | # adodbapi 95 | if not args.skip_adodbapi: 96 | maybes = [ 97 | os.path.join(directory, "adodbapi", "test", "adodbapitest.py") 98 | for directory in code_directories 99 | ] 100 | find_and_run(maybes, remains) 101 | # This script has a hard-coded sql server name in it, (and markh typically 102 | # doesn't have a different server to test on) but there is now supposed to be a server out there on the Internet 103 | # just to run these tests, so try it... 104 | maybes = [ 105 | os.path.join(directory, "adodbapi", "test", "test_adodbapi_dbapi20.py") 106 | for directory in code_directories 107 | ] 108 | find_and_run(maybes, remains) 109 | 110 | if failures: 111 | print("The following scripts failed") 112 | for failure in failures: 113 | print(">", failure) 114 | sys.exit(1) 115 | print("All tests passed \o/") 116 | 117 | 118 | if __name__ == "__main__": 119 | main() 120 | -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/tqdm.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/tqdm.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/uiautomator2.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/uiautomator2.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/wheel-3.9.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/wheel-3.9.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/wheel.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/wheel.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/wheel3.9.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/wheel3.9.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/Scripts/wheel3.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/VisionDroid Code/venv/Scripts/wheel3.exe -------------------------------------------------------------------------------- /VisionDroid Code/venv/pyvenv.cfg: -------------------------------------------------------------------------------- 1 | home = C:\Users\IcyFeather\AppData\Local\Programs\Python\Python39 2 | implementation = CPython 3 | version_info = 3.9.13.final.0 4 | virtualenv = 20.13.0 5 | include-system-site-packages = false 6 | base-prefix = C:\Users\IcyFeather\AppData\Local\Programs\Python\Python39 7 | base-exec-prefix = C:\Users\IcyFeather\AppData\Local\Programs\Python\Python39 8 | base-executable = C:\Users\IcyFeather\AppData\Local\Programs\Python\Python39\python.exe 9 | -------------------------------------------------------------------------------- /workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testtestA6/VisionDroid/3cc7dc097306ce4c8495fdcd09b04e04c3a7c98a/workflow.png --------------------------------------------------------------------------------