windows-mcp 0.5.4__py3-none-any.whl → 0.5.5__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 +4 -4
- windows_mcp/tree/service.py +12 -42
- windows_mcp/tree/views.py +8 -2
- {windows_mcp-0.5.4.dist-info → windows_mcp-0.5.5.dist-info}/METADATA +1 -1
- {windows_mcp-0.5.4.dist-info → windows_mcp-0.5.5.dist-info}/RECORD +8 -8
- {windows_mcp-0.5.4.dist-info → windows_mcp-0.5.5.dist-info}/WHEEL +0 -0
- {windows_mcp-0.5.4.dist-info → windows_mcp-0.5.5.dist-info}/entry_points.txt +0 -0
- {windows_mcp-0.5.4.dist-info → windows_mcp-0.5.5.dist-info}/licenses/LICENSE.md +0 -0
windows_mcp/__main__.py
CHANGED
|
@@ -243,10 +243,10 @@ def wait_tool(duration:int)->str:
|
|
|
243
243
|
def scrape_tool(url:str)->str:
|
|
244
244
|
desktop_state=desktop.desktop_state
|
|
245
245
|
tree_state=desktop_state.tree_state
|
|
246
|
-
if not tree_state.
|
|
247
|
-
return f'
|
|
248
|
-
|
|
249
|
-
vertical_scroll_percent=
|
|
246
|
+
if not tree_state.dom_info:
|
|
247
|
+
return f'No DOM information found. Please open {url} in browser first.'
|
|
248
|
+
dom_info=tree_state.dom_info
|
|
249
|
+
vertical_scroll_percent=dom_info.vertical_scroll_percent
|
|
250
250
|
content='\n'.join([node.text for node in tree_state.dom_informative_nodes])
|
|
251
251
|
header_status = "Reached top" if vertical_scroll_percent <= 0 else "Scroll up to see more"
|
|
252
252
|
footer_status = "Reached bottom" if vertical_scroll_percent >= 100 else "Scroll down to see more"
|
windows_mcp/tree/service.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from windows_mcp.tree.config import INTERACTIVE_CONTROL_TYPE_NAMES,DOCUMENT_CONTROL_TYPE_NAMES,INFORMATIVE_CONTROL_TYPE_NAMES, DEFAULT_ACTIONS, THREAD_MAX_RETRIES
|
|
2
|
+
from windows_mcp.tree.views import TreeElementNode, ScrollElementNode, TextElementNode, Center, BoundingBox, TreeState, DOMInfo
|
|
2
3
|
from uiautomation import Control,ImageControl,ScrollPattern,WindowControl,Rect,GetRootControl,PatternId
|
|
3
|
-
from windows_mcp.tree.views import TreeElementNode, ScrollElementNode, TextElementNode, Center, BoundingBox, TreeState
|
|
4
4
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
5
5
|
from windows_mcp.tree.utils import random_point_within_bounding_box
|
|
6
6
|
from PIL import Image, ImageFont, ImageDraw
|
|
@@ -24,58 +24,22 @@ class Tree:
|
|
|
24
24
|
def __init__(self,desktop:'Desktop'):
|
|
25
25
|
self.desktop=desktop
|
|
26
26
|
self.screen_size=self.desktop.get_screen_size()
|
|
27
|
-
self.
|
|
27
|
+
self.dom_info:Optional[DOMInfo]=None
|
|
28
28
|
self.dom_bounding_box:BoundingBox=None
|
|
29
29
|
self.screen_box=BoundingBox(
|
|
30
30
|
top=0, left=0, bottom=self.screen_size.height, right=self.screen_size.width,
|
|
31
31
|
width=self.screen_size.width, height=self.screen_size.height
|
|
32
32
|
)
|
|
33
|
-
self.root:Optional[TreeElementNode]=None
|
|
34
33
|
|
|
35
34
|
def get_state(self,active_app:App,other_apps:list[App],use_dom:bool=False)->TreeState:
|
|
36
|
-
|
|
35
|
+
root=GetRootControl()
|
|
37
36
|
other_apps_handle=set(map(lambda other_app: other_app.handle,other_apps))
|
|
38
|
-
apps=list(filter(lambda app:app.NativeWindowHandle not in other_apps_handle,
|
|
37
|
+
apps=list(filter(lambda app:app.NativeWindowHandle not in other_apps_handle,root.GetChildren()))
|
|
39
38
|
del other_apps_handle
|
|
40
39
|
if active_app:
|
|
41
40
|
apps=list(filter(lambda app:app.ClassName!='Progman',apps))
|
|
42
41
|
interactive_nodes,scrollable_nodes,dom_informative_nodes=self.get_appwise_nodes(apps=apps,use_dom=use_dom)
|
|
43
|
-
|
|
44
|
-
'name':'Desktop',
|
|
45
|
-
'control_type':'PaneControl',
|
|
46
|
-
'app_name':'Desktop',
|
|
47
|
-
'value':'',
|
|
48
|
-
'shortcut':'',
|
|
49
|
-
'bounding_box':self.screen_box,
|
|
50
|
-
'center':Center(x=self.screen_box.left+self.screen_box.width//2,y=self.screen_box.top+self.screen_box.height//2),
|
|
51
|
-
'xpath':'',
|
|
52
|
-
'is_focused':False
|
|
53
|
-
})
|
|
54
|
-
dom=None
|
|
55
|
-
if self.dom:
|
|
56
|
-
scroll_pattern=self.dom.GetPattern(PatternId.ScrollPattern)
|
|
57
|
-
bounding_box=self.dom.BoundingRectangle
|
|
58
|
-
dom=ScrollElementNode(**{
|
|
59
|
-
'name':"DOM",
|
|
60
|
-
'control_type':'DocumentControl',
|
|
61
|
-
'app_name':"DOM",
|
|
62
|
-
'bounding_box':BoundingBox(
|
|
63
|
-
left=bounding_box.left,
|
|
64
|
-
top=bounding_box.top,
|
|
65
|
-
right=bounding_box.right,
|
|
66
|
-
bottom=bounding_box.bottom,
|
|
67
|
-
width=bounding_box.width(),
|
|
68
|
-
height=bounding_box.height()
|
|
69
|
-
),
|
|
70
|
-
'center':Center(x=bounding_box.left+bounding_box.width()//2,y=bounding_box.top+bounding_box.height()//2),
|
|
71
|
-
'horizontal_scrollable':scroll_pattern.HorizontallyScrollable,
|
|
72
|
-
'horizontal_scroll_percent':scroll_pattern.HorizontalScrollPercent if scroll_pattern.HorizontallyScrollable else 0,
|
|
73
|
-
'vertical_scrollable':scroll_pattern.VerticallyScrollable,
|
|
74
|
-
'vertical_scroll_percent':scroll_pattern.VerticalScrollPercent if scroll_pattern.VerticallyScrollable else 0,
|
|
75
|
-
'xpath':'',
|
|
76
|
-
'is_focused':False
|
|
77
|
-
})
|
|
78
|
-
return TreeState(root=root,dom=dom,interactive_nodes=interactive_nodes,scrollable_nodes=scrollable_nodes,dom_informative_nodes=dom_informative_nodes)
|
|
42
|
+
return TreeState(dom_info=self.dom_info,interactive_nodes=interactive_nodes,scrollable_nodes=scrollable_nodes,dom_informative_nodes=dom_informative_nodes)
|
|
79
43
|
|
|
80
44
|
def get_appwise_nodes(self,apps:list[Control],use_dom:bool=False)-> tuple[list[TreeElementNode],list[ScrollElementNode],list[TextElementNode]]:
|
|
81
45
|
interactive_nodes, scrollable_nodes,dom_informative_nodes = [], [], []
|
|
@@ -388,7 +352,13 @@ class Tree:
|
|
|
388
352
|
self.dom_bounding_box=BoundingBox(left=bounding_box.left,top=bounding_box.top,
|
|
389
353
|
right=bounding_box.right,bottom=bounding_box.bottom,width=bounding_box.width(),
|
|
390
354
|
height=bounding_box.height())
|
|
391
|
-
|
|
355
|
+
scroll_pattern=child.GetPattern(PatternId.ScrollPattern)
|
|
356
|
+
self.dom_info=DOMInfo(
|
|
357
|
+
horizontal_scrollable=scroll_pattern.HorizontallyScrollable,
|
|
358
|
+
horizontal_scroll_percent=scroll_pattern.HorizontalScrollPercent if scroll_pattern.HorizontallyScrollable else 0,
|
|
359
|
+
vertical_scrollable=scroll_pattern.VerticallyScrollable,
|
|
360
|
+
vertical_scroll_percent=scroll_pattern.VerticalScrollPercent if scroll_pattern.VerticallyScrollable else 0
|
|
361
|
+
)
|
|
392
362
|
# enter DOM subtree
|
|
393
363
|
tree_traversal(child, is_dom=True, is_dialog=is_dialog)
|
|
394
364
|
# Check if the child is a dialog
|
windows_mcp/tree/views.py
CHANGED
|
@@ -2,13 +2,19 @@ from dataclasses import dataclass,field
|
|
|
2
2
|
from tabulate import tabulate
|
|
3
3
|
from typing import Optional
|
|
4
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
|
+
|
|
5
12
|
@dataclass
|
|
6
13
|
class TreeState:
|
|
7
|
-
root:Optional['TreeElementNode']=None
|
|
8
|
-
dom:Optional['ScrollElementNode']=None
|
|
9
14
|
interactive_nodes:list['TreeElementNode']=field(default_factory=list)
|
|
10
15
|
scrollable_nodes:list['ScrollElementNode']=field(default_factory=list)
|
|
11
16
|
dom_informative_nodes:list['TextElementNode']=field(default_factory=list)
|
|
17
|
+
dom_info:Optional['DOMInfo']=None
|
|
12
18
|
|
|
13
19
|
def interactive_elements_to_string(self) -> str:
|
|
14
20
|
if not self.interactive_nodes:
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
windows_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
windows_mcp/__main__.py,sha256=
|
|
2
|
+
windows_mcp/__main__.py,sha256=pkwexVikZwUFHe0oqmMYFfiRavjNfs5Bv-ELvLv9Dpo,11939
|
|
3
3
|
windows_mcp/desktop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
windows_mcp/desktop/config.py,sha256=7rAb64pmC275PpNRXVOyOf0Psu089AOosRC8T5kVGWA,384
|
|
5
5
|
windows_mcp/desktop/service.py,sha256=97e2E4TdMs3TwW6CtupVxnwhWqdBKU5eH4MDz6_5Hmk,18469
|
|
6
6
|
windows_mcp/desktop/views.py,sha256=_hZ5sfY1uWVi5mpaysVd-plwP_DT6SXpKa33Z8WT6gI,1523
|
|
7
7
|
windows_mcp/tree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
8
|
windows_mcp/tree/config.py,sha256=k-Mjo_yIn0d1AzcEW_bxiaXyBFxBZZSyy7hCNQ3XVp0,1010
|
|
9
|
-
windows_mcp/tree/service.py,sha256=
|
|
9
|
+
windows_mcp/tree/service.py,sha256=evK62AwhMwifpq6lRQCdrmC4DPt1-w_HSp8nUwXsCVQ,23566
|
|
10
10
|
windows_mcp/tree/utils.py,sha256=6hbxdIQPrAY-I3jcHsRqodHlxboTQj2GnLA71bf1lqY,911
|
|
11
|
-
windows_mcp/tree/views.py,sha256=
|
|
12
|
-
windows_mcp-0.5.
|
|
13
|
-
windows_mcp-0.5.
|
|
14
|
-
windows_mcp-0.5.
|
|
15
|
-
windows_mcp-0.5.
|
|
16
|
-
windows_mcp-0.5.
|
|
11
|
+
windows_mcp/tree/views.py,sha256=K2hTBDicjP4p_tPIRTLZ8Sq3pGYhsDtZVIROAnMGTz4,3599
|
|
12
|
+
windows_mcp-0.5.5.dist-info/METADATA,sha256=Jx7stv6_Lm145NSLB8EdAsRJsBXiP0FhIQ-7cA2FmEU,13537
|
|
13
|
+
windows_mcp-0.5.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
windows_mcp-0.5.5.dist-info/entry_points.txt,sha256=wW8NcVQ_OJK5e5GemZSE_nOKyxfUtBPq2acFLszRwaw,58
|
|
15
|
+
windows_mcp-0.5.5.dist-info/licenses/LICENSE.md,sha256=U1UM4Xi_IX-jHnHjGT0rETNia-Ck8gd92iSQMqQ6a8Y,1089
|
|
16
|
+
windows_mcp-0.5.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|