windows-mcp 0.6.0__py3-none-any.whl → 0.6.2__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 +66 -18
- windows_mcp/desktop/service.py +189 -101
- windows_mcp/desktop/views.py +32 -15
- windows_mcp/tree/service.py +29 -29
- windows_mcp/tree/utils.py +21 -21
- windows_mcp/tree/views.py +9 -9
- windows_mcp/uia/controls.py +0 -42
- windows_mcp/uia/core.py +19 -929
- windows_mcp/vdm/core.py +147 -135
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/METADATA +6 -5
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/RECORD +14 -14
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/WHEEL +0 -0
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/entry_points.txt +0 -0
- {windows_mcp-0.6.0.dist-info → windows_mcp-0.6.2.dist-info}/licenses/LICENSE.md +0 -0
windows_mcp/vdm/core.py
CHANGED
|
@@ -12,43 +12,8 @@ from comtypes import GUID, IUnknown, COMMETHOD, STDMETHOD
|
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
15
|
-
import secrets
|
|
16
|
-
import string
|
|
17
|
-
|
|
18
15
|
_thread_local = threading.local()
|
|
19
16
|
|
|
20
|
-
# Global mapping for Simple ID (Short) <-> GUID (Long)
|
|
21
|
-
_SIMPLE_TO_GUID = {}
|
|
22
|
-
_GUID_TO_SIMPLE = {}
|
|
23
|
-
_MAP_LOCK = threading.Lock()
|
|
24
|
-
|
|
25
|
-
def _get_simple_id(guid_str: str) -> str:
|
|
26
|
-
"""Gets or creates a simple 8-char ID for a given GUID string."""
|
|
27
|
-
guid_str = guid_str.upper() # Normalize GUIDs
|
|
28
|
-
with _MAP_LOCK:
|
|
29
|
-
if guid_str in _GUID_TO_SIMPLE:
|
|
30
|
-
return _GUID_TO_SIMPLE[guid_str]
|
|
31
|
-
|
|
32
|
-
# Generate unique short ID
|
|
33
|
-
while True:
|
|
34
|
-
# 8 char random hex-like string (easy to read)
|
|
35
|
-
alphabet = string.ascii_lowercase + string.digits
|
|
36
|
-
short_id = ''.join(secrets.choice(alphabet) for _ in range(8))
|
|
37
|
-
if short_id not in _SIMPLE_TO_GUID:
|
|
38
|
-
break
|
|
39
|
-
|
|
40
|
-
_SIMPLE_TO_GUID[short_id] = guid_str
|
|
41
|
-
_GUID_TO_SIMPLE[guid_str] = short_id
|
|
42
|
-
return short_id
|
|
43
|
-
|
|
44
|
-
def _resolve_guid(simple_id: str) -> str:
|
|
45
|
-
"""Resolves a simple ID to its GUID string."""
|
|
46
|
-
with _MAP_LOCK:
|
|
47
|
-
if simple_id in _SIMPLE_TO_GUID:
|
|
48
|
-
return _SIMPLE_TO_GUID[simple_id]
|
|
49
|
-
# Fallback: maybe the user passed a real GUID?
|
|
50
|
-
return simple_id
|
|
51
|
-
|
|
52
17
|
def _get_manager():
|
|
53
18
|
if not hasattr(_thread_local, "manager"):
|
|
54
19
|
_thread_local.manager = VirtualDesktopManager()
|
|
@@ -77,7 +42,7 @@ class IVirtualDesktopManager(IUnknown):
|
|
|
77
42
|
(['out', 'retval'], POINTER(GUID), "desktopId")),
|
|
78
43
|
COMMETHOD([], HRESULT, "MoveWindowToDesktop",
|
|
79
44
|
(['in'], HWND, "topLevelWindow"),
|
|
80
|
-
(['in'], GUID, "desktopId")),
|
|
45
|
+
(['in'], POINTER(GUID), "desktopId")),
|
|
81
46
|
]
|
|
82
47
|
|
|
83
48
|
# Internal COM Interfaces for Windows 11
|
|
@@ -89,8 +54,8 @@ class IServiceProvider(IUnknown):
|
|
|
89
54
|
_iid_ = IID_IServiceProvider
|
|
90
55
|
_methods_ = [
|
|
91
56
|
COMMETHOD([], HRESULT, "QueryService",
|
|
92
|
-
(['in'], GUID, "guidService"),
|
|
93
|
-
(['in'], GUID, "riid"),
|
|
57
|
+
(['in'], POINTER(GUID), "guidService"),
|
|
58
|
+
(['in'], POINTER(GUID), "riid"),
|
|
94
59
|
(['out'], POINTER(POINTER(IUnknown)), "ppvObject")),
|
|
95
60
|
]
|
|
96
61
|
|
|
@@ -102,7 +67,7 @@ class IObjectArray(IUnknown):
|
|
|
102
67
|
(['out'], POINTER(UINT), "pcObjects")),
|
|
103
68
|
COMMETHOD([], HRESULT, "GetAt",
|
|
104
69
|
(['in'], UINT, "uiIndex"),
|
|
105
|
-
(['in'], GUID, "riid"),
|
|
70
|
+
(['in'], POINTER(GUID), "riid"),
|
|
106
71
|
(['out'], POINTER(POINTER(IUnknown)), "ppv")),
|
|
107
72
|
]
|
|
108
73
|
|
|
@@ -239,7 +204,7 @@ class VirtualDesktopManager:
|
|
|
239
204
|
# Initialize Internal Manager
|
|
240
205
|
try:
|
|
241
206
|
service_provider = comtypes.client.CreateObject(CLSID_ImmersiveShell, interface=IServiceProvider)
|
|
242
|
-
unk = service_provider.QueryService(CLSID_VirtualDesktopManagerInternal, IVirtualDesktopManagerInternal._iid_)
|
|
207
|
+
unk = service_provider.QueryService(byref(CLSID_VirtualDesktopManagerInternal), byref(IVirtualDesktopManagerInternal._iid_))
|
|
243
208
|
self._internal_manager = unk.QueryInterface(IVirtualDesktopManagerInternal)
|
|
244
209
|
except Exception as e:
|
|
245
210
|
logger.warning(f"Failed to initialize VirtualDesktopManagerInternal: {e}")
|
|
@@ -267,27 +232,81 @@ class VirtualDesktopManager:
|
|
|
267
232
|
return ""
|
|
268
233
|
try:
|
|
269
234
|
guid = self._manager.GetWindowDesktopId(hwnd)
|
|
270
|
-
return
|
|
235
|
+
return str(guid)
|
|
271
236
|
except Exception:
|
|
272
237
|
return ""
|
|
273
238
|
|
|
274
|
-
def
|
|
239
|
+
def _get_name_from_registry(self, guid_str: str) -> str:
|
|
275
240
|
"""
|
|
276
|
-
|
|
241
|
+
Retrieves the user-friendly name of a desktop from the Registry.
|
|
242
|
+
Returns None if no custom name is set.
|
|
243
|
+
"""
|
|
244
|
+
try:
|
|
245
|
+
import winreg
|
|
246
|
+
path = f"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\VirtualDesktops\\Desktops\\{guid_str}"
|
|
247
|
+
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, path) as key:
|
|
248
|
+
name, _ = winreg.QueryValueEx(key, "Name")
|
|
249
|
+
return name
|
|
250
|
+
except Exception:
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
def _resolve_to_guid(self, name: str) -> str:
|
|
254
|
+
"""
|
|
255
|
+
Resolves a desktop Name to a GUID string.
|
|
256
|
+
Also supports passing the GUID string directly if needed, but prioritizes Name.
|
|
257
|
+
"""
|
|
258
|
+
# 1. Get current state (Names and GUIDs)
|
|
259
|
+
# We need to map Names to GUIDs.
|
|
260
|
+
desktops_map = {} # Name -> GUID
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
desktops_array = self._internal_manager.GetDesktops()
|
|
264
|
+
count = desktops_array.GetCount()
|
|
265
|
+
|
|
266
|
+
for i in range(count):
|
|
267
|
+
unk = desktops_array.GetAt(i, byref(IVirtualDesktop._iid_))
|
|
268
|
+
desktop = unk.QueryInterface(IVirtualDesktop)
|
|
269
|
+
guid = getattr(desktop, "GetID", lambda: None)()
|
|
270
|
+
if not guid: continue
|
|
271
|
+
guid_str = str(guid)
|
|
272
|
+
|
|
273
|
+
# Determine Name
|
|
274
|
+
reg_name = self._get_name_from_registry(guid_str)
|
|
275
|
+
display_name = reg_name if reg_name else f"Desktop {i+1}"
|
|
276
|
+
|
|
277
|
+
desktops_map[display_name.lower()] = guid_str
|
|
278
|
+
# Also verify if the input IS the GUID (fallback support)
|
|
279
|
+
if name.lower() == guid_str.lower():
|
|
280
|
+
return guid_str
|
|
281
|
+
|
|
282
|
+
except Exception as e:
|
|
283
|
+
logger.error(f"Error scanning desktops for resolution: {e}")
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
if name.lower() in desktops_map:
|
|
287
|
+
return desktops_map[name.lower()]
|
|
288
|
+
|
|
289
|
+
return None
|
|
290
|
+
|
|
291
|
+
def move_window_to_desktop(self, hwnd: int, desktop_name: str):
|
|
292
|
+
"""
|
|
293
|
+
Moves a window to the specified virtual desktop (by Name).
|
|
277
294
|
"""
|
|
278
295
|
if not self._manager:
|
|
279
296
|
return
|
|
280
297
|
try:
|
|
281
|
-
target_guid_str =
|
|
298
|
+
target_guid_str = self._resolve_to_guid(desktop_name)
|
|
299
|
+
if not target_guid_str:
|
|
300
|
+
logger.error(f"Desktop '{desktop_name}' not found.")
|
|
301
|
+
return
|
|
282
302
|
guid = GUID(target_guid_str)
|
|
283
|
-
self._manager.MoveWindowToDesktop(hwnd, guid)
|
|
303
|
+
self._manager.MoveWindowToDesktop(hwnd, byref(guid))
|
|
284
304
|
except Exception as e:
|
|
285
305
|
logger.error(f"Failed to move window to desktop: {e}")
|
|
286
306
|
|
|
287
307
|
def create_desktop(self, name: str = None) -> str:
|
|
288
308
|
"""
|
|
289
|
-
Creates a new virtual desktop and returns its
|
|
290
|
-
Optionally sets the name of the new desktop.
|
|
309
|
+
Creates a new virtual desktop and returns its Name.
|
|
291
310
|
"""
|
|
292
311
|
if not self._internal_manager:
|
|
293
312
|
raise RuntimeError("Internal VDM not initialized")
|
|
@@ -296,30 +315,37 @@ class VirtualDesktopManager:
|
|
|
296
315
|
guid = desktop.GetID()
|
|
297
316
|
guid_str = str(guid)
|
|
298
317
|
|
|
299
|
-
#
|
|
300
|
-
simple_id = _get_simple_id(guid_str)
|
|
301
|
-
|
|
318
|
+
# If name is provided, set it immediately
|
|
302
319
|
if name:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
320
|
+
# We need to use the GUID to rename because we just created it
|
|
321
|
+
# But the external API returns the Name.
|
|
322
|
+
# We can try to rename using our helper
|
|
323
|
+
self.rename_desktop_by_guid(guid_str, name)
|
|
324
|
+
return name
|
|
325
|
+
else:
|
|
326
|
+
# If no name provided, determine what its default name is
|
|
327
|
+
# We can calculate it by checking total count
|
|
328
|
+
desktops = self.get_all_desktops()
|
|
329
|
+
return desktops[-1]['name'] # Assume it's the last one
|
|
330
|
+
|
|
331
|
+
def remove_desktop(self, desktop_name: str):
|
|
308
332
|
"""
|
|
309
|
-
Removes a virtual desktop by
|
|
310
|
-
Will try to fallback to the first available desktop that is not the one being deleted.
|
|
333
|
+
Removes a virtual desktop by Name.
|
|
311
334
|
"""
|
|
312
335
|
if not self._internal_manager:
|
|
313
336
|
raise RuntimeError("Internal VDM not initialized")
|
|
314
337
|
|
|
315
|
-
target_guid_str =
|
|
338
|
+
target_guid_str = self._resolve_to_guid(desktop_name)
|
|
339
|
+
if not target_guid_str:
|
|
340
|
+
logger.error(f"Desktop '{desktop_name}' not found.")
|
|
341
|
+
return
|
|
342
|
+
|
|
316
343
|
target_guid = GUID(target_guid_str)
|
|
317
344
|
|
|
318
|
-
# We need the IVirtualDesktop object for the target
|
|
319
345
|
try:
|
|
320
346
|
target_desktop = self._internal_manager.FindDesktop(target_guid)
|
|
321
347
|
except Exception:
|
|
322
|
-
logger.error(f"Could not find desktop with
|
|
348
|
+
logger.error(f"Could not find desktop with GUID {target_guid_str}")
|
|
323
349
|
return
|
|
324
350
|
|
|
325
351
|
# Find a fallback desktop
|
|
@@ -328,7 +354,7 @@ class VirtualDesktopManager:
|
|
|
328
354
|
fallback_desktop = None
|
|
329
355
|
|
|
330
356
|
for i in range(count):
|
|
331
|
-
unk = desktops_array.GetAt(i, IVirtualDesktop._iid_)
|
|
357
|
+
unk = desktops_array.GetAt(i, byref(IVirtualDesktop._iid_))
|
|
332
358
|
candidate = unk.QueryInterface(IVirtualDesktop)
|
|
333
359
|
candidate_id = candidate.GetID()
|
|
334
360
|
if str(candidate_id) != str(target_guid):
|
|
@@ -336,31 +362,37 @@ class VirtualDesktopManager:
|
|
|
336
362
|
break
|
|
337
363
|
|
|
338
364
|
if not fallback_desktop:
|
|
339
|
-
# If no other desktop, we can't delete the only one? Or create one?
|
|
340
|
-
# Windows usually prevents deleting the last one.
|
|
341
365
|
logger.error("No fallback desktop found (cannot delete the only desktop)")
|
|
342
366
|
return
|
|
343
367
|
|
|
344
368
|
self._internal_manager.RemoveDesktop(target_desktop, fallback_desktop)
|
|
345
369
|
|
|
346
|
-
def rename_desktop(self,
|
|
370
|
+
def rename_desktop(self, desktop_name: str, new_name: str):
|
|
347
371
|
"""
|
|
348
|
-
Renames a virtual desktop.
|
|
372
|
+
Renames a virtual desktop (identified by current Name).
|
|
349
373
|
"""
|
|
350
|
-
|
|
351
|
-
|
|
374
|
+
target_guid_str = self._resolve_to_guid(desktop_name)
|
|
375
|
+
if not target_guid_str:
|
|
376
|
+
logger.error(f"Desktop '{desktop_name}' not found.")
|
|
377
|
+
return
|
|
352
378
|
|
|
353
|
-
target_guid_str
|
|
354
|
-
|
|
379
|
+
self.rename_desktop_by_guid(target_guid_str, new_name)
|
|
380
|
+
|
|
381
|
+
def rename_desktop_by_guid(self, guid_str: str, new_name: str):
|
|
382
|
+
"""
|
|
383
|
+
Internal helper to rename by GUID.
|
|
384
|
+
"""
|
|
385
|
+
if not self._internal_manager:
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
target_guid = GUID(guid_str)
|
|
355
389
|
try:
|
|
356
390
|
target_desktop = self._internal_manager.FindDesktop(target_guid)
|
|
357
391
|
except Exception:
|
|
358
|
-
logger.error(f"Could not find desktop with ID {desktop_id}")
|
|
359
392
|
return
|
|
360
393
|
|
|
361
394
|
hs_name = create_hstring(new_name)
|
|
362
395
|
try:
|
|
363
|
-
# Check if SetName method exists (it might not on Windows 10)
|
|
364
396
|
if hasattr(self._internal_manager, "SetName"):
|
|
365
397
|
self._internal_manager.SetName(target_desktop, hs_name)
|
|
366
398
|
else:
|
|
@@ -370,30 +402,34 @@ class VirtualDesktopManager:
|
|
|
370
402
|
finally:
|
|
371
403
|
delete_hstring(hs_name)
|
|
372
404
|
|
|
373
|
-
def switch_desktop(self,
|
|
405
|
+
def switch_desktop(self, desktop_name: str):
|
|
374
406
|
"""
|
|
375
|
-
Switches to the specified virtual desktop.
|
|
407
|
+
Switches to the specified virtual desktop (by Name).
|
|
376
408
|
"""
|
|
377
409
|
if not self._internal_manager:
|
|
378
410
|
raise RuntimeError("Internal VDM not initialized")
|
|
379
411
|
|
|
380
|
-
target_guid_str =
|
|
412
|
+
target_guid_str = self._resolve_to_guid(desktop_name)
|
|
413
|
+
if not target_guid_str:
|
|
414
|
+
logger.error(f"Desktop '{desktop_name}' not found")
|
|
415
|
+
return
|
|
416
|
+
|
|
381
417
|
target_guid = GUID(target_guid_str)
|
|
382
418
|
try:
|
|
383
419
|
target_desktop = self._internal_manager.FindDesktop(target_guid)
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
self._internal_manager.SwitchDesktop(target_desktop)
|
|
420
|
+
self._internal_manager.SwitchDesktop(target_desktop)
|
|
421
|
+
except Exception as e:
|
|
422
|
+
logger.error(f"Failed to switch desktop: {e}")
|
|
389
423
|
|
|
390
424
|
def get_all_desktops(self) -> list[dict]:
|
|
391
425
|
"""
|
|
392
426
|
Returns a list of all virtual desktops.
|
|
393
|
-
|
|
427
|
+
Returns [{'name': 'Desktop 1', 'id': '...'}, ...]
|
|
428
|
+
Note: ID is kept for internal robustness but name is preferred.
|
|
394
429
|
"""
|
|
395
430
|
if not self._internal_manager:
|
|
396
|
-
|
|
431
|
+
# Fallback for Server/unsupported builds
|
|
432
|
+
return [{'id': '00000000-0000-0000-0000-000000000000', 'name': 'Default Desktop'}]
|
|
397
433
|
|
|
398
434
|
desktops_array = self._internal_manager.GetDesktops()
|
|
399
435
|
count = desktops_array.GetCount()
|
|
@@ -401,25 +437,22 @@ class VirtualDesktopManager:
|
|
|
401
437
|
result = []
|
|
402
438
|
for i in range(count):
|
|
403
439
|
try:
|
|
404
|
-
unk = desktops_array.GetAt(i, IVirtualDesktop._iid_)
|
|
440
|
+
unk = desktops_array.GetAt(i, byref(IVirtualDesktop._iid_))
|
|
405
441
|
desktop = unk.QueryInterface(IVirtualDesktop)
|
|
406
|
-
guid = desktop
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
# Windows 10 interface might not support GetName commonly or it might fail
|
|
412
|
-
if hasattr(desktop, "GetName"):
|
|
413
|
-
hname = desktop.GetName()
|
|
414
|
-
name = ctypes.wstring_at(hname)
|
|
415
|
-
delete_hstring(hname)
|
|
416
|
-
except Exception:
|
|
417
|
-
pass # Name retrieval failed, ignore
|
|
442
|
+
guid = getattr(desktop, "GetID", lambda: None)()
|
|
443
|
+
if not guid: continue
|
|
444
|
+
|
|
445
|
+
guid_str = str(guid)
|
|
446
|
+
# simple_id = _get_simple_id(guid_str)
|
|
418
447
|
|
|
419
|
-
|
|
448
|
+
# Get Name from Registry or Fallback
|
|
449
|
+
reg_name = self._get_name_from_registry(guid_str)
|
|
450
|
+
if reg_name:
|
|
451
|
+
name = reg_name
|
|
452
|
+
else:
|
|
420
453
|
name = f"Desktop {i+1}"
|
|
421
454
|
|
|
422
|
-
result.append({'id':
|
|
455
|
+
result.append({'id': guid_str, 'name': name})
|
|
423
456
|
except Exception as e:
|
|
424
457
|
logger.error(f"Error retrieving desktop at index {i}: {e}")
|
|
425
458
|
continue
|
|
@@ -430,61 +463,40 @@ class VirtualDesktopManager:
|
|
|
430
463
|
def get_current_desktop(self) -> dict:
|
|
431
464
|
"""
|
|
432
465
|
Returns info about the current virtual desktop.
|
|
433
|
-
Returns: {'
|
|
466
|
+
Returns: {'name': str, 'id': str}
|
|
434
467
|
"""
|
|
435
468
|
if not self._internal_manager:
|
|
436
|
-
|
|
469
|
+
# Fallback for Server/unsupported builds
|
|
470
|
+
return {'id': '00000000-0000-0000-0000-000000000000', 'name': 'Default Desktop'}
|
|
437
471
|
|
|
438
472
|
current_desktop = self._internal_manager.GetCurrentDesktop()
|
|
439
473
|
guid = current_desktop.GetID()
|
|
440
|
-
|
|
474
|
+
guid_str = str(guid)
|
|
475
|
+
# simple_id = _get_simple_id(guid_str)
|
|
441
476
|
|
|
442
|
-
name
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
delete_hstring(hname)
|
|
449
|
-
except Exception:
|
|
450
|
-
pass
|
|
477
|
+
# We need the index to determine fallback name if registry is empty
|
|
478
|
+
# But scanning all is easier to reuse logic
|
|
479
|
+
all_desktops = self.get_all_desktops()
|
|
480
|
+
for d in all_desktops:
|
|
481
|
+
if d['id'] == guid_str:
|
|
482
|
+
return d
|
|
451
483
|
|
|
452
|
-
|
|
453
|
-
# Fallback logic
|
|
454
|
-
desktops_array = self._internal_manager.GetDesktops()
|
|
455
|
-
count = desktops_array.GetCount()
|
|
456
|
-
current_guid_str = str(guid)
|
|
457
|
-
|
|
458
|
-
found_name = "Current Desktop"
|
|
459
|
-
for i in range(count):
|
|
460
|
-
try:
|
|
461
|
-
unk = desktops_array.GetAt(i, IVirtualDesktop._iid_)
|
|
462
|
-
candidate = unk.QueryInterface(IVirtualDesktop)
|
|
463
|
-
candidate_guid_str = str(candidate.GetID())
|
|
464
|
-
if candidate_guid_str == current_guid_str:
|
|
465
|
-
found_name = f"Desktop {i+1}"
|
|
466
|
-
break
|
|
467
|
-
except Exception:
|
|
468
|
-
continue
|
|
469
|
-
name = found_name
|
|
470
|
-
|
|
471
|
-
return {'id': simple_id, 'name': name}
|
|
484
|
+
return {'id': guid_str, 'name': "Unknown"}
|
|
472
485
|
|
|
473
486
|
def create_desktop(name: str = None) -> str:
|
|
474
487
|
return _get_manager().create_desktop(name)
|
|
475
488
|
|
|
476
|
-
def remove_desktop(
|
|
477
|
-
_get_manager().remove_desktop(
|
|
489
|
+
def remove_desktop(desktop_name: str):
|
|
490
|
+
_get_manager().remove_desktop(desktop_name)
|
|
478
491
|
|
|
479
|
-
def rename_desktop(
|
|
480
|
-
_get_manager().rename_desktop(
|
|
492
|
+
def rename_desktop(desktop_name: str, new_name: str):
|
|
493
|
+
_get_manager().rename_desktop(desktop_name, new_name)
|
|
481
494
|
|
|
482
|
-
def switch_desktop(
|
|
483
|
-
_get_manager().switch_desktop(
|
|
495
|
+
def switch_desktop(desktop_name: str):
|
|
496
|
+
_get_manager().switch_desktop(desktop_name)
|
|
484
497
|
|
|
485
498
|
def get_all_desktops() -> list[dict]:
|
|
486
499
|
return _get_manager().get_all_desktops()
|
|
487
500
|
|
|
488
501
|
def get_current_desktop() -> dict:
|
|
489
502
|
return _get_manager().get_current_desktop()
|
|
490
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: windows-mcp
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Lightweight MCP Server for interacting with Windows Operating System.
|
|
5
5
|
Project-URL: homepage, https://github.com/CursorTouch
|
|
6
6
|
Author-email: Jeomon George <jeogeoalukka@gmail.com>
|
|
@@ -73,7 +73,9 @@ Description-Content-Type: text/markdown
|
|
|
73
73
|
mcp-name: io.github.CursorTouch/Windows-MCP
|
|
74
74
|
|
|
75
75
|
## Updates
|
|
76
|
-
-
|
|
76
|
+
- v0.6.2: Extended boolean parameter compatibility and updated `manifest.json` for `mcpb` compatibility in Claude Desktop (Thanks to bryan-anthropic).
|
|
77
|
+
- v0.6.1: Fixed `Shell` tool output issues and improved boolean parameter compatibility (Thanks to @yakub268).
|
|
78
|
+
- Windows-MCP reached `1M+ Users` in [Claude Desktop Extensiosn](https://claude.ai/directory).
|
|
77
79
|
- Windows-MCP is now available on [PyPI](https://pypi.org/project/windows-mcp/) (thus supports `uvx windows-mcp`)
|
|
78
80
|
- Windows-MCP is added to [MCP Registry](https://github.com/modelcontextprotocol/registry)
|
|
79
81
|
- Try out 🪟[Windows-Use](https://github.com/CursorTouch/Windows-Use)!!, an agent built using Windows-MCP.
|
|
@@ -110,7 +112,7 @@ mcp-name: io.github.CursorTouch/Windows-MCP
|
|
|
110
112
|
Easily adapt or extend tools to suit your unique automation or AI integration needs.
|
|
111
113
|
|
|
112
114
|
- **Real-Time Interaction**
|
|
113
|
-
Typical latency between actions (e.g., from one mouse click to the next) ranges from **0.
|
|
115
|
+
Typical latency between actions (e.g., from one mouse click to the next) ranges from **0.2 to 0.9 secs**, and may slightly vary based on the number of active applications and system load, also the inferencing speed of the llm.
|
|
114
116
|
|
|
115
117
|
- **DOM Mode for Browser Automation**
|
|
116
118
|
Special `use_dom=True` mode for State-Tool that focuses exclusively on web page content, filtering out browser UI elements for cleaner, more efficient web automation.
|
|
@@ -347,8 +349,7 @@ MCP Client can access the following tools to interact with Windows:
|
|
|
347
349
|
- `Click`: Click on the screen at the given coordinates.
|
|
348
350
|
- `Type`: Type text on an element (optionally clears existing text).
|
|
349
351
|
- `Scroll`: Scroll vertically or horizontally on the window or specific regions.
|
|
350
|
-
- `
|
|
351
|
-
- `Move`: Move mouse pointer.
|
|
352
|
+
- `Move`: Move mouse pointer or drag (set drag=True) to coordinates.
|
|
352
353
|
- `Shortcut`: Press keyboard shortcuts (`Ctrl+c`, `Alt+Tab`, etc).
|
|
353
354
|
- `Wait`: Pause for a defined duration.
|
|
354
355
|
- `Snapshot`: Combined snapshot of default language, browser, active apps and interactive, textual and scrollable elements along with screenshot of the desktop. Supports `use_dom=True` for browser content extraction (web page elements only) and `use_vision=True` for including screenshots.
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
windows_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
windows_mcp/__main__.py,sha256=
|
|
2
|
+
windows_mcp/__main__.py,sha256=IEiOEC1DiVxvOFCg82fba-94TXQeqYGW0uC9dq5QqdI,15040
|
|
3
3
|
windows_mcp/analytics.py,sha256=yir5IWAI_YN9dmy9KOVIRYsZbeAO12INcxvY2WJSWCU,6307
|
|
4
4
|
windows_mcp/desktop/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
windows_mcp/desktop/config.py,sha256=LxMWSyYOUfMKzkk5ts46dV1NrAnukS2zHjTfyEKRc-A,364
|
|
6
|
-
windows_mcp/desktop/service.py,sha256=
|
|
7
|
-
windows_mcp/desktop/views.py,sha256=
|
|
6
|
+
windows_mcp/desktop/service.py,sha256=Vrq3tHYThWXuV50cj63JGzP-Mx22UiU5Rm0G2TUQiCc,30994
|
|
7
|
+
windows_mcp/desktop/views.py,sha256=pkIJQrriAoKyCxdWP1li4MoThB_Us0xFn21Pbm7eHJ4,2265
|
|
8
8
|
windows_mcp/tree/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
windows_mcp/tree/cache_utils.py,sha256=xuS4Gt96Az8eYoo9cu464NRZWuGQngDCgAhT2iYX-j8,5361
|
|
10
10
|
windows_mcp/tree/config.py,sha256=2UbH76pA5VdlbFQ8-PCog-MoGxLmk5Pv1mvfAAg_9HQ,1496
|
|
11
|
-
windows_mcp/tree/service.py,sha256=
|
|
12
|
-
windows_mcp/tree/utils.py,sha256=
|
|
13
|
-
windows_mcp/tree/views.py,sha256=
|
|
11
|
+
windows_mcp/tree/service.py,sha256=hcRwAU4ahUGeOIWyaVg4_Un_pIXNx8_PS2-rTY91hds,30656
|
|
12
|
+
windows_mcp/tree/utils.py,sha256=Sfy9i7JGxwpynYbU-t_fgTNgDshLX8brO6Z23ySaN1w,914
|
|
13
|
+
windows_mcp/tree/views.py,sha256=C_3Cmm2t7oeEvbsAz8Qk8KNOo5v2Mj289sUj5m-9KXQ,4840
|
|
14
14
|
windows_mcp/uia/__init__.py,sha256=ePdeJOHmTihMedolZUEL59nKDXsBVyofT80miP_5G-Y,93
|
|
15
|
-
windows_mcp/uia/controls.py,sha256=
|
|
16
|
-
windows_mcp/uia/core.py,sha256=
|
|
15
|
+
windows_mcp/uia/controls.py,sha256=u8Z8-HNtquzeLM_MS7ASiOiTAq0pY1OO_rqi0LTppwU,221908
|
|
16
|
+
windows_mcp/uia/core.py,sha256=0GIKuTATu4Rd1FXRKfb5TXnYZHeENdFJrSINn1AIuLQ,92943
|
|
17
17
|
windows_mcp/uia/enums.py,sha256=DYcypTL8zkygdjZUezE2jgz9RMBM9AWe8ofvubLHRCg,83577
|
|
18
18
|
windows_mcp/uia/events.py,sha256=9Xwcn-X1dy2t6WlbwDsIedcKq1SIIHGZ183qpohfFBk,4726
|
|
19
19
|
windows_mcp/uia/patterns.py,sha256=CShOYqYMv7wdwDpNYZZZU3WZuLMvYhAAmnbzQ2Hx7aA,102692
|
|
20
20
|
windows_mcp/vdm/__init__.py,sha256=nE8jJjGJaKuJKCyuf6QLfrj8xPj8IqGKeH2VXP3FMzY,21
|
|
21
|
-
windows_mcp/vdm/core.py,sha256=
|
|
21
|
+
windows_mcp/vdm/core.py,sha256=E5GxlWAxrhXMZm635oXQ_vLFo0tBkPuM0JFwtW7nANE,22216
|
|
22
22
|
windows_mcp/watchdog/__init__.py,sha256=ExC350_KZXwTENsLQfESSPUJZfwsEjrAvGtLRPCqqjk,31
|
|
23
23
|
windows_mcp/watchdog/event_handlers.py,sha256=mHw2msgAjkGuYwTp-U6tYX-gL4S7N1J1HQNmyQ-sdVk,2052
|
|
24
24
|
windows_mcp/watchdog/service.py,sha256=9Tpoq8Ma3MF0Zq7yJKkLOx8NCm4bcUeJY_P1GPkjeLk,9099
|
|
25
|
-
windows_mcp-0.6.
|
|
26
|
-
windows_mcp-0.6.
|
|
27
|
-
windows_mcp-0.6.
|
|
28
|
-
windows_mcp-0.6.
|
|
29
|
-
windows_mcp-0.6.
|
|
25
|
+
windows_mcp-0.6.2.dist-info/METADATA,sha256=kS3y9ehhWdU9vo8VzebO2PFL7gVud1budv4LVcUq9QA,14806
|
|
26
|
+
windows_mcp-0.6.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
27
|
+
windows_mcp-0.6.2.dist-info/entry_points.txt,sha256=wW8NcVQ_OJK5e5GemZSE_nOKyxfUtBPq2acFLszRwaw,58
|
|
28
|
+
windows_mcp-0.6.2.dist-info/licenses/LICENSE.md,sha256=U1UM4Xi_IX-jHnHjGT0rETNia-Ck8gd92iSQMqQ6a8Y,1089
|
|
29
|
+
windows_mcp-0.6.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|