windows-mcp 0.5.8__py3-none-any.whl → 0.5.9__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/tree/views.py CHANGED
@@ -1,116 +1,142 @@
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
- ElementNode=TreeElementNode|ScrollElementNode
1
+ from dataclasses import dataclass,field
2
+ from tabulate import tabulate
3
+ from typing import Optional
4
+
5
+ @dataclass
6
+ class TreeState:
7
+ root_node:Optional['TreeElementNode']=None
8
+ dom_node:Optional['ScrollElementNode']=None
9
+ interactive_nodes:list['TreeElementNode']=field(default_factory=list)
10
+ scrollable_nodes:list['ScrollElementNode']=field(default_factory=list)
11
+ dom_informative_nodes:list['TextElementNode']=field(default_factory=list)
12
+
13
+ def interactive_elements_to_string(self) -> str:
14
+ if not self.interactive_nodes:
15
+ return "No interactive elements"
16
+ # TOON-like format: Pipe-separated values with clear header
17
+ # Using abbreviations in header to save tokens
18
+ header = "# id|app|type|name|coords|focus"
19
+ rows = [header]
20
+ for idx, node in enumerate(self.interactive_nodes):
21
+ row = f"{idx}|{node.app_name}|{node.control_type}|{node.name}|{node.center.to_string()}|{node.is_focused}"
22
+ rows.append(row)
23
+ return "\n".join(rows)
24
+
25
+ def scrollable_elements_to_string(self) -> str:
26
+ if not self.scrollable_nodes:
27
+ return "No scrollable elements"
28
+ # TOON-like format
29
+ header = "# id|app|type|name|coords|h_scroll|h_pct|v_scroll|v_pct|focus"
30
+ rows = [header]
31
+ base_index = len(self.interactive_nodes)
32
+ for idx, node in enumerate(self.scrollable_nodes):
33
+ row = (f"{base_index + idx}|{node.app_name}|{node.control_type}|{node.name}|"
34
+ f"{node.center.to_string()}|{node.horizontal_scrollable}|{node.horizontal_scroll_percent}|"
35
+ f"{node.vertical_scrollable}|{node.vertical_scroll_percent}|{node.is_focused}")
36
+ rows.append(row)
37
+ return "\n".join(rows)
38
+
39
+ @dataclass
40
+ class BoundingBox:
41
+ left:int
42
+ top:int
43
+ right:int
44
+ bottom:int
45
+ width:int
46
+ height:int
47
+
48
+ @classmethod
49
+ def from_bounding_rectangle(cls,bounding_rectangle:'BoundingRectangle')->'BoundingBox':
50
+ return cls(
51
+ left=bounding_rectangle.left,
52
+ top=bounding_rectangle.top,
53
+ right=bounding_rectangle.right,
54
+ bottom=bounding_rectangle.bottom,
55
+ width=bounding_rectangle.width(),
56
+ height=bounding_rectangle.height()
57
+ )
58
+
59
+ def get_center(self)->'Center':
60
+ return Center(x=self.left+self.width//2,y=self.top+self.height//2)
61
+
62
+ def xywh_to_string(self):
63
+ return f'({self.left},{self.top},{self.width},{self.height})'
64
+
65
+ def xyxy_to_string(self):
66
+ x1,y1,x2,y2=self.convert_xywh_to_xyxy()
67
+ return f'({x1},{y1},{x2},{y2})'
68
+
69
+ def convert_xywh_to_xyxy(self)->tuple[int,int,int,int]:
70
+ x1,y1=self.left,self.top
71
+ x2,y2=self.left+self.width,self.top+self.height
72
+ return x1,y1,x2,y2
73
+
74
+ @dataclass
75
+ class Center:
76
+ x:int
77
+ y:int
78
+
79
+ def to_string(self)->str:
80
+ return f'({self.x},{self.y})'
81
+
82
+ @dataclass
83
+ class TreeElementNode:
84
+ bounding_box: BoundingBox
85
+ center: Center
86
+ name: str=''
87
+ control_type: str=''
88
+ app_name: str=''
89
+ value:str=''
90
+ shortcut: str=''
91
+ xpath:str=''
92
+ is_focused:bool=False
93
+
94
+ def update_from_node(self,node:'TreeElementNode'):
95
+ self.name=node.name
96
+ self.control_type=node.control_type
97
+ self.app_name=node.app_name
98
+ self.value=node.value
99
+ self.shortcut=node.shortcut
100
+ self.bounding_box=node.bounding_box
101
+ self.center=node.center
102
+ self.xpath=node.xpath
103
+ self.is_focused=node.is_focused
104
+
105
+ # Legacy method kept for compatibility if needed, but not used in new format
106
+ def to_row(self, index: int):
107
+ return [index, self.app_name, self.control_type, self.name, self.value, self.shortcut, self.center.to_string(),self.is_focused]
108
+
109
+ @dataclass
110
+ class ScrollElementNode:
111
+ name: str
112
+ control_type: str
113
+ xpath:str
114
+ app_name: str
115
+ bounding_box: BoundingBox
116
+ center: Center
117
+ horizontal_scrollable: bool
118
+ horizontal_scroll_percent: float
119
+ vertical_scrollable: bool
120
+ vertical_scroll_percent: float
121
+ is_focused: bool
122
+
123
+ # Legacy method kept for compatibility
124
+ def to_row(self, index: int, base_index: int):
125
+ return [
126
+ base_index + index,
127
+ self.app_name,
128
+ self.control_type,
129
+ self.name,
130
+ self.center.to_string(),
131
+ self.horizontal_scrollable,
132
+ self.horizontal_scroll_percent,
133
+ self.vertical_scrollable,
134
+ self.vertical_scroll_percent,
135
+ self.is_focused
136
+ ]
137
+
138
+ @dataclass
139
+ class TextElementNode:
140
+ text:str
141
+
142
+ ElementNode=TreeElementNode|ScrollElementNode|TextElementNode
@@ -242,9 +242,10 @@ class Control():
242
242
  return self.Element.CachedAutomationId
243
243
 
244
244
  @property
245
- def CachedBoundingRectangle(self) -> Any:
245
+ def CachedBoundingRectangle(self) -> Rect:
246
246
  """Get the cached bounding rectangle."""
247
- return self.Element.CachedBoundingRectangle
247
+ rect = self.Element.CachedBoundingRectangle
248
+ return Rect(rect.left, rect.top, rect.right, rect.bottom)
248
249
 
249
250
  @property
250
251
  def CachedClassName(self) -> str:
@@ -256,6 +257,14 @@ class Control():
256
257
  """Get the cached control type."""
257
258
  return self.Element.CachedControlType
258
259
 
260
+ @property
261
+ def CachedControlTypeName(self) -> str:
262
+ """Get the cached control type name."""
263
+ try:
264
+ return ControlTypeNames.get(self.CachedControlType, "Unknown")
265
+ except:
266
+ return "Unknown"
267
+
259
268
  @property
260
269
  def CachedControllerFor(self) -> Any:
261
270
  """Get the cached controller for."""
windows_mcp/uia/core.py CHANGED
@@ -62,6 +62,15 @@ def WheelUp(wheelTimes: int = 1, interval: float = 0.05, waitTime: float = OPERA
62
62
  def GetScreenSize() -> Tuple[int, int]:
63
63
  return ctypes.windll.user32.GetSystemMetrics(0), ctypes.windll.user32.GetSystemMetrics(1)
64
64
 
65
+ def GetVirtualScreenRect() -> Tuple[int, int, int, int]:
66
+ """Returns (left, top, width, height) of the virtual screen."""
67
+ return (
68
+ ctypes.windll.user32.GetSystemMetrics(76), # SM_XVIRTUALSCREEN
69
+ ctypes.windll.user32.GetSystemMetrics(77), # SM_YVIRTUALSCREEN
70
+ ctypes.windll.user32.GetSystemMetrics(78), # SM_CXVIRTUALSCREEN
71
+ ctypes.windll.user32.GetSystemMetrics(79) # SM_CYVIRTUALSCREEN
72
+ )
73
+
65
74
  def IsTopLevelWindow(handle: int) -> bool:
66
75
  return ctypes.windll.user32.GetParent(handle) == 0
67
76
 
@@ -0,0 +1 @@
1
+ from .core import *