apparser 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apparser-1.0.0/LICENSE.md +28 -0
- apparser-1.0.0/PKG-INFO +89 -0
- apparser-1.0.0/README.md +46 -0
- apparser-1.0.0/apparser/__init__.py +1 -0
- apparser-1.0.0/apparser/core/__init__.py +4 -0
- apparser-1.0.0/apparser/core/app.py +95 -0
- apparser-1.0.0/apparser/core/ui/__init__.py +8 -0
- apparser-1.0.0/apparser/core/ui/base.py +51 -0
- apparser-1.0.0/apparser/core/ui/coordinates.py +118 -0
- apparser-1.0.0/apparser/core/ui/desktop.py +72 -0
- apparser-1.0.0/apparser/core/ui/window.py +75 -0
- apparser-1.0.0/apparser/core/ui/window_by_display.py +90 -0
- apparser-1.0.0/apparser/cv/__init__.py +6 -0
- apparser-1.0.0/apparser/cv/events/__init__.py +8 -0
- apparser-1.0.0/apparser/cv/events/base.py +14 -0
- apparser-1.0.0/apparser/cv/events/detected.py +13 -0
- apparser-1.0.0/apparser/cv/events/moved.py +13 -0
- apparser-1.0.0/apparser/cv/events/resized.py +13 -0
- apparser-1.0.0/apparser/cv/events/undetected.py +13 -0
- apparser-1.0.0/apparser/cv/handlers/__init__.py +5 -0
- apparser-1.0.0/apparser/cv/handlers/base.py +30 -0
- apparser-1.0.0/apparser/cv/handlers/default.py +74 -0
- apparser-1.0.0/apparser/cv/models/__init__.py +4 -0
- apparser-1.0.0/apparser/cv/models/data.py +33 -0
- apparser-1.0.0/apparser/cv/models/handler.py +15 -0
- apparser-1.0.0/apparser/cv/processes/__init__.py +5 -0
- apparser-1.0.0/apparser/cv/processes/base.py +31 -0
- apparser-1.0.0/apparser/cv/processes/default.py +64 -0
- apparser-1.0.0/apparser/cv/readers/__init__.py +4 -0
- apparser-1.0.0/apparser/cv/readers/base.py +19 -0
- apparser-1.0.0/apparser/cv/readers/yolo.py +69 -0
- apparser-1.0.0/apparser/cv/utils/__init__.py +3 -0
- apparser-1.0.0/apparser/cv/utils/changes_checker.py +84 -0
- apparser-1.0.0/apparser/exceptions/__init__.py +10 -0
- apparser-1.0.0/apparser/exceptions/debug.py +14 -0
- apparser-1.0.0/apparser/exceptions/instruction_not_found.py +37 -0
- apparser-1.0.0/apparser/exceptions/text_not_found.py +18 -0
- apparser-1.0.0/apparser/exceptions/timeout.py +13 -0
- apparser-1.0.0/apparser/exceptions/window_action_with_desktop.py +6 -0
- apparser-1.0.0/apparser/geometry/__init__.py +9 -0
- apparser-1.0.0/apparser/geometry/distance.py +21 -0
- apparser-1.0.0/apparser/geometry/relatively_point.py +45 -0
- apparser-1.0.0/apparser/instructions/__init__.py +4 -0
- apparser-1.0.0/apparser/instructions/base.py +24 -0
- apparser-1.0.0/apparser/instructions/debuggers/__init__.py +4 -0
- apparser-1.0.0/apparser/instructions/debuggers/base.py +23 -0
- apparser-1.0.0/apparser/instructions/debuggers/default.py +44 -0
- apparser-1.0.0/apparser/instructions/default/__init__.py +23 -0
- apparser-1.0.0/apparser/instructions/default/click.py +73 -0
- apparser-1.0.0/apparser/instructions/default/play_audio.py +88 -0
- apparser-1.0.0/apparser/instructions/default/play_audio_file.py +77 -0
- apparser-1.0.0/apparser/instructions/default/press.py +99 -0
- apparser-1.0.0/apparser/instructions/default/say_audio.py +92 -0
- apparser-1.0.0/apparser/instructions/default/say_audio_file.py +77 -0
- apparser-1.0.0/apparser/instructions/default/sleep.py +26 -0
- apparser-1.0.0/apparser/instructions/default/write_text.py +36 -0
- apparser-1.0.0/apparser/instructions/ocr/__init__.py +16 -0
- apparser-1.0.0/apparser/instructions/ocr/base.py +36 -0
- apparser-1.0.0/apparser/instructions/ocr/click_on_text.py +52 -0
- apparser-1.0.0/apparser/instructions/ocr/move_to_text.py +71 -0
- apparser-1.0.0/apparser/instructions/ocr/plot_text.py +81 -0
- apparser-1.0.0/apparser/instructions/ocr/print_all_text.py +33 -0
- apparser-1.0.0/apparser/instructions/ocr/text_getter.py +92 -0
- apparser-1.0.0/apparser/instructions/ocr/wait_text.py +69 -0
- apparser-1.0.0/apparser/instructions/speak/__init__.py +5 -0
- apparser-1.0.0/apparser/instructions/speak/base.py +34 -0
- apparser-1.0.0/apparser/instructions/speak/play_text.py +52 -0
- apparser-1.0.0/apparser/instructions/speak/say_text.py +52 -0
- apparser-1.0.0/apparser/instructions/ui/__init__.py +15 -0
- apparser-1.0.0/apparser/instructions/ui/algorithms/__init__.py +16 -0
- apparser-1.0.0/apparser/instructions/ui/algorithms/algorithm.py +60 -0
- apparser-1.0.0/apparser/instructions/ui/algorithms/base.py +47 -0
- apparser-1.0.0/apparser/instructions/ui/algorithms/ids.py +99 -0
- apparser-1.0.0/apparser/instructions/ui/algorithms/names.py +99 -0
- apparser-1.0.0/apparser/instructions/ui/algorithms/ocr.py +72 -0
- apparser-1.0.0/apparser/instructions/ui/algorithms/speak.py +79 -0
- apparser-1.0.0/apparser/instructions/ui/algorithms/unique.py +80 -0
- apparser-1.0.0/apparser/instructions/ui/base.py +29 -0
- apparser-1.0.0/apparser/instructions/ui/click.py +41 -0
- apparser-1.0.0/apparser/instructions/ui/mouse_move.py +38 -0
- apparser-1.0.0/apparser/instructions/ui/move_window.py +27 -0
- apparser-1.0.0/apparser/instructions/ui/resize_window.py +27 -0
- apparser-1.0.0/apparser/instructions/ui/to_window.py +25 -0
- apparser-1.0.0/apparser/instructions/utils/__init__.py +7 -0
- apparser-1.0.0/apparser/instructions/utils/get_all_instructions.py +21 -0
- apparser-1.0.0/apparser/instructions/utils/get_by_id.py +26 -0
- apparser-1.0.0/apparser/instructions/utils/get_by_name.py +26 -0
- apparser-1.0.0/apparser/key_codes/__init__.py +11 -0
- apparser-1.0.0/apparser/key_codes/base.py +14 -0
- apparser-1.0.0/apparser/key_codes/keyboard_keys.py +49 -0
- apparser-1.0.0/apparser/key_codes/mouse_keys.py +25 -0
- apparser-1.0.0/apparser/movers/__init__.py +7 -0
- apparser-1.0.0/apparser/movers/base.py +16 -0
- apparser-1.0.0/apparser/movers/default.py +34 -0
- apparser-1.0.0/apparser/movers/math_antirobot.py +123 -0
- apparser-1.0.0/apparser/speakers/__init__.py +5 -0
- apparser-1.0.0/apparser/speakers/base.py +18 -0
- apparser-1.0.0/apparser/speakers/chat_tts.py +124 -0
- apparser-1.0.0/apparser/speakers/torch.py +87 -0
- apparser-1.0.0/apparser/text_readers/__init__.py +14 -0
- apparser-1.0.0/apparser/text_readers/base.py +20 -0
- apparser-1.0.0/apparser/text_readers/easy_ocr.py +42 -0
- apparser-1.0.0/apparser/text_readers/models/__init__.py +0 -0
- apparser-1.0.0/apparser/text_readers/models/text_data.py +11 -0
- apparser-1.0.0/apparser/text_readers/paddle.py +126 -0
- apparser-1.0.0/apparser/text_readers/screens_controller.py +40 -0
- apparser-1.0.0/apparser/text_readers/white_black_reader.py +30 -0
- apparser-1.0.0/apparser.egg-info/PKG-INFO +89 -0
- apparser-1.0.0/apparser.egg-info/SOURCES.txt +112 -0
- apparser-1.0.0/apparser.egg-info/dependency_links.txt +1 -0
- apparser-1.0.0/apparser.egg-info/requires.txt +24 -0
- apparser-1.0.0/apparser.egg-info/top_level.txt +1 -0
- apparser-1.0.0/pyproject.toml +75 -0
- apparser-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Terochkin A.S
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
* Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
apparser-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apparser
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Apparser is a Python library designed for automating desktop applications and managing UI interfaces using artificial intelligence, such as OCR or object detection models.
|
|
5
|
+
Author-email: "Terochkin A.S" <apparser.development@gmail.com>
|
|
6
|
+
Project-URL: Docs, https://apparser-development.github.io/apparser/
|
|
7
|
+
Project-URL: Repository, https://github.com/apparser-development/apparser
|
|
8
|
+
Project-URL: Issues, https://github.com/apparser-development/apparser/issues
|
|
9
|
+
Keywords: automation,desktop-automation,gui-automation,ui-automation,computer-vision,object-detection,ocr,ui,cv,yolo,text-recognition,screen-recognition,window-management,mouse-automation,keyboard-automation,speech-synthesis,tts,app-control
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 10
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows :: Windows 11
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Environment :: X11 Applications
|
|
17
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
18
|
+
Classifier: Environment :: MacOS X :: Aqua
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE.md
|
|
22
|
+
Requires-Dist: appwindows>=1.3.0
|
|
23
|
+
Requires-Dist: PyAutoGui>=0.9.0
|
|
24
|
+
Requires-Dist: numpy>=1.20
|
|
25
|
+
Requires-Dist: pillow>=11.0
|
|
26
|
+
Requires-Dist: screeninfo>=0.8
|
|
27
|
+
Requires-Dist: thefuzz>=0.20.0
|
|
28
|
+
Provides-Extra: cv
|
|
29
|
+
Requires-Dist: ultralytics>=8.4.30; extra == "cv"
|
|
30
|
+
Provides-Extra: ocr
|
|
31
|
+
Requires-Dist: easyocr<2.0,>=1.7.2; extra == "ocr"
|
|
32
|
+
Requires-Dist: paddleocr>=3.5.0; extra == "ocr"
|
|
33
|
+
Requires-Dist: paddlepaddle>=3.3.1; extra == "ocr"
|
|
34
|
+
Provides-Extra: speak
|
|
35
|
+
Requires-Dist: ChatTTS; extra == "speak"
|
|
36
|
+
Requires-Dist: sounddevice; extra == "speak"
|
|
37
|
+
Requires-Dist: torch; extra == "speak"
|
|
38
|
+
Provides-Extra: all
|
|
39
|
+
Requires-Dist: apparser[cv]; extra == "all"
|
|
40
|
+
Requires-Dist: apparser[ocr]; extra == "all"
|
|
41
|
+
Requires-Dist: apparser[speak]; extra == "all"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
<img src="https://raw.githubusercontent.com/apparser-development/apparser/refs/heads/master/apparser.svg" alt="" width="40%">
|
|
45
|
+
|
|
46
|
+
[](https://apparser-development.github.io/apparser/)
|
|
47
|
+
[](https://github.com/apparser-development/appwindows/actions/workflows/unit_tests.yml)
|
|
48
|
+
<br>
|
|
49
|
+
[](https://github.com/apparser-development/apparser)
|
|
50
|
+
[](https://github.com/apparser-development/apparser/issues)
|
|
51
|
+
|
|
52
|
+
# Apparser
|
|
53
|
+
Apparser is a Python library designed for automating desktop applications and managing UI interfaces using artificial intelligence, such as OCR or object detection models.
|
|
54
|
+
# Install
|
|
55
|
+
```bash
|
|
56
|
+
pip install apparser
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
# Examples
|
|
60
|
+
|
|
61
|
+
1) Open terminal and write "Hello World!"
|
|
62
|
+
```python
|
|
63
|
+
from apparser import App
|
|
64
|
+
from apparser.geometry import RelativelyPoint
|
|
65
|
+
from apparser.instructions import Algorithm, MouseClickTo, WriteText, Sleep
|
|
66
|
+
|
|
67
|
+
algorithm = Algorithm([
|
|
68
|
+
Sleep(1), # Wait for the application to open.
|
|
69
|
+
MouseClickTo(RelativelyPoint(0.5, 0.5)), # Click to window center for start writing
|
|
70
|
+
WriteText("Hello World") # Write text
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
app = App("notepad", window_title="Notepad")
|
|
74
|
+
|
|
75
|
+
algorithm.perform(app.ui)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
# Docs
|
|
79
|
+
All documentation <a href="https://apparser-development.github.io/apparser/">here</a> <br>
|
|
80
|
+
Link to <a href="https://pypi.org/project/apparser/">PyPi</a>
|
|
81
|
+
|
|
82
|
+
# For Developers
|
|
83
|
+
1) If something doesn't work - open issue.
|
|
84
|
+
2) If you want something fixed - open issue.
|
|
85
|
+
3) If you can help with the library - email.
|
|
86
|
+
|
|
87
|
+
apparser.development@gmail.com
|
|
88
|
+
|
|
89
|
+
Any help in development is welcome)!
|
apparser-1.0.0/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<img src="https://raw.githubusercontent.com/apparser-development/apparser/refs/heads/master/apparser.svg" alt="" width="40%">
|
|
2
|
+
|
|
3
|
+
[](https://apparser-development.github.io/apparser/)
|
|
4
|
+
[](https://github.com/apparser-development/appwindows/actions/workflows/unit_tests.yml)
|
|
5
|
+
<br>
|
|
6
|
+
[](https://github.com/apparser-development/apparser)
|
|
7
|
+
[](https://github.com/apparser-development/apparser/issues)
|
|
8
|
+
|
|
9
|
+
# Apparser
|
|
10
|
+
Apparser is a Python library designed for automating desktop applications and managing UI interfaces using artificial intelligence, such as OCR or object detection models.
|
|
11
|
+
# Install
|
|
12
|
+
```bash
|
|
13
|
+
pip install apparser
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
# Examples
|
|
17
|
+
|
|
18
|
+
1) Open terminal and write "Hello World!"
|
|
19
|
+
```python
|
|
20
|
+
from apparser import App
|
|
21
|
+
from apparser.geometry import RelativelyPoint
|
|
22
|
+
from apparser.instructions import Algorithm, MouseClickTo, WriteText, Sleep
|
|
23
|
+
|
|
24
|
+
algorithm = Algorithm([
|
|
25
|
+
Sleep(1), # Wait for the application to open.
|
|
26
|
+
MouseClickTo(RelativelyPoint(0.5, 0.5)), # Click to window center for start writing
|
|
27
|
+
WriteText("Hello World") # Write text
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
app = App("notepad", window_title="Notepad")
|
|
31
|
+
|
|
32
|
+
algorithm.perform(app.ui)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
# Docs
|
|
36
|
+
All documentation <a href="https://apparser-development.github.io/apparser/">here</a> <br>
|
|
37
|
+
Link to <a href="https://pypi.org/project/apparser/">PyPi</a>
|
|
38
|
+
|
|
39
|
+
# For Developers
|
|
40
|
+
1) If something doesn't work - open issue.
|
|
41
|
+
2) If you want something fixed - open issue.
|
|
42
|
+
3) If you can help with the library - email.
|
|
43
|
+
|
|
44
|
+
apparser.development@gmail.com
|
|
45
|
+
|
|
46
|
+
Any help in development is welcome)!
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from apparser.core import *
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from appwindows import get_finder
|
|
6
|
+
from appwindows.exceptions import WindowDoesNotFoundException, WindowDoesNotValidException
|
|
7
|
+
|
|
8
|
+
from apparser.core.ui import WindowUi, BaseUi
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class App:
|
|
12
|
+
"""Manage an application process and its UI wrapper."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, path_to_exe: str,
|
|
15
|
+
window_title: str | None = None,
|
|
16
|
+
timeout: float = 1):
|
|
17
|
+
"""Initialize an application controller.
|
|
18
|
+
|
|
19
|
+
:param path_to_exe: Path to the executable file.
|
|
20
|
+
:type path_to_exe: str
|
|
21
|
+
:param window_title: Title of the window to attach to.
|
|
22
|
+
:type window_title: str
|
|
23
|
+
:param timeout: Delay before the window lookup starts.
|
|
24
|
+
:type timeout: float
|
|
25
|
+
:raises TypeError: If any argument has an invalid type.
|
|
26
|
+
"""
|
|
27
|
+
if not isinstance(path_to_exe, str):
|
|
28
|
+
raise TypeError('path_to_exe must be a string')
|
|
29
|
+
|
|
30
|
+
if window_title is not None and not isinstance(window_title, str):
|
|
31
|
+
raise TypeError('window_title must be a string')
|
|
32
|
+
|
|
33
|
+
if not (isinstance(timeout, float) or isinstance(timeout, int)):
|
|
34
|
+
raise TypeError('timeout must be a number')
|
|
35
|
+
|
|
36
|
+
self.__window_finder = get_finder()
|
|
37
|
+
self.__process: subprocess.Popen | None = None
|
|
38
|
+
self.__path = path_to_exe
|
|
39
|
+
self.__timeout = timeout
|
|
40
|
+
self.__window_title_name: str = window_title
|
|
41
|
+
self.__ui: BaseUi | None = None
|
|
42
|
+
self.start_app()
|
|
43
|
+
|
|
44
|
+
def __find_window_by_title(self):
|
|
45
|
+
try:
|
|
46
|
+
window = self.__window_finder.get_window_by_title(self.__window_title_name)
|
|
47
|
+
self.__ui = WindowUi(window)
|
|
48
|
+
except WindowDoesNotFoundException:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
def __find_window_by_process_id(self, process_id: int):
|
|
52
|
+
try:
|
|
53
|
+
window = self.__window_finder.get_window_by_process_id(process_id)
|
|
54
|
+
self.__ui = WindowUi(window)
|
|
55
|
+
except WindowDoesNotFoundException:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
def start_app(self):
|
|
59
|
+
"""Start the application process and bind its UI.
|
|
60
|
+
|
|
61
|
+
:raises WindowDoesNotValidException: If the window is not found or the application cannot be opened.
|
|
62
|
+
"""
|
|
63
|
+
if self.__window_title_name is not None:
|
|
64
|
+
self.__find_window_by_title()
|
|
65
|
+
if self.__ui is not None:
|
|
66
|
+
return
|
|
67
|
+
window_processes = [i.get_process_id() for i in get_finder().get_all_windows()]
|
|
68
|
+
self.__process = subprocess.Popen([self.__path])
|
|
69
|
+
time.sleep(self.__timeout)
|
|
70
|
+
self.__find_window_by_process_id(os.getpid())
|
|
71
|
+
for i in get_finder().get_all_windows():
|
|
72
|
+
if self.__ui is not None:
|
|
73
|
+
return
|
|
74
|
+
if i.get_process_id() not in window_processes:
|
|
75
|
+
self.__find_window_by_process_id(i.get_process_id())
|
|
76
|
+
if self.__ui is not None:
|
|
77
|
+
return
|
|
78
|
+
self.__find_window_by_title()
|
|
79
|
+
if self.__ui is not None:
|
|
80
|
+
raise WindowDoesNotValidException()
|
|
81
|
+
|
|
82
|
+
def stop_app(self):
|
|
83
|
+
"""Close the application window and stop the process."""
|
|
84
|
+
self.ui.window.close()
|
|
85
|
+
if self.__process is not None:
|
|
86
|
+
self.__process.kill()
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def ui(self) -> BaseUi:
|
|
90
|
+
"""Return the UI wrapper for the running application.
|
|
91
|
+
|
|
92
|
+
:return: UI wrapper bound to the application window.
|
|
93
|
+
:rtype: BaseUi
|
|
94
|
+
"""
|
|
95
|
+
return self.__ui
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from apparser.core.ui.base import BaseUi
|
|
2
|
+
from apparser.core.ui.desktop import DesktopUi
|
|
3
|
+
from apparser.core.ui.coordinates import CoordinatesUi
|
|
4
|
+
from apparser.core.ui.window import WindowUi
|
|
5
|
+
from apparser.core.ui.window_by_display import WindowByDisplayUi
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = ["BaseUi", "DesktopUi", "CoordinatesUi", "WindowUi", "WindowByDisplayUi"]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
|
|
3
|
+
import numpy
|
|
4
|
+
from appwindows import Window
|
|
5
|
+
|
|
6
|
+
from apparser.geometry import Point, RelativelyPoint
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseUi(abc.ABC):
|
|
10
|
+
"""Define the common interface for UI coordinate systems."""
|
|
11
|
+
|
|
12
|
+
@abc.abstractmethod
|
|
13
|
+
def point_to_global(self, coordinates: Point | RelativelyPoint) -> Point:
|
|
14
|
+
"""Convert coordinates to the global screen space.
|
|
15
|
+
|
|
16
|
+
:param coordinates: Local or relative coordinates to convert.
|
|
17
|
+
:type coordinates: Point | RelativelyPoint
|
|
18
|
+
:return: Converted global point.
|
|
19
|
+
:rtype: Point
|
|
20
|
+
"""
|
|
21
|
+
raise NotImplementedError()
|
|
22
|
+
|
|
23
|
+
@abc.abstractmethod
|
|
24
|
+
def point_to_local(self, coordinates: Point) -> Point:
|
|
25
|
+
"""Convert coordinates to the local UI space.
|
|
26
|
+
|
|
27
|
+
:param coordinates: Global point to convert.
|
|
28
|
+
:type coordinates: Point
|
|
29
|
+
:return: Converted local point.
|
|
30
|
+
:rtype: Point
|
|
31
|
+
"""
|
|
32
|
+
raise NotImplementedError()
|
|
33
|
+
|
|
34
|
+
@abc.abstractmethod
|
|
35
|
+
def get_screenshot(self) -> numpy.ndarray:
|
|
36
|
+
"""Capture the current UI screenshot.
|
|
37
|
+
|
|
38
|
+
:return: Screenshot data.
|
|
39
|
+
:rtype: numpy.ndarray
|
|
40
|
+
"""
|
|
41
|
+
raise NotImplementedError()
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
@abc.abstractmethod
|
|
45
|
+
def window(self) -> Window:
|
|
46
|
+
"""Return the window associated with the UI context.
|
|
47
|
+
|
|
48
|
+
:return: Underlying application window.
|
|
49
|
+
:rtype: Window
|
|
50
|
+
"""
|
|
51
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from functools import singledispatchmethod
|
|
2
|
+
|
|
3
|
+
import numpy
|
|
4
|
+
from appwindows import Window
|
|
5
|
+
from appwindows.geometry import Size
|
|
6
|
+
|
|
7
|
+
from apparser.core.ui.base import BaseUi
|
|
8
|
+
from apparser.geometry import Point, RelativelyPoint
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CoordinatesUi(BaseUi):
|
|
12
|
+
"""Represent a UI region defined by two points inside another UI context."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
from_ui: BaseUi,
|
|
17
|
+
point_one: Point | RelativelyPoint,
|
|
18
|
+
point_two: Point | RelativelyPoint
|
|
19
|
+
):
|
|
20
|
+
"""Initialize a nested coordinate-based UI context.
|
|
21
|
+
|
|
22
|
+
:param from_ui: Source UI used as a parent context.
|
|
23
|
+
:type from_ui: BaseUi
|
|
24
|
+
:param point_one: First point of the nested region.
|
|
25
|
+
:type point_one: Point | RelativelyPoint
|
|
26
|
+
:param point_two: Second point of the nested region or region size.
|
|
27
|
+
:type point_two: Point | RelativelyPoint | Size
|
|
28
|
+
:raises TypeError: If any argument has an invalid type.
|
|
29
|
+
"""
|
|
30
|
+
if not isinstance(from_ui, BaseUi):
|
|
31
|
+
raise TypeError('from_ui must be BaseUi')
|
|
32
|
+
|
|
33
|
+
if not isinstance(point_one, (Point, RelativelyPoint)):
|
|
34
|
+
raise TypeError('point1 must be Point or RelativelyPoint')
|
|
35
|
+
|
|
36
|
+
elif not isinstance(point_two, (Point, RelativelyPoint)):
|
|
37
|
+
raise TypeError('point2 must be Point, RelativelyPoint')
|
|
38
|
+
|
|
39
|
+
self.__from_ui = from_ui
|
|
40
|
+
self.__point_one = point_one
|
|
41
|
+
self.__point_two = point_two
|
|
42
|
+
|
|
43
|
+
def __point_to_main_ui_local(self, point: Point | RelativelyPoint) -> Point:
|
|
44
|
+
if isinstance(point, RelativelyPoint):
|
|
45
|
+
return self.__from_ui.point_to_local(self.__from_ui.point_to_global(point))
|
|
46
|
+
return point
|
|
47
|
+
|
|
48
|
+
def __get_local_bounds(self) -> tuple[Point, Point]:
|
|
49
|
+
point1 = self.__point_to_main_ui_local(self.__point_one)
|
|
50
|
+
point2 = self.__point_to_main_ui_local(self.__point_two)
|
|
51
|
+
left_top_point = Point(
|
|
52
|
+
min(point1.x, point2.x),
|
|
53
|
+
min(point1.y, point2.y),
|
|
54
|
+
)
|
|
55
|
+
right_bottom_point = Point(
|
|
56
|
+
max(point1.x, point2.x),
|
|
57
|
+
max(point1.y, point2.y),
|
|
58
|
+
)
|
|
59
|
+
return left_top_point, right_bottom_point
|
|
60
|
+
|
|
61
|
+
@singledispatchmethod
|
|
62
|
+
def point_to_global(self, coordinates: Point | RelativelyPoint) -> Point:
|
|
63
|
+
"""Convert region coordinates to the global screen space.
|
|
64
|
+
|
|
65
|
+
:param coordinates: Local or relative coordinates to convert.
|
|
66
|
+
:type coordinates: Point | RelativelyPoint
|
|
67
|
+
:return: Converted global point.
|
|
68
|
+
:rtype: Point
|
|
69
|
+
"""
|
|
70
|
+
raise NotImplementedError()
|
|
71
|
+
|
|
72
|
+
@point_to_global.register(Point)
|
|
73
|
+
def _(self, coordinates: Point) -> Point:
|
|
74
|
+
left_top_point, _ = self.__get_local_bounds()
|
|
75
|
+
return self.__from_ui.point_to_global(coordinates + left_top_point)
|
|
76
|
+
|
|
77
|
+
@point_to_global.register(RelativelyPoint)
|
|
78
|
+
def _(self, coordinates: RelativelyPoint) -> Point:
|
|
79
|
+
left_top_point, right_bottom_point = self.__get_local_bounds()
|
|
80
|
+
width = abs(round(right_bottom_point.x - left_top_point.x))
|
|
81
|
+
height = abs(round(right_bottom_point.y - left_top_point.y))
|
|
82
|
+
x = round(coordinates.x * width)
|
|
83
|
+
y = round(coordinates.y * height)
|
|
84
|
+
local_point = Point(x, y)
|
|
85
|
+
return self.point_to_global(local_point)
|
|
86
|
+
|
|
87
|
+
def point_to_local(self, coordinates: Point) -> Point:
|
|
88
|
+
"""Convert global coordinates to the local region space.
|
|
89
|
+
|
|
90
|
+
:param coordinates: Global point to convert.
|
|
91
|
+
:type coordinates: Point
|
|
92
|
+
:return: Converted local point.
|
|
93
|
+
:rtype: Point
|
|
94
|
+
"""
|
|
95
|
+
left_top_point, _ = self.__get_local_bounds()
|
|
96
|
+
return self.__from_ui.point_to_local(coordinates) - left_top_point
|
|
97
|
+
|
|
98
|
+
def get_screenshot(self) -> numpy.ndarray:
|
|
99
|
+
"""Capture a screenshot cropped to the nested region.
|
|
100
|
+
|
|
101
|
+
:return: Screenshot data for the nested region.
|
|
102
|
+
:rtype: numpy.ndarray
|
|
103
|
+
"""
|
|
104
|
+
screenshot = self.__from_ui.get_screenshot()
|
|
105
|
+
left_top_point, right_bottom_point = self.__get_local_bounds()
|
|
106
|
+
return screenshot[
|
|
107
|
+
left_top_point.y:right_bottom_point.y,
|
|
108
|
+
left_top_point.x:right_bottom_point.x,
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def window(self) -> Window:
|
|
113
|
+
"""Return the parent window for the nested region.
|
|
114
|
+
|
|
115
|
+
:return: Underlying parent window.
|
|
116
|
+
:rtype: Window
|
|
117
|
+
"""
|
|
118
|
+
return self.__from_ui.window
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from functools import singledispatchmethod
|
|
2
|
+
|
|
3
|
+
import numpy
|
|
4
|
+
from appwindows import Window
|
|
5
|
+
from PIL import ImageGrab
|
|
6
|
+
from screeninfo import get_monitors
|
|
7
|
+
|
|
8
|
+
from apparser.core.ui.base import BaseUi
|
|
9
|
+
from apparser.geometry import Point, RelativelyPoint
|
|
10
|
+
from apparser.exceptions import WindowActionWithDesktopException
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DesktopUi(BaseUi):
|
|
14
|
+
"""Represent the full desktop as a UI context."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, display_id: int = 0):
|
|
17
|
+
"""Initialize a desktop UI context.
|
|
18
|
+
|
|
19
|
+
:param display_id: Monitor index used for relative coordinates.
|
|
20
|
+
:type display_id: int
|
|
21
|
+
"""
|
|
22
|
+
self.__display_id = display_id
|
|
23
|
+
|
|
24
|
+
@singledispatchmethod
|
|
25
|
+
def point_to_global(self, coordinates: Point | RelativelyPoint) -> Point:
|
|
26
|
+
"""Convert desktop coordinates to the global screen space.
|
|
27
|
+
|
|
28
|
+
:param coordinates: Local or relative coordinates to convert.
|
|
29
|
+
:type coordinates: Point | RelativelyPoint
|
|
30
|
+
:return: Converted global point.
|
|
31
|
+
:rtype: Point
|
|
32
|
+
"""
|
|
33
|
+
raise NotImplementedError()
|
|
34
|
+
|
|
35
|
+
@point_to_global.register(Point)
|
|
36
|
+
def _(self, coordinates: Point):
|
|
37
|
+
return coordinates
|
|
38
|
+
|
|
39
|
+
@point_to_global.register(RelativelyPoint)
|
|
40
|
+
def _(self, coordinates: RelativelyPoint):
|
|
41
|
+
monitor = get_monitors()[self.__display_id]
|
|
42
|
+
x = round(coordinates.x * monitor.width)
|
|
43
|
+
y = round(coordinates.y * monitor.height)
|
|
44
|
+
local_point = Point(x, y)
|
|
45
|
+
return self.point_to_global(local_point)
|
|
46
|
+
|
|
47
|
+
def point_to_local(self, coordinates: Point) -> Point:
|
|
48
|
+
"""Convert global coordinates to the desktop local space.
|
|
49
|
+
|
|
50
|
+
:param coordinates: Global point to convert.
|
|
51
|
+
:type coordinates: Point
|
|
52
|
+
:return: Converted local point.
|
|
53
|
+
:rtype: Point
|
|
54
|
+
"""
|
|
55
|
+
return Point(coordinates.x, coordinates.y)
|
|
56
|
+
|
|
57
|
+
def get_screenshot(self) -> numpy.ndarray:
|
|
58
|
+
"""Capture a screenshot of the desktop.
|
|
59
|
+
|
|
60
|
+
:return: Desktop screenshot data.
|
|
61
|
+
:rtype: numpy.ndarray
|
|
62
|
+
"""
|
|
63
|
+
screenshot = ImageGrab.grab()
|
|
64
|
+
return numpy.asarray(screenshot)
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def window(self) -> Window:
|
|
68
|
+
"""Raise an exception because the desktop has no window.
|
|
69
|
+
|
|
70
|
+
:raises WindowActionWithDesktopException: Always raised for desktop UI contexts.
|
|
71
|
+
"""
|
|
72
|
+
raise WindowActionWithDesktopException()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from functools import singledispatchmethod
|
|
2
|
+
|
|
3
|
+
import numpy
|
|
4
|
+
|
|
5
|
+
from appwindows import Window
|
|
6
|
+
from appwindows.geometry import Point, Size
|
|
7
|
+
|
|
8
|
+
from apparser.core.ui.base import BaseUi
|
|
9
|
+
from apparser.geometry.relatively_point import RelativelyPoint
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class WindowUi(BaseUi):
|
|
13
|
+
"""Represent a window as a UI context."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, window: Window):
|
|
16
|
+
"""Initialize a window UI context.
|
|
17
|
+
|
|
18
|
+
:param window: Window instance to wrap.
|
|
19
|
+
:type window: Window
|
|
20
|
+
:raises TypeError: If ``window`` has an invalid type.
|
|
21
|
+
"""
|
|
22
|
+
if not isinstance(window, Window):
|
|
23
|
+
raise TypeError('window must be Window')
|
|
24
|
+
|
|
25
|
+
self.__window = window
|
|
26
|
+
|
|
27
|
+
@singledispatchmethod
|
|
28
|
+
def point_to_global(self, coordinates: Point | RelativelyPoint) -> Point:
|
|
29
|
+
"""Convert window coordinates to the global screen space.
|
|
30
|
+
|
|
31
|
+
:param coordinates: Local or relative coordinates to convert.
|
|
32
|
+
:type coordinates: Point | RelativelyPoint
|
|
33
|
+
:return: Converted global point.
|
|
34
|
+
:rtype: Point
|
|
35
|
+
"""
|
|
36
|
+
raise NotImplementedError()
|
|
37
|
+
|
|
38
|
+
@point_to_global.register(Point)
|
|
39
|
+
def _(self, coordinates: Point):
|
|
40
|
+
return coordinates + self.__window.get_points().left_top
|
|
41
|
+
|
|
42
|
+
@point_to_global.register(RelativelyPoint)
|
|
43
|
+
def _(self, coordinates: RelativelyPoint):
|
|
44
|
+
size: Size = self.__window.get_size()
|
|
45
|
+
x = round(coordinates.x * size.width)
|
|
46
|
+
y = round(coordinates.y * size.height)
|
|
47
|
+
local_point = Point(x, y)
|
|
48
|
+
return self.point_to_global(local_point)
|
|
49
|
+
|
|
50
|
+
def point_to_local(self, coordinates: Point) -> Point:
|
|
51
|
+
"""Convert global coordinates to the window local space.
|
|
52
|
+
|
|
53
|
+
:param coordinates: Global point to convert.
|
|
54
|
+
:type coordinates: Point
|
|
55
|
+
:return: Converted local point.
|
|
56
|
+
:rtype: Point
|
|
57
|
+
"""
|
|
58
|
+
return coordinates - self.__window.get_points().left_top
|
|
59
|
+
|
|
60
|
+
def get_screenshot(self) -> numpy.ndarray:
|
|
61
|
+
"""Capture a screenshot of the window.
|
|
62
|
+
|
|
63
|
+
:return: Window screenshot data.
|
|
64
|
+
:rtype: numpy.ndarray
|
|
65
|
+
"""
|
|
66
|
+
return self.__window.get_screenshot()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def window(self):
|
|
70
|
+
"""Return the wrapped window instance.
|
|
71
|
+
|
|
72
|
+
:return: Wrapped window.
|
|
73
|
+
:rtype: Window
|
|
74
|
+
"""
|
|
75
|
+
return self.__window
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from functools import singledispatchmethod
|
|
2
|
+
|
|
3
|
+
import numpy
|
|
4
|
+
from PIL import ImageGrab
|
|
5
|
+
|
|
6
|
+
from appwindows import Window
|
|
7
|
+
from appwindows.geometry import Point, Size
|
|
8
|
+
|
|
9
|
+
from apparser.core.ui.base import BaseUi
|
|
10
|
+
from apparser.geometry.relatively_point import RelativelyPoint
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WindowByDisplayUi(BaseUi):
|
|
14
|
+
"""
|
|
15
|
+
Represent a window as a display-captured UI context.
|
|
16
|
+
Unlike the WindowUi class, it retrieves the application's image based on its borders rather than from the graphical shell.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, window: Window) -> None:
|
|
20
|
+
"""Initialize a display-captured window UI context.
|
|
21
|
+
|
|
22
|
+
:param window: Window instance to wrap.
|
|
23
|
+
:type window: Window
|
|
24
|
+
:raises TypeError: If ``window`` has an invalid type.
|
|
25
|
+
"""
|
|
26
|
+
if not isinstance(window, Window):
|
|
27
|
+
raise TypeError('window must be Window')
|
|
28
|
+
|
|
29
|
+
self.__window = window
|
|
30
|
+
|
|
31
|
+
@singledispatchmethod
|
|
32
|
+
def point_to_global(self, coordinates: Point | RelativelyPoint) -> Point:
|
|
33
|
+
"""Convert window coordinates to the global screen space.
|
|
34
|
+
|
|
35
|
+
:param coordinates: Local or relative coordinates to convert.
|
|
36
|
+
:type coordinates: Point | RelativelyPoint
|
|
37
|
+
:return: Converted global point.
|
|
38
|
+
:rtype: Point
|
|
39
|
+
"""
|
|
40
|
+
raise NotImplementedError()
|
|
41
|
+
|
|
42
|
+
@point_to_global.register(Point)
|
|
43
|
+
def _(self, coordinates: Point) -> Point:
|
|
44
|
+
return coordinates + self.__window.get_points().left_top
|
|
45
|
+
|
|
46
|
+
@point_to_global.register(RelativelyPoint)
|
|
47
|
+
def _(self, coordinates: RelativelyPoint) -> Point:
|
|
48
|
+
size: Size = self.__window.get_size()
|
|
49
|
+
x = round(coordinates.x * size.width)
|
|
50
|
+
y = round(coordinates.y * size.height)
|
|
51
|
+
local_point = Point(x, y)
|
|
52
|
+
return self.point_to_global(local_point)
|
|
53
|
+
|
|
54
|
+
def point_to_local(self, coordinates: Point) -> Point:
|
|
55
|
+
"""Convert global coordinates to the window local space.
|
|
56
|
+
|
|
57
|
+
:param coordinates: Global point to convert.
|
|
58
|
+
:type coordinates: Point
|
|
59
|
+
:return: Converted local point.
|
|
60
|
+
:rtype: Point
|
|
61
|
+
"""
|
|
62
|
+
return coordinates - self.__window.get_points().left_top
|
|
63
|
+
|
|
64
|
+
def get_screenshot(self) -> numpy.ndarray:
|
|
65
|
+
"""Capture a screenshot of the window from the display.
|
|
66
|
+
|
|
67
|
+
:return: Window screenshot data captured by window bounds.
|
|
68
|
+
:rtype: numpy.ndarray
|
|
69
|
+
"""
|
|
70
|
+
left_top = self.__window.get_points().left_top
|
|
71
|
+
size: Size = self.__window.get_size()
|
|
72
|
+
screenshot = ImageGrab.grab(
|
|
73
|
+
bbox=(
|
|
74
|
+
left_top.x,
|
|
75
|
+
left_top.y,
|
|
76
|
+
left_top.x + size.width,
|
|
77
|
+
left_top.y + size.height,
|
|
78
|
+
),
|
|
79
|
+
all_screens=True,
|
|
80
|
+
)
|
|
81
|
+
return numpy.asarray(screenshot)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def window(self) -> Window:
|
|
85
|
+
"""Return the wrapped window instance.
|
|
86
|
+
|
|
87
|
+
:return: Wrapped window.
|
|
88
|
+
:rtype: Window
|
|
89
|
+
"""
|
|
90
|
+
return self.__window
|