Repository navigation
feat(desktop-gpui): resize a picked window to a ratio or size, and focus it to record it alone - #3454
Conversation
…cus it to record it alone
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
hey @greptileai, please re-review the PR |
# Conflicts: # apps/desktop-gpui/src/picker_probe.rs
… pin the size menu's window, ignore stale moves, restore maximized windows
# Conflicts: # apps/desktop-gpui/src/picker_probe.rs
|
hey @greptileai, please re-review the PR |
…he smoke's test window outright
|
hey @greptileai, please re-review the PR |
…gion, and spawn the restore
|
hey @greptileai, please re-review the PR |
… put moved windows back on every non-recording exit
| let limit = cx.background_executor().timer(ARRANGE_SETTLE_LIMIT); | ||
| futures_util::future::select(std::pin::pin!(settled), std::pin::pin!(limit)).await; | ||
| cx.update(|cx| { | ||
| if !cx.global::<AppWindows>().overlays.is_empty() { |
There was a problem hiding this comment.
Deferred overlay Start can fire against a re-armed picker
Medium Severity
With arranged windows pending, start_recording_from_overlay defers the start until in_order(|| ()) drains or ARRANGE_SETTLE_LIMIT (1500 ms) fires, and the overlay stays open and interactive the whole time: the Record click (target_overlay.rs:1252) only defers, and only start_recording_from_overlay_now closes it. If the user presses Escape (dismiss_target_overlays restores window A, closes the overlays) and re-picks window B within that window — likeliest when a hung target app forces the full 1500 ms — the deferred task's only revalidation is !cx.global::<AppWindows>().overlays.is_empty(), true again because of B's overlay. start_recording_from_overlay_now(target, cx) then runs with the click-time ScreenCaptureTarget for A: keep_arranged_windows drains B's arranged entry and discards its original, permanently leaving B's window moved, B's overlays are force-closed, and a recording of cancelled target A starts (A's own put_back is still queued behind the stuck move, so A's bounds may be read mid-restore). The gate must verify the same picker/target is still armed, or dismiss/arm must cancel a pending deferred start.
| cx.update(|cx| { | ||
| if !cx.global::<AppWindows>().overlays.is_empty() { | ||
| start_recording_from_overlay_now(target, cx); |
There was a problem hiding this comment.
While a slow window move holds the queue, the user can press Start, dismiss the picker, and reopen it for another window. After the wait, this check accepts any open overlay and starts the old target, recording the window the user cancelled.
Tie the pending Start to the picker that requested it, and invalidate it on dismissal.
Knowledge Base Used: GPUI desktop client
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/app_windows.rs
Line: 2454-2456
Comment:
**Cancelled Start can resume**
While a slow window move holds the queue, the user can press Start, dismiss the picker, and reopen it for another window. After the wait, this check accepts any open overlay and starts the old `target`, recording the window the user cancelled.
Tie the pending Start to the picker that requested it, and invalidate it on dismissal.
**Knowledge Base Used:** [GPUI desktop client](https://app.greptile.com/cap/-/custom-context/knowledge-base/capsoftware/cap/-/docs/gpui-desktop-client.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| { | ||
| return; | ||
| } | ||
| keep_arranged_windows(cx); |
There was a problem hiding this comment.
Cancelled starts leave windows changed
keep_arranged_windows forgets the original frames before recording checks finish. For example, cancelling the low-storage prompt after pressing Start leaves every resized or focused window changed, even though no recording started. Users must put those windows back by hand.
Keep the saved frames through these checks and restore them on cancellation. Discard them only when capture commits to starting.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/app_windows.rs
Line: 2472
Comment:
**Cancelled starts leave windows changed**
`keep_arranged_windows` forgets the original frames before recording checks finish. For example, cancelling the low-storage prompt after pressing Start leaves every resized or focused window changed, even though no recording started. Users must put those windows back by hand.
Keep the saved frames through these checks and restore them on cancellation. Discard them only when capture commits to starting.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…ows still moving, keep moved windows until recording commits
| ); | ||
| cx.update(|cx| { | ||
| let current = overlay_handles(cx); | ||
| if current.is_empty() || current != overlays { |
There was a problem hiding this comment.
Deferred Start records the click-time target after the pick changes
Medium Severity
The deferred Start captured target from the picker's selection at click time (target_overlay.rs start_recording), but the revalidation at app_windows.rs:2479 only compares overlay-window identity, and the new comment's claim that opening a new picker drops the Start does not hold for a same-mode re-open. While the settle wait runs (up to 5 s with the new limit), the picker stays interactive: a click on another window re-pins through the overlay's click-anywhere-to-select (pin_hovered), and a same-mode re-arm (tray item, hotkey) goes through open_overlays_core/reopens_overlays (app_windows.rs:1951), which keeps the identical overlay windows when the armed mode already matches, so current == overlays passes. The deferred task then calls start_recording_from_overlay_now with the stale target: it force-closes the picker that now presents window B and records window A; keep_arranged_windows at app_windows.rs:4015 then drains every arranged entry including B's saved original, so a B the user resized after the click is left moved with nothing to put it back. Bind the waiting Start to the picked target — compare the click-time target/pinned window against TargetSelect's current one and drop when it moved — not just to the overlay set.
| if !settled { | ||
| tracing::warn!("a picked window is still moving; recording not started"); | ||
| RecordingSession::global(cx).update(cx, |session, cx| { | ||
| session.error = | ||
| Some("The window is still being resized. Try starting again.".into()); | ||
| cx.notify(); | ||
| }); | ||
| return; |
There was a problem hiding this comment.
When a window move takes more than five seconds, Start stops and saves the retry message in session.error. The picker stays open, but only the hidden main window shows that error. The user sees neither a recording nor an explanation until they close the picker.
Show the error in the picker, or reveal the main window when Start times out.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop-gpui/src/app_windows.rs
Line: 2482-2489
Comment:
**Start hides its retry message**
When a window move takes more than five seconds, Start stops and saves the retry message in `session.error`. The picker stays open, but only the hidden main window shows that error. The user sees neither a recording nor an explanation until they close the picker.
Show the error in the picker, or reveal the main window when Start times out.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…rt gives up on a moving window
| now=$(date +%s) | ||
| for client in "$app" /bin/bash /Users/runner/bin/Runner.Worker /Users/runner/bin/Runner.Listener; do | ||
| sudo sqlite3 "$db" "INSERT OR REPLACE INTO access (service,client,client_type,auth_value,auth_reason,auth_version,indirect_object_identifier,flags,last_modified) VALUES ('kTCCServiceScreenCapture','$client',1,2,4,1,'UNUSED',0,$now);" | ||
| for service in kTCCServiceScreenCapture kTCCServiceAccessibility; do |
There was a problem hiding this comment.
P2: macOS picker smoke grants Accessibility TCC to runner processes used by fork-PR code
New loop grants Accessibility TCC to /bin/bash and runner worker processes used by untrusted fork-PR builds.
Grant Accessibility only to the built app, or prevent fork PRs from running this self-hosted smoke with the grant.
AI prompt
Check if this security scanner issue is valid. If so, understand the root cause and fix it. If appropriate, update or add tests. Keep the change focused and preserve intended behavior.
<file name=".github/workflows/picker-smoke.yml">
<violation number="1" location=".github/workflows/picker-smoke.yml:141">
<priority>P2</priority>
<title>macOS picker smoke grants Accessibility TCC to runner processes used by fork-PR code</title>
<evidence>The permission step previously inserted only `kTCCServiceScreenCapture` for the built app, `/bin/bash`, `Runner.Worker`, and `Runner.Listener`. The changed loop now inserts both `kTCCServiceScreenCapture` and `kTCCServiceAccessibility` for the same clients. This workflow is triggered by `pull_request` and, for fork PRs that touch `apps/desktop-gpui/src/**`, runs the PR-built binary and PR-controlled `scripts/setup.js`/`scripts/picker-smoke/run.sh` on Blacksmith macOS runners. Granting Accessibility to `/bin/bash` and the runner worker processes means any later shell step can use macOS UI automation, read other applications' accessibility trees, and synthesize input without an additional prompt. That is broader than the pre-existing screen-capture grant.</evidence>
<recommendation>Do not grant `kTCCServiceAccessibility` to `/bin/bash`, `Runner.Worker`, or `Runner.Listener`. Grant it only to the specific built binary, ideally only after the build has been reviewed, or restrict this workflow to same-repository PRs/trusted branches. If the smoke must run on fork PRs, use an ephemeral runner account that has no access to other applications or credentials and explicitly reset the TCC grant after the job.</recommendation>
</violation>
</file>
There was a problem hiding this comment.
This grant does not widen what PR code can do on that runner. The Blacksmith macOS runner is a single-use VM, and the same job already runs with passwordless sudo (this very step writes TCC.db with it), so code from a fork PR could grant itself any TCC service anyway. Accessibility is needed because the smoke moves another app's window through the AX API, and macOS checks that against the responsible process (the runner that launches cap-gpui), not the binary alone; granting it to the app only would make the window-arrangement check fail.
There was a problem hiding this comment.
The developer’s explanation addresses the core concern: the Blacksmith runner is a single-use VM, so the TCC grant is not persistent, and the same job already runs with passwordless sudo—meaning untrusted code could already modify TCC.db directly. Additionally, the Accessibility grant to the runner processes is technically required because macOS applies the AX permission check to the responsible process launching the app, not the built binary alone, so restricting the grant to the app would break the smoke test. No additional exposure is introduced by this change.
Stacked on #3452.
Problem
In the window picker, the picked window's size was plain text, and the only way to change the shape of what gets recorded was to resize the window by hand. A window recording also captured a region of the screen. #3452 adds the option to record the window by itself, but nothing in the app could turn it on.
Change
foreign_windowdoes the platform work:AXPosition/AXSize, matched to the window through_AXUIElementGetWindow), which Cap already holds the permission for.SetWindowPos, adding back DWM's invisible resize borders and restoring a maximized window first. The maximized window'sWINDOWPLACEMENTis saved, so putting it back maximizes it again with its old restore size._NET_MOVERESIZE_WINDOWand_NET_ACTIVE_WINDOW.SetWindowPoswaits on the target app. So a cancel's restore always lands after a move the user started first. The highlight then animates to wherever the app actually put the window, since apps clamp to their own minimum sizes or cell grids. A move that finishes after the user has picked another window, or closed the picker, leaves the new choice alone.The new overlay strings are literals, like the rest of the overlay on this base. #3423 localizes the overlay, and they move into its catalogue when this rebases onto it. The settings row and options are localized in all seven catalogues.
Test
Unit tests (
window_arrange, andforeign_window's ordering of queued jobs):i18n: the i18n surface tests pin the new keys, and
cap-i18n-buildpasses.Real app:
Picker smoke (real app)now drives a real window on macOS, Windows and Linux X11 through the overlay's own menu and toggle. It picks the window it opened (TextEdit, Notepad or xterm) outright, so the runner's cursor position doesn't matter:Screenshots of each step are uploaded.


Confidence Score: 5/5
The PR appears safe to merge, with a non-blocking gap in feedback when Start times out.Findings
Fix with agent prompt
Summary
This PR adds window resizing and Focus controls to the picker, plus a saved window capture setting.
Reviews (8) · Last reviewed commit: "fix(desktop-gpui): tie a waiting Start t..." · Reviewed by Greptile