From f0ed1f18daf46035aef62acaafb48dba52f7f258 Mon Sep 17 00:00:00 2001 From: Atirna <288419661+atirna@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:49:48 +0530 Subject: [PATCH] fix: resolve localized Start Menu app names --- src/windows_mcp/desktop/service.py | 36 ++++++++++++++++++--- tests/test_app_launch_localized.py | 51 ++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 tests/test_app_launch_localized.py diff --git a/src/windows_mcp/desktop/service.py b/src/windows_mcp/desktop/service.py index cbccead2..f0efc4d9 100755 --- a/src/windows_mcp/desktop/service.py +++ b/src/windows_mcp/desktop/service.py @@ -329,6 +329,7 @@ def get_apps_from_start_menu(self) -> dict[str, str]: command = "Get-StartApps | ConvertTo-Csv -NoTypeInformation" apps_info, status = PowerShellExecutor.execute_command(command) + apps: dict[str, str] = {} if status == 0 and apps_info and apps_info.strip(): try: reader = csv.DictReader(io.StringIO(apps_info.strip())) @@ -337,14 +338,39 @@ def get_apps_from_start_menu(self) -> dict[str, str]: for row in reader if row.get("Name") and row.get("AppID") } - if apps: - return apps except Exception as e: logger.warning(f"Error parsing Get-StartApps output: {e}") - # Fallback: scan Start Menu shortcut folders (works on all Windows versions) - logger.info("Get-StartApps unavailable, falling back to Start Menu folder scan") - return self._get_apps_from_shortcuts() + if not apps: + # Fallback: scan Start Menu shortcut folders (works on all Windows versions) + logger.info("Get-StartApps unavailable, falling back to Start Menu folder scan") + apps = self._get_apps_from_shortcuts() + + # AppsFolder supplies the display name rendered for the current Windows locale. + for name, appid in self._get_apps_folder_display_names().items(): + apps.setdefault(name, appid) + return apps + + def _get_apps_folder_display_names(self) -> dict[str, str]: + """Return localized Start Menu display names mapped to AppUserModelIDs.""" + command = ( + "(New-Object -ComObject Shell.Application)." + "NameSpace('shell:::{4234d49b-0245-4df3-b780-3893943456e1}').Items() " + "| Select-Object Name,@{n='AppID';e={$_.Path}} | ConvertTo-Csv -NoTypeInformation" + ) + apps_info, status = PowerShellExecutor.execute_command(command) + + if status == 0 and apps_info and apps_info.strip(): + try: + reader = csv.DictReader(io.StringIO(apps_info.strip())) + return { + row.get("Name", "").lower(): row.get("AppID", "") + for row in reader + if row.get("Name") and row.get("AppID") + } + except Exception as e: + logger.warning(f"Error parsing AppsFolder display names: {e}") + return {} def _get_apps_from_shortcuts(self) -> dict[str, str]: """Scan Start Menu folders for .lnk shortcuts as a fallback for Get-StartApps.""" diff --git a/tests/test_app_launch_localized.py b/tests/test_app_launch_localized.py new file mode 100644 index 00000000..82754a97 --- /dev/null +++ b/tests/test_app_launch_localized.py @@ -0,0 +1,51 @@ +import sys + +import pytest + +if sys.platform == "win32": + from windows_mcp.desktop.service import Desktop + from windows_mcp.powershell import PowerShellExecutor +else: + pytestmark = pytest.mark.skip(reason="Desktop imports Windows-only pywin32 modules") + + +def test_start_menu_apps_include_localized_appsfolder_name(monkeypatch: pytest.MonkeyPatch) -> None: + app_id = "Microsoft.WindowsNotepad_8wekyb3d8bbwe!App" + commands: list[str] = [] + + def execute_command(command: str, *_: object) -> tuple[str, int]: + commands.append(command) + if "Get-StartApps" in command: + return f'"Name","AppID"\n"Notepad","{app_id}"\n', 0 + if "4234d49b-0245-4df3-b780-3893943456e1" in command: + return f'"Name","AppID"\n"记事本","{app_id}"\n', 0 + if command == f"Start-Process 'shell:AppsFolder\\{app_id}'": + return "", 0 + pytest.fail(f"unexpected PowerShell command: {command}") + + monkeypatch.setattr(PowerShellExecutor, "execute_command", staticmethod(execute_command)) + + desktop = Desktop.__new__(Desktop) + monkeypatch.setattr(desktop, "_check_app_exists", lambda _: True) + + assert desktop.launch_app("Notepad") == ("", 0, 0) + assert desktop.launch_app("记事本") == ("", 0, 0) + assert commands[-1] == f"Start-Process 'shell:AppsFolder\\{app_id}'" + + +def test_start_menu_shortcut_fallback_is_preserved_when_appsfolder_is_unavailable( + monkeypatch: pytest.MonkeyPatch, +) -> None: + shortcut_apps = { + "legacy app": r"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Legacy.lnk" + } + + monkeypatch.setattr( + PowerShellExecutor, + "execute_command", + staticmethod(lambda *_: ("", 1)), + ) + desktop = Desktop.__new__(Desktop) + monkeypatch.setattr(desktop, "_get_apps_from_shortcuts", lambda: shortcut_apps) + + assert desktop.get_apps_from_start_menu() == shortcut_apps