windows-mcp 0.5.7__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.
@@ -1,58 +1,60 @@
1
- from windows_mcp.tree.views import TreeState
2
- from dataclasses import dataclass
3
- from tabulate import tabulate
4
- from typing import Optional
5
- from PIL.Image import Image
6
- from enum import Enum
7
-
8
- class Browser(Enum):
9
- CHROME='Chrome'
10
- EDGE='Edge'
11
- FIREFOX='Firefox'
12
-
13
- class Status(Enum):
14
- MAXIMIZED='Maximized'
15
- MINIMIZED='Minimized'
16
- NORMAL='Normal'
17
- HIDDEN='Hidden'
18
-
19
-
20
- @dataclass
21
- class App:
22
- name:str
23
- depth:int
24
- status:Status
25
- size:'Size'
26
- handle: int
27
- process_id:int
28
-
29
- def to_row(self):
30
- return [self.name, self.depth, self.status.value, self.size.width, self.size.height, self.handle]
31
-
32
- @dataclass
33
- class Size:
34
- width:int
35
- height:int
36
-
37
- def to_string(self):
38
- return f'({self.width},{self.height})'
39
-
40
- @dataclass
41
- class DesktopState:
42
- apps:list[App]
43
- active_app:Optional[App]
44
- screenshot:Image|None
45
- tree_state:TreeState
46
-
47
- def active_app_to_string(self):
48
- if self.active_app is None:
49
- return 'No active app found'
50
- headers = ["Name", "Depth", "Status", "Width", "Height", "Handle"]
51
- return tabulate([self.active_app.to_row()], headers=headers, tablefmt="simple")
52
-
53
- def apps_to_string(self):
54
- if not self.apps:
55
- return 'No apps running in background'
56
- headers = ["Name", "Depth", "Status", "Width", "Height", "Handle"]
57
- rows = [app.to_row() for app in self.apps]
1
+ from windows_mcp.tree.views import TreeState,BoundingBox
2
+ from dataclasses import dataclass
3
+ from tabulate import tabulate
4
+ from typing import Optional
5
+ from PIL.Image import Image
6
+ from enum import Enum
7
+
8
+ class Browser(Enum):
9
+ CHROME='Chrome'
10
+ EDGE='Edge'
11
+ FIREFOX='Firefox'
12
+
13
+ class Status(Enum):
14
+ MAXIMIZED='Maximized'
15
+ MINIMIZED='Minimized'
16
+ NORMAL='Normal'
17
+ HIDDEN='Hidden'
18
+
19
+
20
+ @dataclass
21
+ class App:
22
+ name:str
23
+ runtime_id:tuple[int]
24
+ is_browser:bool
25
+ depth:int
26
+ status:Status
27
+ bounding_box:BoundingBox
28
+ handle: int
29
+ process_id:int
30
+
31
+ def to_row(self):
32
+ return [self.name, self.depth, self.status.value, self.bounding_box.width, self.bounding_box.height, self.handle]
33
+
34
+ @dataclass
35
+ class Size:
36
+ width:int
37
+ height:int
38
+
39
+ def to_string(self):
40
+ return f'({self.width},{self.height})'
41
+
42
+ @dataclass
43
+ class DesktopState:
44
+ apps:list[App]
45
+ active_app:Optional[App]
46
+ screenshot:Optional[Image]=None
47
+ tree_state:Optional[TreeState]=None
48
+
49
+ def active_app_to_string(self):
50
+ if self.active_app is None:
51
+ return 'No active app found'
52
+ headers = ["Name", "Depth", "Status", "Width", "Height", "Handle"]
53
+ return tabulate([self.active_app.to_row()], headers=headers, tablefmt="simple")
54
+
55
+ def apps_to_string(self):
56
+ if not self.apps:
57
+ return 'No apps running in background'
58
+ headers = ["Name", "Depth", "Status", "Width", "Height", "Handle"]
59
+ rows = [app.to_row() for app in self.apps]
58
60
  return tabulate(rows, headers=headers, tablefmt="simple")
@@ -0,0 +1,126 @@
1
+ """
2
+ UIA Caching Utilities for Performance Optimization
3
+
4
+ This module provides utilities for implementing UI Automation caching
5
+ to reduce cross-process COM calls during tree traversal.
6
+ """
7
+
8
+ from windows_mcp.uia import CacheRequest, PropertyId, PatternId, TreeScope, Control
9
+ from typing import Optional, Any
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class CacheRequestFactory:
16
+ """Factory for creating optimized cache requests for different scenarios."""
17
+
18
+ @staticmethod
19
+ def create_tree_traversal_cache() -> CacheRequest:
20
+ """
21
+ Creates a cache request optimized for tree traversal.
22
+ Caches all commonly accessed properties and patterns.
23
+
24
+ This cache request is designed to minimize COM calls during
25
+ the tree_traversal() operation in tree/service.py.
26
+
27
+ Returns:
28
+ CacheRequest configured for tree traversal
29
+ """
30
+ cache_request = CacheRequest()
31
+
32
+ # Set scope to cache element and its children
33
+ # This allows us to get children with pre-cached properties
34
+ cache_request.TreeScope = TreeScope.TreeScope_Element | TreeScope.TreeScope_Children
35
+
36
+ # Basic identification properties
37
+ cache_request.AddProperty(PropertyId.NameProperty)
38
+ cache_request.AddProperty(PropertyId.AutomationIdProperty)
39
+ cache_request.AddProperty(PropertyId.LocalizedControlTypeProperty)
40
+ cache_request.AddProperty(PropertyId.AcceleratorKeyProperty)
41
+ cache_request.AddProperty(PropertyId.ClassNameProperty)
42
+ cache_request.AddProperty(PropertyId.ControlTypeProperty)
43
+
44
+ # State properties for visibility and interaction checks
45
+ cache_request.AddProperty(PropertyId.IsEnabledProperty)
46
+ cache_request.AddProperty(PropertyId.IsOffscreenProperty)
47
+ cache_request.AddProperty(PropertyId.IsControlElementProperty)
48
+ cache_request.AddProperty(PropertyId.HasKeyboardFocusProperty)
49
+ cache_request.AddProperty(PropertyId.IsKeyboardFocusableProperty)
50
+
51
+ # Layout properties
52
+ cache_request.AddProperty(PropertyId.BoundingRectangleProperty)
53
+
54
+ # REMOVED: Expensive patterns and less critical properties to improve performance
55
+ # Patterns like LegacyIAccessible are very expensive to marshal for every element.
56
+ # We will fetch them live only for the few elements that actually need them.
57
+
58
+ return cache_request
59
+
60
+ class CachedControlHelper:
61
+ """Helper class for working with cached controls."""
62
+
63
+ @staticmethod
64
+ def build_cached_control(node: Control, cache_request: Optional[CacheRequest] = None) -> Control:
65
+ """
66
+ Build a cached version of a control.
67
+
68
+ Args:
69
+ node: The control to cache
70
+ cache_request: Optional custom cache request. If None, uses tree traversal cache.
71
+
72
+ Returns:
73
+ A control with cached properties, or the original control if caching fails
74
+ """
75
+ if cache_request is None:
76
+ cache_request = CacheRequestFactory.create_tree_traversal_cache()
77
+
78
+ try:
79
+ cached_node = node.BuildUpdatedCache(cache_request)
80
+ cached_node._is_cached = True
81
+ return cached_node
82
+ except Exception as e:
83
+ logger.debug(f"Failed to build cached control: {e}")
84
+ return node
85
+
86
+ @staticmethod
87
+ def get_cached_children(node: Control, cache_request: Optional[CacheRequest] = None) -> list[Control]:
88
+ """
89
+ Get children with pre-cached properties.
90
+
91
+ This is the most significant optimization - it retrieves all children
92
+ with their properties already cached, eliminating the need for individual
93
+ property access calls on each child.
94
+
95
+ Args:
96
+ node: The parent control
97
+ cache_request: Optional custom cache request. If None, uses tree traversal cache.
98
+
99
+ Returns:
100
+ List of children with cached properties
101
+ """
102
+ if cache_request is None:
103
+ cache_request = CacheRequestFactory.create_tree_traversal_cache()
104
+
105
+ # Ensure the cache request includes children
106
+ # Note: We do NOT set this here to avoid modifying shared cache request objects
107
+ # The caller is responsible for providing a CacheRequest with TreeScope_Children
108
+ if (cache_request.TreeScope & TreeScope.TreeScope_Children) == 0:
109
+ logger.warning("Cache request passed to get_cached_children does not have Children scope!")
110
+
111
+ # Try to use existing cache first if available
112
+ try:
113
+ # Build updated cache that includes children
114
+ cached_node = node.BuildUpdatedCache(cache_request)
115
+ children = cached_node.GetCachedChildren()
116
+
117
+ for child in children:
118
+ child._is_cached = True
119
+
120
+ logger.debug(f"Retrieved {len(children)} cached children (newly built)")
121
+ return children
122
+
123
+ except Exception as e:
124
+ logger.debug(f"Failed to get cached children, falling back to regular access: {e}")
125
+ return node.GetChildren()
126
+
@@ -1,51 +1,76 @@
1
- INTERACTIVE_CONTROL_TYPE_NAMES=set([
2
- 'ButtonControl',
3
- 'ListItemControl',
4
- 'MenuItemControl',
5
- 'EditControl',
6
- 'CheckBoxControl',
7
- 'RadioButtonControl',
8
- 'ComboBoxControl',
9
- 'HyperlinkControl',
10
- 'SplitButtonControl',
11
- 'TabItemControl',
12
- 'TreeItemControl',
13
- 'DataItemControl',
14
- 'HeaderItemControl',
15
- 'TextBoxControl',
16
- 'SpinnerControl',
17
- 'ScrollBarControl'
18
- ])
19
-
20
- DOCUMENT_CONTROL_TYPE_NAMES=set([
21
- 'DocumentControl'
22
- ])
23
-
24
- STRUCTURAL_CONTROL_TYPE_NAMES = set([
25
- 'PaneControl',
26
- 'GroupControl',
27
- 'CustomControl'
28
- ])
29
-
30
- INFORMATIVE_CONTROL_TYPE_NAMES=set([
31
- 'TextControl',
32
- 'ImageControl',
33
- 'StatusBarControl',
34
- # 'ProgressBarControl',
35
- # 'ToolTipControl',
36
- # 'TitleBarControl',
37
- # 'SeparatorControl',
38
- # 'HeaderControl',
39
- # 'HeaderItemControl',
40
- ])
41
-
42
- DEFAULT_ACTIONS=set([
43
- 'Click',
44
- 'Press',
45
- 'Jump',
46
- 'Check',
47
- 'Uncheck',
48
- 'Double Click'
49
- ])
50
-
1
+ INTERACTIVE_CONTROL_TYPE_NAMES=set([
2
+ 'ButtonControl',
3
+ 'ListItemControl',
4
+ 'MenuItemControl',
5
+ 'EditControl',
6
+ 'CheckBoxControl',
7
+ 'RadioButtonControl',
8
+ 'ComboBoxControl',
9
+ 'HyperlinkControl',
10
+ 'SplitButtonControl',
11
+ 'TabItemControl',
12
+ 'TreeItemControl',
13
+ 'DataItemControl',
14
+ 'HeaderItemControl',
15
+ 'TextBoxControl',
16
+ 'SpinnerControl',
17
+ 'ScrollBarControl'
18
+ ])
19
+
20
+ INTERACTIVE_ROLES = {
21
+ # Buttons
22
+ 'PushButton', 'SplitButton', 'ButtonDropDown', 'ButtonMenu',
23
+ 'ButtonDropDownGrid', 'OutlineButton',
24
+
25
+ # Links
26
+ 'Link',
27
+
28
+ # Inputs & Selection
29
+ 'Text', 'IpAddress', 'HotkeyField', 'ComboBox', 'DropList',
30
+ 'CheckButton', 'RadioButton',
31
+
32
+ # Menus & Tabs
33
+ 'MenuItem', 'ListItem', 'PageTab',
34
+
35
+ # Trees
36
+ 'OutlineItem',
37
+
38
+ # Values
39
+ 'Slider', 'SpinButton', 'Dial', 'ScrollBar', 'Grip',
40
+
41
+ # Grids
42
+ 'ColumnHeader', 'RowHeader', 'Cell'
43
+ }
44
+
45
+ DOCUMENT_CONTROL_TYPE_NAMES=set([
46
+ 'DocumentControl'
47
+ ])
48
+
49
+ STRUCTURAL_CONTROL_TYPE_NAMES = set([
50
+ 'PaneControl',
51
+ 'GroupControl',
52
+ 'CustomControl'
53
+ ])
54
+
55
+ INFORMATIVE_CONTROL_TYPE_NAMES=set([
56
+ 'TextControl',
57
+ 'ImageControl',
58
+ 'StatusBarControl',
59
+ # 'ProgressBarControl',
60
+ # 'ToolTipControl',
61
+ # 'TitleBarControl',
62
+ # 'SeparatorControl',
63
+ # 'HeaderControl',
64
+ # 'HeaderItemControl',
65
+ ])
66
+
67
+ DEFAULT_ACTIONS=set([
68
+ 'Click',
69
+ 'Press',
70
+ 'Jump',
71
+ 'Check',
72
+ 'Uncheck',
73
+ 'Double Click'
74
+ ])
75
+
51
76
  THREAD_MAX_RETRIES = 3