window_management.py•4.51 kB
import asyncio
import json
import win32gui
import win32con
import win32process
import win32api
import psutil
from fastmcp import FastMCP
mcp: FastMCP = FastMCP(
name="window_management"
)
def get_process_info(pid: int) -> dict:
try:
proc = psutil.Process(pid)
return {
"pid": pid,
"exe": proc.name(),
}
except psutil.NoSuchProcess:
return {
"pid": pid,
"exe": "<terminated>"
}
@mcp.tool("get_foreground_window_info")
async def get_foreground_window_info() -> str:
"""Return information about the currently focused (foreground) window."""
hwnd = win32gui.GetForegroundWindow()
if hwnd == 0:
return json.dumps({"error": "No active window"})
_, pid = win32process.GetWindowThreadProcessId(hwnd)
info = get_process_info(pid)
info.update({
"hwnd": hwnd,
"title": win32gui.GetWindowText(hwnd),
"class": win32gui.GetClassName(hwnd),
})
return json.dumps(info, ensure_ascii=False)
@mcp.tool("get_window_list")
async def list_open_windows() -> str:
"""Return a list of all top-level visible windows."""
windows = []
def callback(hwnd, _):
if win32gui.IsWindowVisible(hwnd) and win32gui.GetWindowText(hwnd):
_, pid = win32process.GetWindowThreadProcessId(hwnd)
info = get_process_info(pid)
info.update({
"hwnd": hwnd,
"title": win32gui.GetWindowText(hwnd),
"class": win32gui.GetClassName(hwnd),
})
windows.append(info)
win32gui.EnumWindows(callback, None)
return json.dumps(windows, ensure_ascii=False)
@mcp.tool("focus_window")
async def focus_window(hwnd: int) -> str:
"""Force focus a window using all known safe tricks (thread attach, fake input, fallback restore)."""
try:
hwnd = int(hwnd)
if not win32gui.IsWindow(hwnd):
return "Invalid HWND"
# Step 1: Only restore if minimized (prevent resizing)
if win32gui.IsIconic(hwnd):
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
# Step 2: Try normal focus via thread attach
fg_hwnd = win32gui.GetForegroundWindow()
fg_thread = win32process.GetWindowThreadProcessId(fg_hwnd)[0]
current_thread = win32api.GetCurrentThreadId()
if fg_thread != current_thread:
win32process.AttachThreadInput(fg_thread, current_thread, True)
try:
win32gui.SetForegroundWindow(hwnd)
except Exception:
pass
if fg_thread != current_thread:
win32process.AttachThreadInput(fg_thread, current_thread, False)
# Step 3: Check if it worked
if win32gui.GetForegroundWindow() == hwnd:
return "Focused window successfully"
# Step 4: Fallback — simulate user input (to defeat foreground lock)
win32api.keybd_event(0, 0, 0, 0)
await asyncio.sleep(0.05)
# Step 5: Try again
try:
win32gui.SetForegroundWindow(hwnd)
except Exception:
pass
if win32gui.GetForegroundWindow() == hwnd:
return "Focused window (after simulating input)"
# Step 6: Hard fallback — minimize + restore
win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
await asyncio.sleep(0.2)
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
win32gui.SetForegroundWindow(hwnd)
if win32gui.GetForegroundWindow() == hwnd:
return "Focused window (after minimize/restore trick)"
return "Could not focus window: OS restrictions"
except Exception as e:
return f"Could not focus window: {type(e).__name__}: {e}"
@mcp.tool("close_window")
async def close_window(hwnd: int) -> str:
"""Close the specified window."""
try:
win32gui.PostMessage(hwnd, win32con.WM_CLOSE, 0, 0)
return "Closed window"
except Exception as e:
return f"Could not close window: {type(e).__name__}: {e}"
@mcp.tool("minimize_window")
async def minimize_window(hwnd: int) -> str:
"""Minimize the specified window."""
try:
win32gui.ShowWindow(hwnd, win32con.SW_MINIMIZE)
return "Minimized window"
except Exception as e:
return f"Could not minimize window: {type(e).__name__}: {e}"