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.
Files changed (114) hide show
  1. apparser-1.0.0/LICENSE.md +28 -0
  2. apparser-1.0.0/PKG-INFO +89 -0
  3. apparser-1.0.0/README.md +46 -0
  4. apparser-1.0.0/apparser/__init__.py +1 -0
  5. apparser-1.0.0/apparser/core/__init__.py +4 -0
  6. apparser-1.0.0/apparser/core/app.py +95 -0
  7. apparser-1.0.0/apparser/core/ui/__init__.py +8 -0
  8. apparser-1.0.0/apparser/core/ui/base.py +51 -0
  9. apparser-1.0.0/apparser/core/ui/coordinates.py +118 -0
  10. apparser-1.0.0/apparser/core/ui/desktop.py +72 -0
  11. apparser-1.0.0/apparser/core/ui/window.py +75 -0
  12. apparser-1.0.0/apparser/core/ui/window_by_display.py +90 -0
  13. apparser-1.0.0/apparser/cv/__init__.py +6 -0
  14. apparser-1.0.0/apparser/cv/events/__init__.py +8 -0
  15. apparser-1.0.0/apparser/cv/events/base.py +14 -0
  16. apparser-1.0.0/apparser/cv/events/detected.py +13 -0
  17. apparser-1.0.0/apparser/cv/events/moved.py +13 -0
  18. apparser-1.0.0/apparser/cv/events/resized.py +13 -0
  19. apparser-1.0.0/apparser/cv/events/undetected.py +13 -0
  20. apparser-1.0.0/apparser/cv/handlers/__init__.py +5 -0
  21. apparser-1.0.0/apparser/cv/handlers/base.py +30 -0
  22. apparser-1.0.0/apparser/cv/handlers/default.py +74 -0
  23. apparser-1.0.0/apparser/cv/models/__init__.py +4 -0
  24. apparser-1.0.0/apparser/cv/models/data.py +33 -0
  25. apparser-1.0.0/apparser/cv/models/handler.py +15 -0
  26. apparser-1.0.0/apparser/cv/processes/__init__.py +5 -0
  27. apparser-1.0.0/apparser/cv/processes/base.py +31 -0
  28. apparser-1.0.0/apparser/cv/processes/default.py +64 -0
  29. apparser-1.0.0/apparser/cv/readers/__init__.py +4 -0
  30. apparser-1.0.0/apparser/cv/readers/base.py +19 -0
  31. apparser-1.0.0/apparser/cv/readers/yolo.py +69 -0
  32. apparser-1.0.0/apparser/cv/utils/__init__.py +3 -0
  33. apparser-1.0.0/apparser/cv/utils/changes_checker.py +84 -0
  34. apparser-1.0.0/apparser/exceptions/__init__.py +10 -0
  35. apparser-1.0.0/apparser/exceptions/debug.py +14 -0
  36. apparser-1.0.0/apparser/exceptions/instruction_not_found.py +37 -0
  37. apparser-1.0.0/apparser/exceptions/text_not_found.py +18 -0
  38. apparser-1.0.0/apparser/exceptions/timeout.py +13 -0
  39. apparser-1.0.0/apparser/exceptions/window_action_with_desktop.py +6 -0
  40. apparser-1.0.0/apparser/geometry/__init__.py +9 -0
  41. apparser-1.0.0/apparser/geometry/distance.py +21 -0
  42. apparser-1.0.0/apparser/geometry/relatively_point.py +45 -0
  43. apparser-1.0.0/apparser/instructions/__init__.py +4 -0
  44. apparser-1.0.0/apparser/instructions/base.py +24 -0
  45. apparser-1.0.0/apparser/instructions/debuggers/__init__.py +4 -0
  46. apparser-1.0.0/apparser/instructions/debuggers/base.py +23 -0
  47. apparser-1.0.0/apparser/instructions/debuggers/default.py +44 -0
  48. apparser-1.0.0/apparser/instructions/default/__init__.py +23 -0
  49. apparser-1.0.0/apparser/instructions/default/click.py +73 -0
  50. apparser-1.0.0/apparser/instructions/default/play_audio.py +88 -0
  51. apparser-1.0.0/apparser/instructions/default/play_audio_file.py +77 -0
  52. apparser-1.0.0/apparser/instructions/default/press.py +99 -0
  53. apparser-1.0.0/apparser/instructions/default/say_audio.py +92 -0
  54. apparser-1.0.0/apparser/instructions/default/say_audio_file.py +77 -0
  55. apparser-1.0.0/apparser/instructions/default/sleep.py +26 -0
  56. apparser-1.0.0/apparser/instructions/default/write_text.py +36 -0
  57. apparser-1.0.0/apparser/instructions/ocr/__init__.py +16 -0
  58. apparser-1.0.0/apparser/instructions/ocr/base.py +36 -0
  59. apparser-1.0.0/apparser/instructions/ocr/click_on_text.py +52 -0
  60. apparser-1.0.0/apparser/instructions/ocr/move_to_text.py +71 -0
  61. apparser-1.0.0/apparser/instructions/ocr/plot_text.py +81 -0
  62. apparser-1.0.0/apparser/instructions/ocr/print_all_text.py +33 -0
  63. apparser-1.0.0/apparser/instructions/ocr/text_getter.py +92 -0
  64. apparser-1.0.0/apparser/instructions/ocr/wait_text.py +69 -0
  65. apparser-1.0.0/apparser/instructions/speak/__init__.py +5 -0
  66. apparser-1.0.0/apparser/instructions/speak/base.py +34 -0
  67. apparser-1.0.0/apparser/instructions/speak/play_text.py +52 -0
  68. apparser-1.0.0/apparser/instructions/speak/say_text.py +52 -0
  69. apparser-1.0.0/apparser/instructions/ui/__init__.py +15 -0
  70. apparser-1.0.0/apparser/instructions/ui/algorithms/__init__.py +16 -0
  71. apparser-1.0.0/apparser/instructions/ui/algorithms/algorithm.py +60 -0
  72. apparser-1.0.0/apparser/instructions/ui/algorithms/base.py +47 -0
  73. apparser-1.0.0/apparser/instructions/ui/algorithms/ids.py +99 -0
  74. apparser-1.0.0/apparser/instructions/ui/algorithms/names.py +99 -0
  75. apparser-1.0.0/apparser/instructions/ui/algorithms/ocr.py +72 -0
  76. apparser-1.0.0/apparser/instructions/ui/algorithms/speak.py +79 -0
  77. apparser-1.0.0/apparser/instructions/ui/algorithms/unique.py +80 -0
  78. apparser-1.0.0/apparser/instructions/ui/base.py +29 -0
  79. apparser-1.0.0/apparser/instructions/ui/click.py +41 -0
  80. apparser-1.0.0/apparser/instructions/ui/mouse_move.py +38 -0
  81. apparser-1.0.0/apparser/instructions/ui/move_window.py +27 -0
  82. apparser-1.0.0/apparser/instructions/ui/resize_window.py +27 -0
  83. apparser-1.0.0/apparser/instructions/ui/to_window.py +25 -0
  84. apparser-1.0.0/apparser/instructions/utils/__init__.py +7 -0
  85. apparser-1.0.0/apparser/instructions/utils/get_all_instructions.py +21 -0
  86. apparser-1.0.0/apparser/instructions/utils/get_by_id.py +26 -0
  87. apparser-1.0.0/apparser/instructions/utils/get_by_name.py +26 -0
  88. apparser-1.0.0/apparser/key_codes/__init__.py +11 -0
  89. apparser-1.0.0/apparser/key_codes/base.py +14 -0
  90. apparser-1.0.0/apparser/key_codes/keyboard_keys.py +49 -0
  91. apparser-1.0.0/apparser/key_codes/mouse_keys.py +25 -0
  92. apparser-1.0.0/apparser/movers/__init__.py +7 -0
  93. apparser-1.0.0/apparser/movers/base.py +16 -0
  94. apparser-1.0.0/apparser/movers/default.py +34 -0
  95. apparser-1.0.0/apparser/movers/math_antirobot.py +123 -0
  96. apparser-1.0.0/apparser/speakers/__init__.py +5 -0
  97. apparser-1.0.0/apparser/speakers/base.py +18 -0
  98. apparser-1.0.0/apparser/speakers/chat_tts.py +124 -0
  99. apparser-1.0.0/apparser/speakers/torch.py +87 -0
  100. apparser-1.0.0/apparser/text_readers/__init__.py +14 -0
  101. apparser-1.0.0/apparser/text_readers/base.py +20 -0
  102. apparser-1.0.0/apparser/text_readers/easy_ocr.py +42 -0
  103. apparser-1.0.0/apparser/text_readers/models/__init__.py +0 -0
  104. apparser-1.0.0/apparser/text_readers/models/text_data.py +11 -0
  105. apparser-1.0.0/apparser/text_readers/paddle.py +126 -0
  106. apparser-1.0.0/apparser/text_readers/screens_controller.py +40 -0
  107. apparser-1.0.0/apparser/text_readers/white_black_reader.py +30 -0
  108. apparser-1.0.0/apparser.egg-info/PKG-INFO +89 -0
  109. apparser-1.0.0/apparser.egg-info/SOURCES.txt +112 -0
  110. apparser-1.0.0/apparser.egg-info/dependency_links.txt +1 -0
  111. apparser-1.0.0/apparser.egg-info/requires.txt +24 -0
  112. apparser-1.0.0/apparser.egg-info/top_level.txt +1 -0
  113. apparser-1.0.0/pyproject.toml +75 -0
  114. 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.
@@ -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
+ [![Documentation](https://img.shields.io/badge/docs-pages-green)](https://apparser-development.github.io/apparser/)
47
+ [![unit_tests](https://github.com/apparser-development/appwindows/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/apparser-development/appwindows/actions/workflows/unit_tests.yml)
48
+ <br>
49
+ [![Github](https://img.shields.io/badge/github-repo-green)](https://github.com/apparser-development/apparser)
50
+ [![Issues](https://img.shields.io/badge/github-issues-green)](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)!
@@ -0,0 +1,46 @@
1
+ <img src="https://raw.githubusercontent.com/apparser-development/apparser/refs/heads/master/apparser.svg" alt="" width="40%">
2
+
3
+ [![Documentation](https://img.shields.io/badge/docs-pages-green)](https://apparser-development.github.io/apparser/)
4
+ [![unit_tests](https://github.com/apparser-development/appwindows/actions/workflows/unit_tests.yml/badge.svg)](https://github.com/apparser-development/appwindows/actions/workflows/unit_tests.yml)
5
+ <br>
6
+ [![Github](https://img.shields.io/badge/github-repo-green)](https://github.com/apparser-development/apparser)
7
+ [![Issues](https://img.shields.io/badge/github-issues-green)](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,4 @@
1
+ from apparser.core.app import App
2
+ from apparser.core.ui import BaseUi, DesktopUi, CoordinatesUi, WindowUi, WindowByDisplayUi
3
+
4
+ __all__ = ["App", "BaseUi", "DesktopUi", "CoordinatesUi", "WindowUi", "WindowByDisplayUi"]
@@ -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