Skip to content

feat(launch_app): UWP/packaged-app support on Windows - #1544

Merged
f-trycua merged 4 commits into
mainfrom
feat/cua-driver-rs-launch-app-uwp
May 17, 2026
Merged

feat(launch_app): UWP/packaged-app support on Windows#1544
f-trycua merged 4 commits into
mainfrom
feat/cua-driver-rs-launch-app-uwp

Conversation

@f-trycua

@f-trycua f-trycua commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds UWP / MSIX / Microsoft Store packaged-app launch support to cua-driver-rs's launch_app MCP tool on Windows, fixing the Win11 notepad problem.

Problem: On Win11, many built-in apps (Notepad, Calculator, Paint, …) ship as packaged apps. The legacy notepad.exe / calc.exe / mspaint.exe in C:\Windows\System32\ are now ~7 KB stubs that launch the packaged equivalent and exit within milliseconds. launch_app {name: "notepad"} was using ShellExecuteExW + GetProcessId on the spawned handle, which returned the stub's pid — gone before any window registers, and list_windows(pid) for that pid is always empty.

Fix: Detect the packaged-app case and route through IApplicationActivationManager::ActivateApplication — the Microsoft-canonical API for launching packaged apps from outside a packaged context. It returns the real UWP process pid via its pid out-parameter.

Routing rules

Order of precedence — most explicit signal wins:

  1. aumid parameter (new) — explicit App User Model ID, packaged-app path.
  2. bundle_id containing ! — treated as AUMID, packaged-app path (Win32 PATH lookups never produce !, so this is a safe marker).
  3. name with no path — first looked up against shell:AppsFolder (the Start Menu index, cached for the lifetime of the driver process). On a hit, packaged path with the resolved AUMID. On miss, falls through to ShellExecuteExW's PATH search.
  4. Explicit path or no-match — existing ShellExecuteExW path (unchanged behavior for plain Win32 apps).

Implementation notes

  • New module crates/platform-windows/src/launch_uwp.rs (~290 LoC) with two entry points: launch_uwp(aumid, args) and resolve_aumid_by_name(display_name).
  • AppsFolder enumeration walks shell:AppsFolder via IShellItem / IEnumShellItems / IShellItem2. Cached for the process lifetime (~200 ms cold enumeration on Win11).
  • PKEY_AppUserModel_ID is defined inline (just a PROPERTYKEY literal) to avoid pulling the entire Win32_Storage_EnhancedStorage feature subtree just for one constant. Only Win32_UI_Shell_PropertiesSystem is added to the windows-rs feature list (needed by IShellItem2::GetString).
  • Response bundle_id is the AUMID actually used when the packaged path was taken (so callers can round-trip to relaunch), and null for plain Win32 launches.

Commits

  1. 4c65562f — add launch_uwp module, no wiring (so existing callers aren't surprised by a behavior change in a single commit).
  2. b6a89b50 — wire into LaunchAppTool::invoke, update schema + description, update PARITY.md, add Windows-only callout to mcp-tools.mdx, extend parity example to cover the AUMID path.

Test plan

  • CI cargo build --release --target x86_64-pc-windows-msvc is clean (that target is in the CD matrix; macOS dev host has no cross-toolchain so build verification happens in CI).
  • cargo test -p platform-windows launch_uwp::tests --target x86_64-pc-windows-msvc passes the is_aumid unit tests.
  • Manual smoke on a Win11 host:
    • cua-driver call launch_app '{"bundle_id":"Microsoft.WindowsNotepad_8wekyb3d8bbwe!App"}' returns a non-zero pid; Get-Process -Id <pid> shows Notepad; the response bundle_id round-trips the AUMID.
    • cua-driver call launch_app '{"name":"notepad"}' returns the real packaged-Notepad pid (not a stub pid); windows array is non-empty within the 5×200 ms retry budget.
    • cua-driver call launch_app '{"name":"calc"}' returns a Calculator pid via AppsFolder shortest-prefix match.
    • cua-driver call launch_app '{"name":"regedit.exe"}' still works via the ShellExecuteExW fallback (no AppsFolder match for unpackaged Win32 apps).
    • cua-driver call launch_app '{"path":"C:\\Windows\\System32\\mspaint.exe"}' still uses ShellExecuteExW (explicit path skips packaged routing).
  • launch_app_parity.exe passes on both Win10 (stays on null bundle_id) and Win11 (AUMID bundle_id, explicit-AUMID round-trip).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for launching Windows packaged (UWP/MSIX) apps via a new AUMID input and updated launch precedence (path > aumid > name > bundle_id).
    • Packaged-app activation now returns the real packaged process PID and, when used with URLs, still fans out URL launches via the legacy path.
  • Documentation

    • Clarified Windows 11 packaged-app routing and parameter semantics in the CLI and driver docs; updated parity examples and version header.

Review Change Stack

f-trycua and others added 2 commits May 17, 2026 18:46
Adds `launch_uwp` module exposing two entry points for packaged-app
(Microsoft Store / UWP / MSIX) activation on Windows:

- `launch_uwp(aumid, args)` — calls
  `IApplicationActivationManager::ActivateApplication`, returning the
  **real** UWP process pid (the one whose `MainWindowHandle` the user
  actually sees), not the stub-redirect pid that
  `ShellExecuteExW("notepad")` returns on Win11.

- `resolve_aumid_by_name(display_name)` — enumerates `shell:AppsFolder`
  via `IShellItem`/`IEnumShellItems` and matches a display name to its
  AUMID. Result is cached for the lifetime of the driver process
  (~200 ms cold enumeration on Win11). Strips an optional `.exe` suffix
  so `"notepad.exe"` and `"notepad"` resolve identically.

`PKEY_AppUserModel_ID` is defined inline to avoid pulling in the
entire `Win32_Storage_EnhancedStorage` feature subtree just for one
constant; only `Win32_UI_Shell_PropertiesSystem` is added to the
windows-rs feature list (needed by `IShellItem2::GetString`).

Not wired into `LaunchAppTool` yet — that comes in a follow-up commit
so existing callers aren't surprised by behavior changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires `crate::launch_uwp` into `LaunchAppTool::invoke`. Routing
precedence (most explicit signal wins):

1. `aumid` parameter — explicit AUMID, packaged-app path.
2. `bundle_id` containing `!` — treated as AUMID, packaged-app path
   (Win32 PATH lookups never produce `!`, so this is a safe marker
   of caller intent).
3. `name` with no `path` — first looked up against `shell:AppsFolder`
   (the Start Menu's "all apps" index). On a hit, packaged path with
   the resolved AUMID. On miss, falls through to `ShellExecuteExW`.
4. `path` or no match — existing `ShellExecuteExW` path (unchanged).

Win11 `launch_app {name: "notepad"}` now returns the real packaged
Notepad pid instead of the ~7 KB System32 stub pid (which exits within
milliseconds and is useless for `list_windows` / `get_window_state`).

Schema additions:
- `aumid` — optional explicit AUMID, cleaner than overloading
  `bundle_id`. Takes precedence.
- `bundle_id` description updated to call out AUMID detection.
- `name` description updated to call out the AppsFolder lookup.

Response shape: `bundle_id` is now the AUMID actually used when the
packaged path was taken (so the caller can round-trip the same value
to relaunch), and `null` for plain Win32 launches.

Other changes:
- Fixes a pointer-coercion compile bug in `launch_uwp::enumerate_apps_folder`
  (`Option<*mut u32>` needs an explicit `as *mut u32` cast — was
  `Some(&mut fetched)` which doesn't unify to the right type).
- Parity example (`launch_app_parity.rs`) now exercises both the plain
  `name` path and an explicit-AUMID path, and accepts either `null` or
  an AUMID for `bundle_id` in the response.
- PARITY.md updated with the new fix list entries.
- mcp-tools.mdx — Windows-only callout describing the routing rules
  plus an `aumid` arg-list entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment May 17, 2026 5:26pm

Request Review

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2f15023f-3356-48c0-b59a-8e61c4ee5cb2

📥 Commits

Reviewing files that changed from the base of the PR and between b4536a9 and a5c1007.

⛔ Files ignored due to path filters (1)
  • libs/cua-driver-rs/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • docs/content/docs/cua-driver/reference/cli-reference.mdx
  • docs/content/docs/cua-driver/reference/mcp-tools.mdx
  • libs/cua-driver-rs/PARITY.md
  • libs/cua-driver-rs/crates/platform-windows/Cargo.toml
  • libs/cua-driver-rs/crates/platform-windows/examples/launch_app_parity.rs
  • libs/cua-driver-rs/crates/platform-windows/src/launch_uwp.rs
  • libs/cua-driver-rs/crates/platform-windows/src/lib.rs
  • libs/cua-driver-rs/crates/platform-windows/src/tools/impl_.rs

📝 Walkthrough

Walkthrough

Adds Windows packaged-app (UWP/MSIX) launching by AUMID: new launch_uwp module (COM activation, AppsFolder name→AUMID resolution with cache), integrates into launch_app routing (path > aumid > name > bundle_id), and updates docs, parity notes, and examples for Win11 behavior.

Changes

Windows UWP/MSIX launcher integration

Layer / File(s) Summary
UWP/MSIX module implementation and wiring
libs/cua-driver-rs/crates/platform-windows/src/launch_uwp.rs, libs/cua-driver-rs/crates/platform-windows/src/lib.rs, libs/cua-driver-rs/crates/platform-windows/Cargo.toml
New launch_uwp module provides launch_uwp(aumid, args) for COM-based packaged app activation, is_aumid(s) AUMID heuristic, and resolve_aumid_by_name(display_name) with a process-lifetime cached AppsFolder snapshot; includes COM memory helpers and unit tests.
Launch_app tool schema and routing refactor
libs/cua-driver-rs/crates/platform-windows/src/tools/impl_.rs
LaunchAppTool adds explicit aumid input, updates input schema and routing precedence to path > aumid > name > bundle_id, and branches to packaged activation via crate::launch_uwp::launch_uwp or falls back to ShellExecuteExW; URLs are still fanned out via ShellExecuteExW. Response bundle_id is set to the resolved AUMID for packaged launches or null for Win32 launches.
API documentation and parity validation
docs/content/docs/cua-driver/reference/mcp-tools.mdx, docs/content/docs/cua-driver/reference/cli-reference.mdx, libs/cua-driver-rs/PARITY.md, libs/cua-driver-rs/crates/platform-windows/examples/launch_app_parity.rs
Docs header versions updated; PARITY.md documents Win11 packaged-app routing, AUMID precedence, name resolution via shell:AppsFolder and Win10 vs Win11 PID/bundle_id expectations. Example launch_app_parity.rs adds Win11 AUMID-based verification and tightens name-based assertions.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant LaunchAppTool
  participant LaunchUWPModule
  participant IApplicationActivationManager
  participant ShellExecuteExW
  Caller->>LaunchAppTool: invoke(target params)
  LaunchAppTool->>LaunchUWPModule: resolve AUMID (aumid / bundle_id / name)
  alt AUMID resolved
    LaunchAppTool->>LaunchUWPModule: launch_uwp(aumid, args)
    LaunchUWPModule->>IApplicationActivationManager: ActivateApplication(aumid, arguments)
    IApplicationActivationManager-->>LaunchUWPModule: real packaged PID
    LaunchUWPModule-->>LaunchAppTool: packaged PID
  else no AUMID
    LaunchAppTool->>ShellExecuteExW: ShellExecuteExW(target)
    ShellExecuteExW-->>LaunchAppTool: stub/parent PID
  end
  LaunchAppTool-->>Caller: structured response (pid, running, bundle_id)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A rabbit hops through Windows glass,
Activates apps with a well-placed pass,
AUMIDs found and names resolved true,
Cached shell lists and COM calls too —
Hooray for launches, tidy and new! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(launch_app): UWP/packaged-app support on Windows' clearly and specifically describes the main change—adding UWP/packaged-app launch support to the launch_app tool on Windows. It is directly related to the primary objective and aligns with the substantial changes across documentation, new module, and tool integration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cua-driver-rs-launch-app-uwp

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
libs/cua-driver-rs/crates/platform-windows/src/launch_uwp.rs (1)

182-195: 💤 Low value

Consider caching lowercase display names to avoid repeated allocations.

The prefix-match loop calls entry.display_name.to_lowercase() on every entry per lookup. Since entries is already cached, you could store a pre-lowercased display name alongside the original to avoid repeated allocations on each call. This is a minor optimization given the cache is small (~150–300 entries) and lookups are infrequent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/cua-driver-rs/crates/platform-windows/src/launch_uwp.rs` around lines
182 - 195, The loop repeatedly calls entry.display_name.to_lowercase(); modify
the cached data structure (AppsFolderEntry) to include a precomputed
lowercase_display_name (String) when entries are built, then change this
function to compare against
entry.lowercase_display_name.starts_with(query_stripped) instead of calling
to_lowercase() each iteration; keep the existing selection logic (best, entries,
aumid) unchanged and ensure any code that constructs AppsFolderEntry populates
the new lowercase field.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@libs/cua-driver-rs/crates/platform-windows/examples/launch_app_parity.rs`:
- Around line 109-127: The match currently treats any non-(Some(pid),
Some(bundle)) result as a benign skip; tighten it so only an explicit "not
installed/not found" tool error yields the skip. Update the match on (pid_aumid,
bundle_id_aumid) to: if pid exists always attempt cleanup (use the existing
std::process::Command::new("taskkill") cleanup regardless), if bundle_id_aumid
is None inspect the actual error value returned by the AUMID launch call and
only print the skip message when that error equals the explicit
not-installed/not-found sentinel (or its string contains "not installed"/"not
found"); otherwise treat the case as a test failure (panic/assert) so real
regressions surface. Ensure you reference pid_aumid, bundle_id_aumid, aumid and
keep the taskkill cleanup intact.

---

Nitpick comments:
In `@libs/cua-driver-rs/crates/platform-windows/src/launch_uwp.rs`:
- Around line 182-195: The loop repeatedly calls
entry.display_name.to_lowercase(); modify the cached data structure
(AppsFolderEntry) to include a precomputed lowercase_display_name (String) when
entries are built, then change this function to compare against
entry.lowercase_display_name.starts_with(query_stripped) instead of calling
to_lowercase() each iteration; keep the existing selection logic (best, entries,
aumid) unchanged and ensure any code that constructs AppsFolderEntry populates
the new lowercase field.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c4d7ed5-6d43-486a-9bc5-b4a0e16ac712

📥 Commits

Reviewing files that changed from the base of the PR and between b4536a9 and b6a89b5.

📒 Files selected for processing (7)
  • docs/content/docs/cua-driver/reference/mcp-tools.mdx
  • libs/cua-driver-rs/PARITY.md
  • libs/cua-driver-rs/crates/platform-windows/Cargo.toml
  • libs/cua-driver-rs/crates/platform-windows/examples/launch_app_parity.rs
  • libs/cua-driver-rs/crates/platform-windows/src/launch_uwp.rs
  • libs/cua-driver-rs/crates/platform-windows/src/lib.rs
  • libs/cua-driver-rs/crates/platform-windows/src/tools/impl_.rs

Comment thread libs/cua-driver-rs/crates/platform-windows/examples/launch_app_parity.rs Outdated
f-trycua added 2 commits May 17, 2026 19:22
…tch (CodeRabbit)

- launch_uwp: AppsFolderEntry now caches lowercase_display_name at
  construction; the name-lookup loop compares against the cached field
  instead of calling to_lowercase() per-iteration.
- launch_app_parity example: tightened the match on (pid_aumid,
  bundle_id_aumid). Always run taskkill cleanup when pid exists; only
  print the "skip" message when the error explicitly indicates the AUMID
  is not installed; otherwise panic so real regressions surface.
@f-trycua

Copy link
Copy Markdown
Collaborator Author

Pushed CR fixups + docs regen: 7a3adf8a (launch_uwp lowercase cache + parity-example tighter match), plus the autogen-doc refresh that follows. @coderabbitai please re-review. Lychee failure is pre-existing redirect noise.

@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

@f-trycua Sure! I'll kick off a fresh review of the PR with the latest commits, including the 7a3adf8a fixups (lowercase cache, tighter parity-example matching, and doc refresh). The pre-existing Lychee redirect noise is noted and won't be flagged.

✅ Actions performed

Full review triggered.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants