windows-mcp 0.5.7__py3-none-any.whl → 0.5.8__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.
- windows_mcp/__main__.py +314 -312
- windows_mcp/analytics.py +175 -171
- windows_mcp/desktop/config.py +20 -20
- windows_mcp/desktop/service.py +457 -457
- windows_mcp/desktop/views.py +57 -57
- windows_mcp/tree/config.py +50 -50
- windows_mcp/tree/service.py +600 -466
- windows_mcp/tree/utils.py +21 -21
- windows_mcp/tree/views.py +115 -115
- windows_mcp/uia/__init__.py +4 -0
- windows_mcp/uia/controls.py +4781 -0
- windows_mcp/uia/core.py +3269 -0
- windows_mcp/uia/enums.py +1963 -0
- windows_mcp/uia/events.py +83 -0
- windows_mcp/uia/patterns.py +2106 -0
- windows_mcp/watchdog/__init__.py +1 -0
- windows_mcp/watchdog/event_handlers.py +51 -0
- windows_mcp/watchdog/service.py +188 -0
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/METADATA +4 -4
- windows_mcp-0.5.8.dist-info/RECORD +26 -0
- windows_mcp-0.5.7.dist-info/RECORD +0 -17
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/WHEEL +0 -0
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/entry_points.txt +0 -0
- {windows_mcp-0.5.7.dist-info → windows_mcp-0.5.8.dist-info}/licenses/LICENSE.md +0 -0
windows_mcp/tree/utils.py
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import random
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
def random_point_within_bounding_box(node: Control, scale_factor: float = 1.0) -> tuple[int, int]:
|
|
5
|
-
"""
|
|
6
|
-
Generate a random point within a scaled-down bounding box.
|
|
7
|
-
|
|
8
|
-
Args:
|
|
9
|
-
node (Control): The node with a bounding rectangle
|
|
10
|
-
scale_factor (float, optional): The factor to scale down the bounding box. Defaults to 1.0.
|
|
11
|
-
|
|
12
|
-
Returns:
|
|
13
|
-
tuple: A random point (x, y) within the scaled-down bounding box
|
|
14
|
-
"""
|
|
15
|
-
box = node.BoundingRectangle
|
|
16
|
-
scaled_width = int(box.width() * scale_factor)
|
|
17
|
-
scaled_height = int(box.height() * scale_factor)
|
|
18
|
-
scaled_left = box.left + (box.width() - scaled_width) // 2
|
|
19
|
-
scaled_top = box.top + (box.height() - scaled_height) // 2
|
|
20
|
-
x = random.randint(scaled_left, scaled_left + scaled_width)
|
|
21
|
-
y = random.randint(scaled_top, scaled_top + scaled_height)
|
|
1
|
+
import random
|
|
2
|
+
from windows_mcp.uia import Control
|
|
3
|
+
|
|
4
|
+
def random_point_within_bounding_box(node: Control, scale_factor: float = 1.0) -> tuple[int, int]:
|
|
5
|
+
"""
|
|
6
|
+
Generate a random point within a scaled-down bounding box.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
node (Control): The node with a bounding rectangle
|
|
10
|
+
scale_factor (float, optional): The factor to scale down the bounding box. Defaults to 1.0.
|
|
11
|
+
|
|
12
|
+
Returns:
|
|
13
|
+
tuple: A random point (x, y) within the scaled-down bounding box
|
|
14
|
+
"""
|
|
15
|
+
box = node.BoundingRectangle
|
|
16
|
+
scaled_width = int(box.width() * scale_factor)
|
|
17
|
+
scaled_height = int(box.height() * scale_factor)
|
|
18
|
+
scaled_left = box.left + (box.width() - scaled_width) // 2
|
|
19
|
+
scaled_top = box.top + (box.height() - scaled_height) // 2
|
|
20
|
+
x = random.randint(scaled_left, scaled_left + scaled_width)
|
|
21
|
+
y = random.randint(scaled_top, scaled_top + scaled_height)
|
|
22
22
|
return (x, y)
|
windows_mcp/tree/views.py
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
from dataclasses import dataclass,field
|
|
2
|
-
from tabulate import tabulate
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
|
-
@dataclass
|
|
6
|
-
class DOMInfo:
|
|
7
|
-
horizontal_scrollable: bool
|
|
8
|
-
horizontal_scroll_percent: float
|
|
9
|
-
vertical_scrollable: bool
|
|
10
|
-
vertical_scroll_percent: float
|
|
11
|
-
|
|
12
|
-
@dataclass
|
|
13
|
-
class TreeState:
|
|
14
|
-
interactive_nodes:list['TreeElementNode']=field(default_factory=list)
|
|
15
|
-
scrollable_nodes:list['ScrollElementNode']=field(default_factory=list)
|
|
16
|
-
dom_informative_nodes:list['TextElementNode']=field(default_factory=list)
|
|
17
|
-
dom_info:Optional['DOMInfo']=None
|
|
18
|
-
|
|
19
|
-
def interactive_elements_to_string(self) -> str:
|
|
20
|
-
if not self.interactive_nodes:
|
|
21
|
-
return "No interactive elements"
|
|
22
|
-
headers = ["Label", "App Name", "ControlType", "Name", "Value", "Shortcut", "Coordinates" ,"IsFocused"]
|
|
23
|
-
rows = [node.to_row(idx) for idx, node in enumerate(self.interactive_nodes)]
|
|
24
|
-
return tabulate(rows, headers=headers, tablefmt="simple")
|
|
25
|
-
|
|
26
|
-
def scrollable_elements_to_string(self) -> str:
|
|
27
|
-
if not self.scrollable_nodes:
|
|
28
|
-
return "No scrollable elements"
|
|
29
|
-
headers = [
|
|
30
|
-
"Label", "App Name", "ControlType", "Name", "Coordinates",
|
|
31
|
-
"Horizontal Scrollable", "Horizontal Scroll Percent(%)", "Vertical Scrollable", "Vertical Scroll Percent(%)", "IsFocused"
|
|
32
|
-
]
|
|
33
|
-
base_index = len(self.interactive_nodes)
|
|
34
|
-
rows = [node.to_row(idx, base_index) for idx, node in enumerate(self.scrollable_nodes)]
|
|
35
|
-
return tabulate(rows, headers=headers, tablefmt="simple")
|
|
36
|
-
|
|
37
|
-
@dataclass
|
|
38
|
-
class BoundingBox:
|
|
39
|
-
left:int
|
|
40
|
-
top:int
|
|
41
|
-
right:int
|
|
42
|
-
bottom:int
|
|
43
|
-
width:int
|
|
44
|
-
height:int
|
|
45
|
-
|
|
46
|
-
def get_center(self)->'Center':
|
|
47
|
-
return Center(x=self.left+self.width//2,y=self.top+self.height//2)
|
|
48
|
-
|
|
49
|
-
def xywh_to_string(self):
|
|
50
|
-
return f'({self.left},{self.top},{self.width},{self.height})'
|
|
51
|
-
|
|
52
|
-
def xyxy_to_string(self):
|
|
53
|
-
x1,y1,x2,y2=self.convert_xywh_to_xyxy()
|
|
54
|
-
return f'({x1},{y1},{x2},{y2})'
|
|
55
|
-
|
|
56
|
-
def convert_xywh_to_xyxy(self)->tuple[int,int,int,int]:
|
|
57
|
-
x1,y1=self.left,self.top
|
|
58
|
-
x2,y2=self.left+self.width,self.top+self.height
|
|
59
|
-
return x1,y1,x2,y2
|
|
60
|
-
|
|
61
|
-
@dataclass
|
|
62
|
-
class Center:
|
|
63
|
-
x:int
|
|
64
|
-
y:int
|
|
65
|
-
|
|
66
|
-
def to_string(self)->str:
|
|
67
|
-
return f'({self.x},{self.y})'
|
|
68
|
-
|
|
69
|
-
@dataclass
|
|
70
|
-
class TreeElementNode:
|
|
71
|
-
name: str
|
|
72
|
-
control_type: str
|
|
73
|
-
app_name: str
|
|
74
|
-
value:str
|
|
75
|
-
shortcut: str
|
|
76
|
-
bounding_box: BoundingBox
|
|
77
|
-
center: Center
|
|
78
|
-
xpath:str
|
|
79
|
-
is_focused:bool
|
|
80
|
-
|
|
81
|
-
def to_row(self, index: int):
|
|
82
|
-
return [index, self.app_name, self.control_type, self.name, self.value, self.shortcut, self.center.to_string(),self.is_focused]
|
|
83
|
-
|
|
84
|
-
@dataclass
|
|
85
|
-
class ScrollElementNode:
|
|
86
|
-
name: str
|
|
87
|
-
control_type: str
|
|
88
|
-
xpath:str
|
|
89
|
-
app_name: str
|
|
90
|
-
bounding_box: BoundingBox
|
|
91
|
-
center: Center
|
|
92
|
-
horizontal_scrollable: bool
|
|
93
|
-
horizontal_scroll_percent: float
|
|
94
|
-
vertical_scrollable: bool
|
|
95
|
-
vertical_scroll_percent: float
|
|
96
|
-
is_focused: bool
|
|
97
|
-
|
|
98
|
-
def to_row(self, index: int, base_index: int):
|
|
99
|
-
return [
|
|
100
|
-
base_index + index,
|
|
101
|
-
self.app_name,
|
|
102
|
-
self.control_type,
|
|
103
|
-
self.name,
|
|
104
|
-
self.center.to_string(),
|
|
105
|
-
self.horizontal_scrollable,
|
|
106
|
-
self.horizontal_scroll_percent,
|
|
107
|
-
self.vertical_scrollable,
|
|
108
|
-
self.vertical_scroll_percent,
|
|
109
|
-
self.is_focused
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
@dataclass
|
|
113
|
-
class TextElementNode:
|
|
114
|
-
text:str
|
|
115
|
-
|
|
1
|
+
from dataclasses import dataclass,field
|
|
2
|
+
from tabulate import tabulate
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class DOMInfo:
|
|
7
|
+
horizontal_scrollable: bool
|
|
8
|
+
horizontal_scroll_percent: float
|
|
9
|
+
vertical_scrollable: bool
|
|
10
|
+
vertical_scroll_percent: float
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class TreeState:
|
|
14
|
+
interactive_nodes:list['TreeElementNode']=field(default_factory=list)
|
|
15
|
+
scrollable_nodes:list['ScrollElementNode']=field(default_factory=list)
|
|
16
|
+
dom_informative_nodes:list['TextElementNode']=field(default_factory=list)
|
|
17
|
+
dom_info:Optional['DOMInfo']=None
|
|
18
|
+
|
|
19
|
+
def interactive_elements_to_string(self) -> str:
|
|
20
|
+
if not self.interactive_nodes:
|
|
21
|
+
return "No interactive elements"
|
|
22
|
+
headers = ["Label", "App Name", "ControlType", "Name", "Value", "Shortcut", "Coordinates" ,"IsFocused"]
|
|
23
|
+
rows = [node.to_row(idx) for idx, node in enumerate(self.interactive_nodes)]
|
|
24
|
+
return tabulate(rows, headers=headers, tablefmt="simple")
|
|
25
|
+
|
|
26
|
+
def scrollable_elements_to_string(self) -> str:
|
|
27
|
+
if not self.scrollable_nodes:
|
|
28
|
+
return "No scrollable elements"
|
|
29
|
+
headers = [
|
|
30
|
+
"Label", "App Name", "ControlType", "Name", "Coordinates",
|
|
31
|
+
"Horizontal Scrollable", "Horizontal Scroll Percent(%)", "Vertical Scrollable", "Vertical Scroll Percent(%)", "IsFocused"
|
|
32
|
+
]
|
|
33
|
+
base_index = len(self.interactive_nodes)
|
|
34
|
+
rows = [node.to_row(idx, base_index) for idx, node in enumerate(self.scrollable_nodes)]
|
|
35
|
+
return tabulate(rows, headers=headers, tablefmt="simple")
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class BoundingBox:
|
|
39
|
+
left:int
|
|
40
|
+
top:int
|
|
41
|
+
right:int
|
|
42
|
+
bottom:int
|
|
43
|
+
width:int
|
|
44
|
+
height:int
|
|
45
|
+
|
|
46
|
+
def get_center(self)->'Center':
|
|
47
|
+
return Center(x=self.left+self.width//2,y=self.top+self.height//2)
|
|
48
|
+
|
|
49
|
+
def xywh_to_string(self):
|
|
50
|
+
return f'({self.left},{self.top},{self.width},{self.height})'
|
|
51
|
+
|
|
52
|
+
def xyxy_to_string(self):
|
|
53
|
+
x1,y1,x2,y2=self.convert_xywh_to_xyxy()
|
|
54
|
+
return f'({x1},{y1},{x2},{y2})'
|
|
55
|
+
|
|
56
|
+
def convert_xywh_to_xyxy(self)->tuple[int,int,int,int]:
|
|
57
|
+
x1,y1=self.left,self.top
|
|
58
|
+
x2,y2=self.left+self.width,self.top+self.height
|
|
59
|
+
return x1,y1,x2,y2
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class Center:
|
|
63
|
+
x:int
|
|
64
|
+
y:int
|
|
65
|
+
|
|
66
|
+
def to_string(self)->str:
|
|
67
|
+
return f'({self.x},{self.y})'
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class TreeElementNode:
|
|
71
|
+
name: str
|
|
72
|
+
control_type: str
|
|
73
|
+
app_name: str
|
|
74
|
+
value:str
|
|
75
|
+
shortcut: str
|
|
76
|
+
bounding_box: BoundingBox
|
|
77
|
+
center: Center
|
|
78
|
+
xpath:str
|
|
79
|
+
is_focused:bool
|
|
80
|
+
|
|
81
|
+
def to_row(self, index: int):
|
|
82
|
+
return [index, self.app_name, self.control_type, self.name, self.value, self.shortcut, self.center.to_string(),self.is_focused]
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class ScrollElementNode:
|
|
86
|
+
name: str
|
|
87
|
+
control_type: str
|
|
88
|
+
xpath:str
|
|
89
|
+
app_name: str
|
|
90
|
+
bounding_box: BoundingBox
|
|
91
|
+
center: Center
|
|
92
|
+
horizontal_scrollable: bool
|
|
93
|
+
horizontal_scroll_percent: float
|
|
94
|
+
vertical_scrollable: bool
|
|
95
|
+
vertical_scroll_percent: float
|
|
96
|
+
is_focused: bool
|
|
97
|
+
|
|
98
|
+
def to_row(self, index: int, base_index: int):
|
|
99
|
+
return [
|
|
100
|
+
base_index + index,
|
|
101
|
+
self.app_name,
|
|
102
|
+
self.control_type,
|
|
103
|
+
self.name,
|
|
104
|
+
self.center.to_string(),
|
|
105
|
+
self.horizontal_scrollable,
|
|
106
|
+
self.horizontal_scroll_percent,
|
|
107
|
+
self.vertical_scrollable,
|
|
108
|
+
self.vertical_scroll_percent,
|
|
109
|
+
self.is_focused
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class TextElementNode:
|
|
114
|
+
text:str
|
|
115
|
+
|
|
116
116
|
ElementNode=TreeElementNode|ScrollElementNode
|