Skip to content

Extend resize mode to support targeting window by name#97

Merged
Jeomon merged 1 commit intoCursorTouch:mainfrom
JezaChen:enhance-resize-app
Mar 10, 2026
Merged

Extend resize mode to support targeting window by name#97
Jeomon merged 1 commit intoCursorTouch:mainfrom
JezaChen:enhance-resize-app

Conversation

@JezaChen
Copy link
Contributor

Problem

Currently the resize mode of the App tool only operates on the active window and the name parameter is ignored.

This leads to a subtle bug in practice. When an AI agent (e.g., Claude) is asked to resize a non-active window, it usually first calls switch to bring the target window to the foreground, then calls resize. However, the resize still operates on the window that was active at snapshot time — before the switch happened. So the agent complains that it is always resizing the wrong window and tries rolling back to the pwsh command.

video1.mp4

Note: The stale snapshot issue may warrant a separate fix, but supporting name in resize mode can sidestep the problem.

Solution

  • Add name parameter support to resize_app: When name is provided, the tool fuzzy-matches and targets the specified window directly; when omitted, it falls back to the active window, preserving full backward compatibility.
  • Extract shared window lookup logic into _find_window_by_name helper: Both switch_app and resize_app need to locate a window by fuzzy name match. This duplicated logic is refactored into a single reusable method.
  • Update tool descriptions in __main__.py and manifest.json so the agent understands name is effective in resize mode and uses it correctly.

With these changes, when Claude is asked to resize a non-active window, it will pass the window name directly to the resize call rather than issuing switch first, resulting in correct and reliable behavior.

Changes

  • src/windows_mcp/desktop/service.py
    • New _find_window_by_name(name, refresh_state) method encapsulating window list retrieval and fuzzy matching
    • resize_app now accepts an optional name parameter and delegates to _find_window_by_name when provided
    • switch_app refactored to use _find_window_by_name, removing duplicated code
  • src/windows_mcp/__main__.py — Updated App tool description to reflect that name works in resize mode
  • manifest.json — Updated tool description to match

The demo after modification

video2.mp4

- Extract shared window lookup logic into _find_window_by_name helper
- Add name parameter to resize_app to target a specific window by name
- Update App tool description in __main__.py and manifest.json
Copilot AI review requested due to automatic review settings March 10, 2026 17:07
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the App tool’s resize mode so it can target a specific window by name (via fuzzy matching) instead of always operating on the active window, improving reliability when resizing non-foreground apps.

Changes:

  • Added a shared _find_window_by_name() helper to centralize fuzzy window lookup.
  • Updated resize_app to accept an optional name and resize the matched window (refreshing state when name is provided).
  • Updated App tool descriptions in __main__.py and manifest.json to document the new resize behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
src/windows_mcp/desktop/service.py Adds shared fuzzy window lookup and enables name-targeted resizing; refactors switch_app to reuse the helper.
src/windows_mcp/__main__.py Updates the App tool description to clarify resize can use name.
manifest.json Mirrors the App tool description update for the manifest.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mcp.tool(
name="App",
description="Manages Windows applications with three modes: 'launch' (opens the prescibed application), 'resize' (adjusts active window size/position), 'switch' (brings specific window into focus).",
description="Manages Windows applications with three modes: 'launch' (opens the prescibed application), 'resize' (adjusts the size/position of a named window or the active window if name is omitted), 'switch' (brings specific window into focus).",
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: the tool description says "prescibed"; this should be "prescribed".

Suggested change
description="Manages Windows applications with three modes: 'launch' (opens the prescibed application), 'resize' (adjusts the size/position of a named window or the active window if name is omitted), 'switch' (brings specific window into focus).",
description="Manages Windows applications with three modes: 'launch' (opens the prescribed application), 'resize' (adjusts the size/position of a named window or the active window if name is omitted), 'switch' (brings specific window into focus).",

Copilot uses AI. Check for mistakes.
{
"name": "App",
"description": "Manages Windows applications with three modes: 'launch' (opens the prescibed application), 'resize' (adjusts active window size/position), 'switch' (brings specific window into focus)."
"description": "Manages Windows applications with three modes: 'launch' (opens the prescibed application), 'resize' (adjusts the size/position of a named window or the active window if name is omitted), 'switch' (brings specific window into focus)."
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling: the tool description says "prescibed"; this should be "prescribed".

Suggested change
"description": "Manages Windows applications with three modes: 'launch' (opens the prescibed application), 'resize' (adjusts the size/position of a named window or the active window if name is omitted), 'switch' (brings specific window into focus)."
"description": "Manages Windows applications with three modes: 'launch' (opens the prescribed application), 'resize' (adjusts the size/position of a named window or the active window if name is omitted), 'switch' (brings specific window into focus)."

Copilot uses AI. Check for mistakes.

def resize_app(
self, size: tuple[int, int] = None, loc: tuple[int, int] = None
self, name: str | None = None, size: tuple[int, int] = None, loc: tuple[int, int] = None
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resize_app parameters size/loc default to None but are annotated as non-optional tuple[int, int]. Consider updating them to tuple[int, int] | None (consistent with app()'s loc/size annotations) to keep type hints accurate.

Suggested change
self, name: str | None = None, size: tuple[int, int] = None, loc: tuple[int, int] = None
self, name: str | None = None, size: tuple[int, int] | None = None, loc: tuple[int, int] | None = None

Copilot uses AI. Check for mistakes.
Comment on lines +338 to +344
windows = {window.name: window for window in window_list}
matched_window = process.extractOne(name, list(windows.keys()), score_cutoff=70)
if matched_window is None:
return None, f"Application {name.title()} not found."
window_name, _ = matched_window
return windows.get(window_name), ""

Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_find_window_by_name builds a {window.name: window} dict, so multiple windows with the same title will collide and one will be silently dropped. With resize_app(name=...) this can resize the wrong window in common cases (e.g., multiple Explorer windows titled "Downloads"). Consider matching against a list of windows and, when there are duplicate titles, either (a) disambiguate using additional attributes (handle/process id/class) or (b) detect duplicates for the best match and return an error asking for a more specific name.

Suggested change
windows = {window.name: window for window in window_list}
matched_window = process.extractOne(name, list(windows.keys()), score_cutoff=70)
if matched_window is None:
return None, f"Application {name.title()} not found."
window_name, _ = matched_window
return windows.get(window_name), ""
# Map window names to lists of windows to avoid silently dropping duplicates
name_to_windows: dict[str, list[Window]] = {}
for window in window_list:
name_to_windows.setdefault(window.name, []).append(window)
matched_window = process.extractOne(name, list(name_to_windows.keys()), score_cutoff=70)
if matched_window is None:
return None, f"Application {name.title()} not found."
window_name, _ = matched_window
candidates = name_to_windows.get(window_name, [])
if not candidates:
# Should not normally happen, but handle defensively
return None, f"Application {name.title()} not found."
if len(candidates) > 1:
# Multiple windows share this title; avoid choosing one arbitrarily
return (
None,
f"Multiple windows named '{window_name}' were found. "
"Please make the desired window active and try again, or close other windows with the same title.",
)
return candidates[0], ""

Copilot uses AI. Check for mistakes.
elif active_window.status == Status.MAXIMIZED:
return f"{active_window.name} is maximized", 1
if name is not None:
target_window, error = self._find_window_by_name(name, refresh_state=True)
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resize_app(name=...) forces refresh_state=True, which calls get_state() and triggers a full desktop capture (including tree.get_state(...)). This can make frequent resize operations unexpectedly expensive/slow. Consider adding a lightweight refresh path for window lookup (e.g., re-enumerate windows/active window without rebuilding the full DesktopState/tree) and have _find_window_by_name use that when only window handles/titles are needed.

Suggested change
target_window, error = self._find_window_by_name(name, refresh_state=True)
target_window, error = self._find_window_by_name(name)

Copilot uses AI. Check for mistakes.
reader = csv.DictReader(io.StringIO(response))
return "".join([row.get("DisplayName") for row in reader])

def _find_window_by_name(self, name: str, refresh_state: bool = False) -> tuple["Window | None", str]:
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_find_window_by_name annotates its return type as tuple["Window | None", str] even though Window is imported in this module. Using tuple[Window | None, str] (or tuple[Window | None, str] with postponed evaluation) would be clearer and avoids an unusual forward-ref expression string in type hints.

Suggested change
def _find_window_by_name(self, name: str, refresh_state: bool = False) -> tuple["Window | None", str]:
def _find_window_by_name(self, name: str, refresh_state: bool = False) -> tuple[Window | None, str]:

Copilot uses AI. Check for mistakes.
@Jeomon Jeomon merged commit 06edced into CursorTouch:main Mar 10, 2026
3 of 4 checks passed
@Jeomon
Copy link
Member

Jeomon commented Mar 10, 2026

Thanks and well done

Bro, can we connect just to talk.
mail me: [email protected]

@JezaChen
Copy link
Contributor Author

Sure, I will mail you after work, haha.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants