apparser 1.0.0__py3-none-any.whl
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/__init__.py +1 -0
- apparser/core/__init__.py +4 -0
- apparser/core/app.py +95 -0
- apparser/core/ui/__init__.py +8 -0
- apparser/core/ui/base.py +51 -0
- apparser/core/ui/coordinates.py +118 -0
- apparser/core/ui/desktop.py +72 -0
- apparser/core/ui/window.py +75 -0
- apparser/core/ui/window_by_display.py +90 -0
- apparser/cv/__init__.py +6 -0
- apparser/cv/events/__init__.py +8 -0
- apparser/cv/events/base.py +14 -0
- apparser/cv/events/detected.py +13 -0
- apparser/cv/events/moved.py +13 -0
- apparser/cv/events/resized.py +13 -0
- apparser/cv/events/undetected.py +13 -0
- apparser/cv/handlers/__init__.py +5 -0
- apparser/cv/handlers/base.py +30 -0
- apparser/cv/handlers/default.py +74 -0
- apparser/cv/models/__init__.py +4 -0
- apparser/cv/models/data.py +33 -0
- apparser/cv/models/handler.py +15 -0
- apparser/cv/processes/__init__.py +5 -0
- apparser/cv/processes/base.py +31 -0
- apparser/cv/processes/default.py +64 -0
- apparser/cv/readers/__init__.py +4 -0
- apparser/cv/readers/base.py +19 -0
- apparser/cv/readers/yolo.py +69 -0
- apparser/cv/utils/__init__.py +3 -0
- apparser/cv/utils/changes_checker.py +84 -0
- apparser/exceptions/__init__.py +10 -0
- apparser/exceptions/debug.py +14 -0
- apparser/exceptions/instruction_not_found.py +37 -0
- apparser/exceptions/text_not_found.py +18 -0
- apparser/exceptions/timeout.py +13 -0
- apparser/exceptions/window_action_with_desktop.py +6 -0
- apparser/geometry/__init__.py +9 -0
- apparser/geometry/distance.py +21 -0
- apparser/geometry/relatively_point.py +45 -0
- apparser/instructions/__init__.py +4 -0
- apparser/instructions/base.py +24 -0
- apparser/instructions/debuggers/__init__.py +4 -0
- apparser/instructions/debuggers/base.py +23 -0
- apparser/instructions/debuggers/default.py +44 -0
- apparser/instructions/default/__init__.py +23 -0
- apparser/instructions/default/click.py +73 -0
- apparser/instructions/default/play_audio.py +88 -0
- apparser/instructions/default/play_audio_file.py +77 -0
- apparser/instructions/default/press.py +99 -0
- apparser/instructions/default/say_audio.py +92 -0
- apparser/instructions/default/say_audio_file.py +77 -0
- apparser/instructions/default/sleep.py +26 -0
- apparser/instructions/default/write_text.py +36 -0
- apparser/instructions/ocr/__init__.py +16 -0
- apparser/instructions/ocr/base.py +36 -0
- apparser/instructions/ocr/click_on_text.py +52 -0
- apparser/instructions/ocr/move_to_text.py +71 -0
- apparser/instructions/ocr/plot_text.py +81 -0
- apparser/instructions/ocr/print_all_text.py +33 -0
- apparser/instructions/ocr/text_getter.py +92 -0
- apparser/instructions/ocr/wait_text.py +69 -0
- apparser/instructions/speak/__init__.py +5 -0
- apparser/instructions/speak/base.py +34 -0
- apparser/instructions/speak/play_text.py +52 -0
- apparser/instructions/speak/say_text.py +52 -0
- apparser/instructions/ui/__init__.py +15 -0
- apparser/instructions/ui/algorithms/__init__.py +16 -0
- apparser/instructions/ui/algorithms/algorithm.py +60 -0
- apparser/instructions/ui/algorithms/base.py +47 -0
- apparser/instructions/ui/algorithms/ids.py +99 -0
- apparser/instructions/ui/algorithms/names.py +99 -0
- apparser/instructions/ui/algorithms/ocr.py +72 -0
- apparser/instructions/ui/algorithms/speak.py +79 -0
- apparser/instructions/ui/algorithms/unique.py +80 -0
- apparser/instructions/ui/base.py +29 -0
- apparser/instructions/ui/click.py +41 -0
- apparser/instructions/ui/mouse_move.py +38 -0
- apparser/instructions/ui/move_window.py +27 -0
- apparser/instructions/ui/resize_window.py +27 -0
- apparser/instructions/ui/to_window.py +25 -0
- apparser/instructions/utils/__init__.py +7 -0
- apparser/instructions/utils/get_all_instructions.py +21 -0
- apparser/instructions/utils/get_by_id.py +26 -0
- apparser/instructions/utils/get_by_name.py +26 -0
- apparser/key_codes/__init__.py +11 -0
- apparser/key_codes/base.py +14 -0
- apparser/key_codes/keyboard_keys.py +49 -0
- apparser/key_codes/mouse_keys.py +25 -0
- apparser/movers/__init__.py +7 -0
- apparser/movers/base.py +16 -0
- apparser/movers/default.py +34 -0
- apparser/movers/math_antirobot.py +123 -0
- apparser/speakers/__init__.py +5 -0
- apparser/speakers/base.py +18 -0
- apparser/speakers/chat_tts.py +124 -0
- apparser/speakers/torch.py +87 -0
- apparser/text_readers/__init__.py +14 -0
- apparser/text_readers/base.py +20 -0
- apparser/text_readers/easy_ocr.py +42 -0
- apparser/text_readers/models/__init__.py +0 -0
- apparser/text_readers/models/text_data.py +11 -0
- apparser/text_readers/paddle.py +126 -0
- apparser/text_readers/screens_controller.py +40 -0
- apparser/text_readers/white_black_reader.py +30 -0
- apparser-1.0.0.dist-info/METADATA +89 -0
- apparser-1.0.0.dist-info/RECORD +109 -0
- apparser-1.0.0.dist-info/WHEEL +5 -0
- apparser-1.0.0.dist-info/licenses/LICENSE.md +28 -0
- apparser-1.0.0.dist-info/top_level.txt +1 -0
apparser/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from apparser.core import *
|
apparser/core/app.py
ADDED
|
@@ -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"]
|
apparser/core/ui/base.py
ADDED
|
@@ -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
|
apparser/cv/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""Public computer vision interfaces and default implementations."""
|
|
2
|
+
|
|
3
|
+
from apparser.cv.handlers.default import DefaultHandlers
|
|
4
|
+
from apparser.cv.processes.default import DefaultCvProcess
|
|
5
|
+
from apparser.cv.readers.yolo import YoloReader
|
|
6
|
+
from apparser.cv.models import *
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from apparser.cv.events.base import CvEvent
|
|
2
|
+
from apparser.cv.events.moved import Moved
|
|
3
|
+
from apparser.cv.events.detected import Detected
|
|
4
|
+
from apparser.cv.events.resized import Resized
|
|
5
|
+
from apparser.cv.events.undetected import UnDetected
|
|
6
|
+
|
|
7
|
+
__all__ = ["CvEvent", "Moved",
|
|
8
|
+
"Detected", "Resized", "UnDetected"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CvEvent(abc.ABC):
|
|
5
|
+
"""Define the interface for computer vision change events."""
|
|
6
|
+
|
|
7
|
+
@abc.abstractmethod
|
|
8
|
+
def __str__(self) -> str:
|
|
9
|
+
"""Return the human-readable event name.
|
|
10
|
+
|
|
11
|
+
:return: Event name.
|
|
12
|
+
:rtype: str
|
|
13
|
+
"""
|
|
14
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from apparser.cv.events.base import CvEvent
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Moved(CvEvent):
|
|
5
|
+
"""Represent a detected object whose position changed."""
|
|
6
|
+
|
|
7
|
+
def __str__(self) -> str:
|
|
8
|
+
"""Return the event name.
|
|
9
|
+
|
|
10
|
+
:return: Moved event name.
|
|
11
|
+
:rtype: str
|
|
12
|
+
"""
|
|
13
|
+
return "Moved"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from apparser.cv.events.base import CvEvent
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Resized(CvEvent):
|
|
5
|
+
"""Represent a detected object whose size changed."""
|
|
6
|
+
|
|
7
|
+
def __str__(self) -> str:
|
|
8
|
+
"""Return the event name.
|
|
9
|
+
|
|
10
|
+
:return: Resized event name.
|
|
11
|
+
:rtype: str
|
|
12
|
+
"""
|
|
13
|
+
return "Resized"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from apparser.cv.events.base import CvEvent
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class UnDetected(CvEvent):
|
|
5
|
+
"""Represent a previously tracked object that disappeared."""
|
|
6
|
+
|
|
7
|
+
def __str__(self) -> str:
|
|
8
|
+
"""Return the event name.
|
|
9
|
+
|
|
10
|
+
:return: Undetected event name.
|
|
11
|
+
:rtype: str
|
|
12
|
+
"""
|
|
13
|
+
return "UnDetected"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Type
|
|
3
|
+
|
|
4
|
+
from apparser.cv.events import CvEvent
|
|
5
|
+
from apparser.cv.models import CvChangeData
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CvHandlers(abc.ABC):
|
|
9
|
+
"""Define the interface for computer vision event handler registries."""
|
|
10
|
+
|
|
11
|
+
@abc.abstractmethod
|
|
12
|
+
def register_handler(self, event: Type[CvEvent]):
|
|
13
|
+
"""Return a decorator that registers a handler for an event.
|
|
14
|
+
|
|
15
|
+
:param event: Event type to subscribe to.
|
|
16
|
+
:type event: Type[CvEvent]
|
|
17
|
+
"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abc.abstractmethod
|
|
21
|
+
def call(self, event: Type[CvEvent], changed_data: CvChangeData, *args):
|
|
22
|
+
"""Call handlers registered for the provided event.
|
|
23
|
+
|
|
24
|
+
:param event: Event type to dispatch.
|
|
25
|
+
:type event: Type[CvEvent]
|
|
26
|
+
:param changed_data: Event payload with current and previous box data.
|
|
27
|
+
:type changed_data: CvChangeData
|
|
28
|
+
:param args: Additional context passed to handler functions.
|
|
29
|
+
"""
|
|
30
|
+
pass
|