pyjab 1.2.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.
- pyjab/__init__.py +14 -0
- pyjab/accessibleinfo.py +183 -0
- pyjab/common/__init__.py +0 -0
- pyjab/common/actorscheduler.py +48 -0
- pyjab/common/by.py +18 -0
- pyjab/common/exceptions.py +21 -0
- pyjab/common/logger.py +31 -0
- pyjab/common/role.py +71 -0
- pyjab/common/service.py +95 -0
- pyjab/common/shortcutkeys.py +164 -0
- pyjab/common/singleton.py +13 -0
- pyjab/common/states.py +28 -0
- pyjab/common/textreader.py +47 -0
- pyjab/common/types.py +8 -0
- pyjab/common/win32utils.py +459 -0
- pyjab/common/xpathparser.py +65 -0
- pyjab/config.py +260 -0
- pyjab/jabdriver.py +652 -0
- pyjab/jabelement.py +1759 -0
- pyjab/jabfixedfunc.py +343 -0
- pyjab-1.2.0.dist-info/METADATA +377 -0
- pyjab-1.2.0.dist-info/RECORD +25 -0
- pyjab-1.2.0.dist-info/WHEEL +5 -0
- pyjab-1.2.0.dist-info/licenses/LICENSE +339 -0
- pyjab-1.2.0.dist-info/top_level.txt +1 -0
pyjab/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python implementation for Java application UI automation with Java Access Bridge.
|
|
3
|
+
"""
|
|
4
|
+
__author__ = "Gary Gao"
|
|
5
|
+
__email__ = "gaozhao89@qq.com"
|
|
6
|
+
__license__ = "GPLv2"
|
|
7
|
+
__url__ = "https://github.com/gaozhao1989/pyjab"
|
|
8
|
+
__version__ = "1.2.0"
|
|
9
|
+
|
|
10
|
+
# NOTE: no platform guard here on purpose. pyjab.config and pyjab.common.service
|
|
11
|
+
# are platform independent and are unit tested on every OS, so `import pyjab`
|
|
12
|
+
# must stay harmless. The Windows-only entry points (pyjab.jabdriver,
|
|
13
|
+
# pyjab.jabelement) raise a descriptive ImportError instead of letting a bare
|
|
14
|
+
# "No module named 'win32process'" escape.
|
pyjab/accessibleinfo.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
from ctypes import c_float
|
|
2
|
+
from ctypes import c_int
|
|
3
|
+
from ctypes import c_wchar
|
|
4
|
+
from ctypes import c_bool
|
|
5
|
+
from ctypes import Structure
|
|
6
|
+
from ctypes.wintypes import BOOL
|
|
7
|
+
from ctypes.wintypes import WCHAR
|
|
8
|
+
from pyjab.common.types import JOBJECT64
|
|
9
|
+
from pyjab.config import (
|
|
10
|
+
MAX_STRING_SIZE,
|
|
11
|
+
MAX_VISIBLE_CHILDREN,
|
|
12
|
+
SHORT_STRING_SIZE,
|
|
13
|
+
MAX_KEY_BINDINGS,
|
|
14
|
+
MAX_RELATION_TARGETS,
|
|
15
|
+
MAX_RELATIONS,
|
|
16
|
+
MAX_ACTION_INFO,
|
|
17
|
+
MAX_ACTIONS_TO_DO,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AccessBridgeVersionInfo(Structure):
|
|
22
|
+
_fields_ = [
|
|
23
|
+
("VMVersion", WCHAR * SHORT_STRING_SIZE),
|
|
24
|
+
("bridgeJavaClassVersion", WCHAR * SHORT_STRING_SIZE),
|
|
25
|
+
("bridgeJavaDLLVersion", WCHAR * SHORT_STRING_SIZE),
|
|
26
|
+
("bridgeWinDLLVersion", WCHAR * SHORT_STRING_SIZE),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AccessibleContextInfo(Structure):
|
|
31
|
+
_fields_ = [
|
|
32
|
+
("name", WCHAR * MAX_STRING_SIZE),
|
|
33
|
+
("description", WCHAR * MAX_STRING_SIZE),
|
|
34
|
+
("role", WCHAR * SHORT_STRING_SIZE),
|
|
35
|
+
("role_en_US", WCHAR * SHORT_STRING_SIZE),
|
|
36
|
+
("states", WCHAR * SHORT_STRING_SIZE),
|
|
37
|
+
("states_en_US", WCHAR * SHORT_STRING_SIZE),
|
|
38
|
+
("indexInParent", c_int),
|
|
39
|
+
("childrenCount", c_int),
|
|
40
|
+
("x", c_int),
|
|
41
|
+
("y", c_int),
|
|
42
|
+
("width", c_int),
|
|
43
|
+
("height", c_int),
|
|
44
|
+
("accessibleComponent", BOOL),
|
|
45
|
+
("accessibleAction", BOOL),
|
|
46
|
+
("accessibleSelection", BOOL),
|
|
47
|
+
("accessibleText", BOOL),
|
|
48
|
+
("accessibleValue", BOOL),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class AccessibleTextInfo(Structure):
|
|
53
|
+
_fields_ = [
|
|
54
|
+
("charCount", c_int),
|
|
55
|
+
("caretIndex", c_int),
|
|
56
|
+
("indexAtPoint", c_int),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class AccessibleTextItemsInfo(Structure):
|
|
61
|
+
_fields_ = [
|
|
62
|
+
("letter", WCHAR),
|
|
63
|
+
("word", WCHAR * SHORT_STRING_SIZE),
|
|
64
|
+
("sentence", WCHAR * MAX_STRING_SIZE),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AccessibleTextSelectionInfo(Structure):
|
|
69
|
+
_fields_ = [
|
|
70
|
+
("selectionStartIndex", c_int),
|
|
71
|
+
("selectionEndIndex", c_int),
|
|
72
|
+
("selectedText", WCHAR * MAX_STRING_SIZE),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AccessibleTextRectInfo(Structure):
|
|
77
|
+
_fields_ = [
|
|
78
|
+
("x", c_int),
|
|
79
|
+
("y", c_int),
|
|
80
|
+
("width", c_int),
|
|
81
|
+
("height", c_int),
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class AccessibleTextAttributesInfo(Structure):
|
|
86
|
+
_fields_ = [
|
|
87
|
+
("bold", BOOL),
|
|
88
|
+
("italic", BOOL),
|
|
89
|
+
("underline", BOOL),
|
|
90
|
+
("strikethrough", BOOL),
|
|
91
|
+
("superscript", BOOL),
|
|
92
|
+
("subscript", BOOL),
|
|
93
|
+
("backgroundColor", WCHAR * SHORT_STRING_SIZE),
|
|
94
|
+
("foregroundColor", WCHAR * SHORT_STRING_SIZE),
|
|
95
|
+
("fontFamily", WCHAR * SHORT_STRING_SIZE),
|
|
96
|
+
("fontSize", c_int),
|
|
97
|
+
("alignment", c_int),
|
|
98
|
+
("bidiLevel", c_int),
|
|
99
|
+
("firstLineIndent", c_float),
|
|
100
|
+
("LeftIndent", c_float),
|
|
101
|
+
("rightIndent", c_float),
|
|
102
|
+
("lineSpacing", c_float),
|
|
103
|
+
("spaceAbove", c_float),
|
|
104
|
+
("spaceBelow", c_float),
|
|
105
|
+
("fullAttributesString", WCHAR * MAX_STRING_SIZE),
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class AccessibleRelationInfo(Structure):
|
|
110
|
+
_fields_ = [
|
|
111
|
+
("key", WCHAR * SHORT_STRING_SIZE),
|
|
112
|
+
("targetCount", c_int),
|
|
113
|
+
("targets", JOBJECT64 * MAX_RELATION_TARGETS),
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class AccessibleRelationSetInfo(Structure):
|
|
118
|
+
_fields_ = [
|
|
119
|
+
("relationCount", c_int),
|
|
120
|
+
("relations", AccessibleRelationInfo * MAX_RELATIONS),
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class AccessibleActionInfo(Structure):
|
|
125
|
+
_fields_ = (("name", c_wchar * SHORT_STRING_SIZE),)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class AccessibleActions(Structure):
|
|
129
|
+
_fields_ = (
|
|
130
|
+
("actionsCount", c_int),
|
|
131
|
+
("actionInfo", AccessibleActionInfo * MAX_ACTION_INFO),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class AccessibleActionsToDo(Structure):
|
|
136
|
+
_fields_ = (
|
|
137
|
+
("actionsCount", c_int),
|
|
138
|
+
("actions", AccessibleActionInfo * MAX_ACTIONS_TO_DO),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class AccessibleTableInfo(Structure):
|
|
143
|
+
_fields_ = [
|
|
144
|
+
("caption", JOBJECT64),
|
|
145
|
+
("summary", JOBJECT64),
|
|
146
|
+
("rowCount", c_int),
|
|
147
|
+
("columnCount", c_int),
|
|
148
|
+
("accessibleContext", JOBJECT64),
|
|
149
|
+
("accessibleTable", JOBJECT64),
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class AccessibleTableCellInfo(Structure):
|
|
154
|
+
_fields_ = [
|
|
155
|
+
("accessibleContext", JOBJECT64),
|
|
156
|
+
("index", c_int),
|
|
157
|
+
("row", c_int),
|
|
158
|
+
("column", c_int),
|
|
159
|
+
("rowExtent", c_int),
|
|
160
|
+
("columnExtent", c_int),
|
|
161
|
+
("isSelected", c_bool),
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class AccessibleKeyBindingInfo(Structure):
|
|
166
|
+
_fields_ = [
|
|
167
|
+
("character", c_wchar),
|
|
168
|
+
("modifiers", c_int),
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class AccessibleKeyBindings(Structure):
|
|
173
|
+
_fields_ = [
|
|
174
|
+
("keyBindingsCount", c_int),
|
|
175
|
+
("keyBindingInfo", AccessibleKeyBindingInfo * MAX_KEY_BINDINGS),
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class VisibleChildrenInfo(Structure):
|
|
180
|
+
_fields_ = [
|
|
181
|
+
("returnedChildrenCount", c_int),
|
|
182
|
+
("children", JOBJECT64 * MAX_VISIBLE_CHILDREN),
|
|
183
|
+
]
|
pyjab/common/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from pyjab.common.logger import Logger
|
|
3
|
+
from pyjab.common.singleton import singleton
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@singleton
|
|
7
|
+
class ActorScheduler:
|
|
8
|
+
"""Message queue for run generator func as thread.
|
|
9
|
+
|
|
10
|
+
Sample:
|
|
11
|
+
|
|
12
|
+
sched = ActorScheduler()
|
|
13
|
+
sched.new_actor("jab", win32utils.setup_msg_pump())
|
|
14
|
+
sched.run()
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.actors = {} # Mapping of names to actors
|
|
19
|
+
self.msg_queue = deque() # Message queue
|
|
20
|
+
self.logger = Logger("pyjab")
|
|
21
|
+
|
|
22
|
+
def new_actor(self, name, actor):
|
|
23
|
+
"""
|
|
24
|
+
Admit a newly started actor to the scheduler and give it a name
|
|
25
|
+
"""
|
|
26
|
+
self.logger.debug(f"msg queue append new actor '{name}'")
|
|
27
|
+
self.msg_queue.append((actor, None))
|
|
28
|
+
self.actors[name] = actor
|
|
29
|
+
|
|
30
|
+
def send(self, name, msg):
|
|
31
|
+
"""
|
|
32
|
+
Send a message to a named actor
|
|
33
|
+
"""
|
|
34
|
+
if actor := self.actors.get(name):
|
|
35
|
+
self.logger.debug(f"send msg '{msg}' to actor '{actor}'")
|
|
36
|
+
self.msg_queue.append((actor, msg))
|
|
37
|
+
|
|
38
|
+
def run(self):
|
|
39
|
+
"""
|
|
40
|
+
Run as long as there are pending messages.
|
|
41
|
+
"""
|
|
42
|
+
while self.msg_queue:
|
|
43
|
+
actor, msg = self.msg_queue.popleft()
|
|
44
|
+
try:
|
|
45
|
+
self.logger.debug(f"run actor '{actor}' with msg '{msg}'")
|
|
46
|
+
actor.send(msg)
|
|
47
|
+
except StopIteration:
|
|
48
|
+
self.logger.debug("stop run action in scheduler")
|
pyjab/common/by.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The By implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class By(object):
|
|
7
|
+
"""
|
|
8
|
+
Set of supported locator strategies.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
XPATH = "xpath"
|
|
12
|
+
NAME = "name"
|
|
13
|
+
DESCRIPTION = "description"
|
|
14
|
+
ROLE = "role"
|
|
15
|
+
STATES = "states"
|
|
16
|
+
OBJECT_DEPTH = "objectdepth"
|
|
17
|
+
CHILDREN_COUNT = "childrencount"
|
|
18
|
+
INDEX_IN_PARENT = "indexinparent"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class CommonException(Exception):
|
|
2
|
+
def __init__(self, message: str = None, status: str = None) -> None:
|
|
3
|
+
super().__init__(message, status)
|
|
4
|
+
self.message = message
|
|
5
|
+
self.status = status
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class JABException(CommonException):
|
|
9
|
+
"""
|
|
10
|
+
Raised by Java Access Bridge if func internal error
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class XpathParserException(CommonException):
|
|
17
|
+
"""
|
|
18
|
+
Raised by Xpath Parser if error
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
pass
|
pyjab/common/logger.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pyjab.common.singleton import singleton
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@singleton
|
|
6
|
+
class Logger(object):
|
|
7
|
+
LOGGER_INFO = logging.INFO
|
|
8
|
+
LOGGER_DEBUG = logging.DEBUG
|
|
9
|
+
LOGGER_WARN = logging.WARN
|
|
10
|
+
LOGGER_ERROR = logging.ERROR
|
|
11
|
+
LOGGER_CRITICAL = logging.CRITICAL
|
|
12
|
+
|
|
13
|
+
def __init__(self, name=None, level=logging.INFO):
|
|
14
|
+
self.FORMAT = "%(asctime)-15s %(levelname)s %(name)s %(message)s"
|
|
15
|
+
self.log = logging.getLogger(name)
|
|
16
|
+
logging.basicConfig(format=self.FORMAT, level=level)
|
|
17
|
+
|
|
18
|
+
def info(self, msg, *args, **kwargs):
|
|
19
|
+
self.log.info(msg, *args, **kwargs)
|
|
20
|
+
|
|
21
|
+
def debug(self, msg, *args, **kwargs):
|
|
22
|
+
self.log.debug(msg, *args, **kwargs)
|
|
23
|
+
|
|
24
|
+
def warning(self, msg, *args, **kwargs):
|
|
25
|
+
self.log.warning(msg, *args, **kwargs)
|
|
26
|
+
|
|
27
|
+
def error(self, msg, *args, **kwargs):
|
|
28
|
+
self.log.error(msg, *args, **kwargs)
|
|
29
|
+
|
|
30
|
+
def critical(self, msg, *args, **kwargs):
|
|
31
|
+
self.log.critical(msg, *args, **kwargs)
|
pyjab/common/role.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
The Role implementation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Role(str, Enum):
|
|
9
|
+
"""
|
|
10
|
+
Set of supported role strategies.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
ALERT = "alert"
|
|
14
|
+
CANVAS = "canvas"
|
|
15
|
+
CHECK_BOX = "check box"
|
|
16
|
+
COLOR_CHOOSER = "color chooser"
|
|
17
|
+
COLUMN_HEADER = "column header"
|
|
18
|
+
COMBO_BOX = "combo box"
|
|
19
|
+
DATE_EDITOR = "date editor"
|
|
20
|
+
DESKTOP_ICON = "desktop icon"
|
|
21
|
+
DESKTOP_PANE = "desktop pane"
|
|
22
|
+
DIALOG = "dialog"
|
|
23
|
+
DIRECTORY_PANE = "directory pane"
|
|
24
|
+
EDIT_BAR = "edit bar"
|
|
25
|
+
FILE_CHOOSER = "file chooser"
|
|
26
|
+
FILLER = "filler"
|
|
27
|
+
FONT_CHOOSER = "font chooser"
|
|
28
|
+
FOOTER = "footer"
|
|
29
|
+
FRAME = "frame"
|
|
30
|
+
GLASS_PANE = "glass pane"
|
|
31
|
+
GROUP_BOX = "group box"
|
|
32
|
+
HEADER = "header"
|
|
33
|
+
HYPERLINK = "hyperlink"
|
|
34
|
+
ICON = "icon"
|
|
35
|
+
INTERNAL_FRAME = "internal frame"
|
|
36
|
+
LABEL = "label"
|
|
37
|
+
LAYERED_PANE = "layered pane"
|
|
38
|
+
LIST = "list"
|
|
39
|
+
LIST_ITEM = "list item"
|
|
40
|
+
MENU = "menu"
|
|
41
|
+
MENU_BAR = "menu bar"
|
|
42
|
+
MENU_ITEM = "menu item"
|
|
43
|
+
OPTION_PANE = "option pane"
|
|
44
|
+
PAGE_TAB = "page tab"
|
|
45
|
+
PAGE_TAB_LIST = "page tab list"
|
|
46
|
+
PANEL = "panel"
|
|
47
|
+
PARAGRAPH = "paragraph"
|
|
48
|
+
PASSWORD_TEXT = "password text"
|
|
49
|
+
POPUP_MENU = "popup menu"
|
|
50
|
+
PROGRESS_BAR = "progress bar"
|
|
51
|
+
PUSH_BUTTON = "push button"
|
|
52
|
+
RADIO_BUTTON = "radio button"
|
|
53
|
+
ROOT_PANE = "root pane"
|
|
54
|
+
ROW_HEADER = "row header"
|
|
55
|
+
RULER = "ruler"
|
|
56
|
+
SCROLL_BAR = "scroll bar"
|
|
57
|
+
SCROLL_PANE = "scroll pane"
|
|
58
|
+
SEPARATOR = "separator"
|
|
59
|
+
SLIDER = "slider"
|
|
60
|
+
SPIN_BOX = "spinbox"
|
|
61
|
+
SPLIT_PANE = "split pane"
|
|
62
|
+
STATUS_BAR = "status bar"
|
|
63
|
+
TABLE = "table"
|
|
64
|
+
TEXT = "text"
|
|
65
|
+
TOGGLE_BUTTON = "toggle button"
|
|
66
|
+
TOOL_BAR = "tool bar"
|
|
67
|
+
TOOL_TIP = "tool tip"
|
|
68
|
+
TREE = "tree"
|
|
69
|
+
UNKNOWN = "unknown"
|
|
70
|
+
VIEW_PORT = "viewport"
|
|
71
|
+
WINDOW = "window"
|
pyjab/common/service.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from ctypes import cdll
|
|
2
|
+
from ctypes import CDLL
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pyjab.common.logger import Logger
|
|
7
|
+
from pyjab.common.singleton import singleton
|
|
8
|
+
from pyjab.config import A11Y_PROPS_CONTENT
|
|
9
|
+
from pyjab.config import A11Y_PROPS_PATH
|
|
10
|
+
from pyjab.config import describe_bridge_dll_search
|
|
11
|
+
from pyjab.config import find_bridge_dll
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@singleton
|
|
15
|
+
class Service(object):
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self.logger = Logger("pyjab")
|
|
18
|
+
self.init_bridge()
|
|
19
|
+
|
|
20
|
+
def enable_bridge(self) -> None:
|
|
21
|
+
with open(A11Y_PROPS_PATH, "wt") as fp:
|
|
22
|
+
try:
|
|
23
|
+
self.logger.debug("enable bridge")
|
|
24
|
+
fp.write(A11Y_PROPS_CONTENT)
|
|
25
|
+
except (OSError, IOError):
|
|
26
|
+
self.logger.error("enable bridge failed")
|
|
27
|
+
|
|
28
|
+
def is_bridge_enabled(self) -> bool:
|
|
29
|
+
if not Path(A11Y_PROPS_PATH).is_file():
|
|
30
|
+
return False
|
|
31
|
+
with open(A11Y_PROPS_PATH, "rt") as fp:
|
|
32
|
+
try:
|
|
33
|
+
data = fp.read()
|
|
34
|
+
except (OSError, IOError):
|
|
35
|
+
self.logger.error("bridge is not enabled")
|
|
36
|
+
return False
|
|
37
|
+
is_enabled = data == A11Y_PROPS_CONTENT
|
|
38
|
+
self.logger.debug("is bridge enabled => '{}'".format(is_enabled))
|
|
39
|
+
return is_enabled
|
|
40
|
+
|
|
41
|
+
def init_bridge(self) -> None:
|
|
42
|
+
self.logger.debug("init bridge")
|
|
43
|
+
if not self.is_bridge_enabled():
|
|
44
|
+
self.enable_bridge()
|
|
45
|
+
|
|
46
|
+
def find_bridge_dll(self, bridge_dll: Optional[str] = None) -> Optional[Path]:
|
|
47
|
+
"""Locate the Java Access Bridge DLL without loading it.
|
|
48
|
+
|
|
49
|
+
Useful for diagnostics: it reports *where* the DLL would be loaded from
|
|
50
|
+
without actually loading it into the process.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
bridge_dll: An explicit DLL path supplied by the caller. When given
|
|
54
|
+
and valid it takes precedence over all automatic discovery.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
The path to the DLL, or ``None`` when it could not be located.
|
|
58
|
+
"""
|
|
59
|
+
if bridge_dll:
|
|
60
|
+
explicit = Path(str(bridge_dll))
|
|
61
|
+
if explicit.is_file():
|
|
62
|
+
return explicit
|
|
63
|
+
self.logger.warning(
|
|
64
|
+
"The explicit bridge_dll path does not exist: '{}'. "
|
|
65
|
+
"Falling back to automatic discovery.".format(bridge_dll)
|
|
66
|
+
)
|
|
67
|
+
return find_bridge_dll()
|
|
68
|
+
|
|
69
|
+
def load_library(self, bridge_dll: str = "") -> CDLL:
|
|
70
|
+
"""Load the Java Access Bridge DLL.
|
|
71
|
+
|
|
72
|
+
Resolution order:
|
|
73
|
+
|
|
74
|
+
1. The ``bridge_dll`` argument, when it points at an existing file.
|
|
75
|
+
2. :func:`pyjab.config.find_bridge_dll`, which probes
|
|
76
|
+
``%JAVA_HOME%\\bin`` (JDK 11+), ``%JAVA_HOME%\\jre\\bin`` (JDK 8-10),
|
|
77
|
+
``%JRE_HOME%``, ``%JAB_HOME%``, common vendor install locations and
|
|
78
|
+
finally performs a bounded recursive search.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
bridge_dll: Explicit path to ``WindowsAccessBridge-XX.dll``.
|
|
82
|
+
Defaults to "" (automatic discovery).
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
FileNotFoundError: When no DLL could be located. The message lists
|
|
86
|
+
every location that was probed and how to fix the situation.
|
|
87
|
+
"""
|
|
88
|
+
self.logger.debug("load library of bridge")
|
|
89
|
+
|
|
90
|
+
resolved = self.find_bridge_dll(bridge_dll)
|
|
91
|
+
if resolved is None:
|
|
92
|
+
raise FileNotFoundError(describe_bridge_dll_search())
|
|
93
|
+
|
|
94
|
+
self.logger.debug("Loading Java Access Bridge DLL from '{}'".format(resolved))
|
|
95
|
+
return cdll.LoadLibrary(str(resolved))
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from pyjab.common.logger import Logger
|
|
2
|
+
from pyjab.common.win32utils import Win32Utils
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ShortcutKeys(object):
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
self.logger = Logger("pyjab")
|
|
8
|
+
self.win32_utils = Win32Utils()
|
|
9
|
+
self.press_hold_release_key = self.win32_utils._press_hold_release_key
|
|
10
|
+
self.press_key = self.win32_utils._press_key
|
|
11
|
+
|
|
12
|
+
# Common shortcut keys
|
|
13
|
+
def clear_text(self) -> None:
|
|
14
|
+
self.logger.debug("send shortcut command clear text")
|
|
15
|
+
self.press_key("home")
|
|
16
|
+
self.press_hold_release_key("ctrl", "shift", "end")
|
|
17
|
+
# TODO: need find out method to clear text for JABElement
|
|
18
|
+
for _ in range(100):
|
|
19
|
+
self.press_key("backspace")
|
|
20
|
+
|
|
21
|
+
def paste(self) -> None:
|
|
22
|
+
self.logger.debug("paste text from clipboard")
|
|
23
|
+
self.press_hold_release_key("ctrl", "v")
|
|
24
|
+
|
|
25
|
+
# Oracle Form shortcut keys
|
|
26
|
+
def block_menu(self) -> None:
|
|
27
|
+
self.logger.debug("send shortcut command block menu")
|
|
28
|
+
self.press_hold_release_key("ctrl", "b")
|
|
29
|
+
|
|
30
|
+
def clear_block(self) -> None:
|
|
31
|
+
self.logger.debug("send shortcut command clear block")
|
|
32
|
+
self.press_key("F7")
|
|
33
|
+
|
|
34
|
+
def clear_field(self) -> None:
|
|
35
|
+
self.logger.debug("send shortcut command clear field")
|
|
36
|
+
self.press_key("F5")
|
|
37
|
+
|
|
38
|
+
def clear_form(self) -> None:
|
|
39
|
+
self.logger.debug("send shortcut command clear form")
|
|
40
|
+
self.press_key("F8")
|
|
41
|
+
|
|
42
|
+
def clear_record(self) -> None:
|
|
43
|
+
self.logger.debug("send shortcut command clear record")
|
|
44
|
+
self.press_key("F6")
|
|
45
|
+
|
|
46
|
+
def commit(self) -> None:
|
|
47
|
+
self.logger.debug("send shortcut command commit")
|
|
48
|
+
self.press_hold_release_key("ctrl", "s")
|
|
49
|
+
|
|
50
|
+
def count_query(self) -> None:
|
|
51
|
+
self.logger.debug("send shortcut command count query")
|
|
52
|
+
self.press_key("F12")
|
|
53
|
+
|
|
54
|
+
def delete_record(self) -> None:
|
|
55
|
+
self.logger.debug("send shortcut command delete record")
|
|
56
|
+
self.press_hold_release_key("ctrl", "up_arrow")
|
|
57
|
+
|
|
58
|
+
def display_error(self) -> None:
|
|
59
|
+
self.logger.debug("send shortcut command display error")
|
|
60
|
+
self.press_hold_release_key("shift", "ctrl", "e")
|
|
61
|
+
|
|
62
|
+
def down(self) -> None:
|
|
63
|
+
self.logger.debug("send shortcut command down")
|
|
64
|
+
self.press_key("down_arrow")
|
|
65
|
+
|
|
66
|
+
def duplicate_field(self) -> None:
|
|
67
|
+
self.logger.debug("send shortcut command duplicate field")
|
|
68
|
+
self.press_hold_release_key("shift", "F5")
|
|
69
|
+
|
|
70
|
+
def duplicate_record(self) -> None:
|
|
71
|
+
self.logger.debug("send shortcut command duplicate record")
|
|
72
|
+
self.press_hold_release_key("shift", "F6")
|
|
73
|
+
|
|
74
|
+
def edit(self) -> None:
|
|
75
|
+
self.logger.debug("send shortcut command edit")
|
|
76
|
+
self.press_hold_release_key("ctrl", "e")
|
|
77
|
+
|
|
78
|
+
def enter_query(self) -> None:
|
|
79
|
+
self.logger.debug("send shortcut command enter query")
|
|
80
|
+
self.press_key("F11")
|
|
81
|
+
|
|
82
|
+
def execute_query(self) -> None:
|
|
83
|
+
self.logger.debug("send shortcut command execute query")
|
|
84
|
+
self.press_hold_release_key("ctrl", "F11")
|
|
85
|
+
|
|
86
|
+
def _exit(self) -> None:
|
|
87
|
+
self.logger.debug("send shortcut command exit")
|
|
88
|
+
self.press_key("F4")
|
|
89
|
+
|
|
90
|
+
def _help(self) -> None:
|
|
91
|
+
self.logger.debug("send shortcut command help")
|
|
92
|
+
self.press_hold_release_key("ctrl", "h")
|
|
93
|
+
|
|
94
|
+
def insert_record(self) -> None:
|
|
95
|
+
self.logger.debug("send shortcut command insert record")
|
|
96
|
+
self.press_hold_release_key("ctrl", "down_arrow")
|
|
97
|
+
|
|
98
|
+
def list_of_values(self) -> None:
|
|
99
|
+
self.logger.debug("send shortcut command list of values")
|
|
100
|
+
self.press_hold_release_key("ctrl", "l")
|
|
101
|
+
|
|
102
|
+
def list_tab_pages(self) -> None:
|
|
103
|
+
self.logger.debug("send shortcut command list tab pages")
|
|
104
|
+
self.press_key("F2")
|
|
105
|
+
|
|
106
|
+
def next_block(self) -> None:
|
|
107
|
+
self.logger.debug("send shortcut command next block")
|
|
108
|
+
self.press_hold_release_key("shift", "page_down")
|
|
109
|
+
|
|
110
|
+
def next_field(self) -> None:
|
|
111
|
+
self.logger.debug("send shortcut command next field")
|
|
112
|
+
self.press_key("tab")
|
|
113
|
+
|
|
114
|
+
def next_primary_key(self) -> None:
|
|
115
|
+
self.logger.debug("send shortcut command next primary key")
|
|
116
|
+
self.press_hold_release_key("shift", "F7")
|
|
117
|
+
|
|
118
|
+
def next_record(self) -> None:
|
|
119
|
+
self.logger.debug("send shortcut command next record")
|
|
120
|
+
self.press_key("down_arrow")
|
|
121
|
+
|
|
122
|
+
def next_set_of_records(self) -> None:
|
|
123
|
+
self.logger.debug("send shortcut command next set of records")
|
|
124
|
+
self.press_key("shift", "F8")
|
|
125
|
+
|
|
126
|
+
def previous_block(self) -> None:
|
|
127
|
+
self.logger.debug("send shortcut command previous block")
|
|
128
|
+
self.press_key("shift", "page_up")
|
|
129
|
+
|
|
130
|
+
def previous_field(self) -> None:
|
|
131
|
+
self.logger.debug("send shortcut command previous field")
|
|
132
|
+
self.press_key("shift", "tab")
|
|
133
|
+
|
|
134
|
+
def previous_record(self) -> None:
|
|
135
|
+
self.logger.debug("send shortcut command previous record")
|
|
136
|
+
self.press_key("shift", "up_arrow")
|
|
137
|
+
|
|
138
|
+
def _print(self) -> None:
|
|
139
|
+
self.logger.debug("send shortcut command print")
|
|
140
|
+
self.press_key("ctrl", "p")
|
|
141
|
+
|
|
142
|
+
def _return(self) -> None:
|
|
143
|
+
self.logger.debug("send shortcut command return")
|
|
144
|
+
self.press_key("clear")
|
|
145
|
+
|
|
146
|
+
def scroll_down(self) -> None:
|
|
147
|
+
self.logger.debug("send shortcut command scroll down")
|
|
148
|
+
self.press_key("page_down")
|
|
149
|
+
|
|
150
|
+
def scroll_up(self) -> None:
|
|
151
|
+
self.logger.debug("send shortcut command scroll up")
|
|
152
|
+
self.press_key("page_up")
|
|
153
|
+
|
|
154
|
+
def show_keys(self) -> None:
|
|
155
|
+
self.logger.debug("send shortcut command show keys")
|
|
156
|
+
self.press_key("ctrl", "k")
|
|
157
|
+
|
|
158
|
+
def up(self) -> None:
|
|
159
|
+
self.logger.debug("send shortcut command up")
|
|
160
|
+
self.press_key("up_arrow")
|
|
161
|
+
|
|
162
|
+
def update_record(self) -> None:
|
|
163
|
+
self.logger.debug("send shortcut command update record")
|
|
164
|
+
self.press_key("ctrl", "u")
|
pyjab/common/states.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
The States implementation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class States(str, Enum):
|
|
9
|
+
"""
|
|
10
|
+
Set of supported states strategies.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
BUSY = "busy"
|
|
14
|
+
CHECKED = "checked"
|
|
15
|
+
ENABLED = "enabled"
|
|
16
|
+
FOCUSED = "focused"
|
|
17
|
+
SELECTED = "selected"
|
|
18
|
+
PRESSED = "pressed"
|
|
19
|
+
EXPANDED = "expanded"
|
|
20
|
+
COLLAPSED = "collapsed"
|
|
21
|
+
ICONIFIED = "iconified"
|
|
22
|
+
MODAL = "modal"
|
|
23
|
+
MULTI_LINE = "multi_line"
|
|
24
|
+
FOCUSABLE = "focusable"
|
|
25
|
+
EDITABLE = "editable"
|
|
26
|
+
VISIBLE = "visible"
|
|
27
|
+
SHOWING = "showing"
|
|
28
|
+
UNKNOWN = "unknown"
|