A mode where selecting an app in the switcher shows thumbnails of that app's individual windows (similar to Windows Alt+Tab or macOS's own Cmd+` / Dock Exposé), allowing the user to pick a specific window instead of just activating the app.
| API | Purpose | Where | Complexity |
|---|---|---|---|
CGWindowListCopyWindowInfo |
Enumerate all windows, filter by PID | platform/macos.rs |
Medium |
CGWindowListCreateImage |
Capture a window to an image (CGImage → PNG bytes) | platform/macos.rs |
High |
CGWindowListCreateDescriptionFromArray |
Get window titles, bounds, level | platform/macos.rs |
Medium |
AXUIElement (Accessibility API) |
Bring a specific sub-window to front (not just the app) | platform/macos.rs |
High |
NSRunningApplication + AXUIElementCreateApplication |
Bridge PID → AX element for window-level focus | platform/macos.rs |
Medium |
These are from Core Graphics (objc2-core-graphics) and Application Services
(objc2-application-services) — both already in the dependency tree.
AppState:
├─ mode: AppMode
│ └─ NEW: Preview
├─ selected_app_windows: Vec<WindowInfo>
└─ window_thumbnails: HashMap<WindowId, ImageHandle>
New Messages:
PreviewMode(id)
WindowSelected(id, window_idx)
WindowThumbnailsReady(id, thumbs)
CycleWindow(id, direction)
windows_of_app(pid) -> Vec<WindowInfo>— enumerates windows for a given PID viaCGWindowListCopyWindowInfocapture_thumbnail(WindowId, size) -> Vec<u8>— captures a downscaled PNG of a specific windowfocus_window(WindowId)— brings a specific window to front using AXUIElement
| File | Change |
|---|---|
app/app.rs |
AppMode::Preview variant; preview_windows: Vec<WindowInfo>; thumbnails: HashMap<WindowId, ImageHandle> |
app/update.rs |
Handle PreviewMode toggle, WindowSelected, thumbnail capture commands, CycleWindow |
app/view.rs |
Render thumbnail strip/grid when in preview mode (stacked overlay below the app row) |
app/subs.rs |
Add hotkey binding for preview mode (Ctrl+P or assignable) |
platform/macos.rs |
New functions: enumerate_windows(pid), capture_window_thumbnail(window_id), focus_window(window_id) |
main.rs |
Possibly larger window dimensions for preview mode |
config.rs |
Maybe enable_preview: bool setting |
| Component | Effort | Risk |
|---|---|---|
| Mode plumbing (enum variant, hotkey, toggle) | 1–2 hrs | Low |
| Window enumeration (CGWindowListCopyWindowInfo → PID filter) | 2–4 hrs | Medium |
| Thumbnail capture (CGImage → PNG → Iced Handle, async) | 4–8 hrs | High — performance, memory, caching strategy |
| Thumbnail UI (rendering stacked overlay in view.rs) | 3–5 hrs | Medium |
| Window-level focusing (AXUIElement to bring specific window front) | 3–6 hrs | High — macOS AX quirks, permission requirements |
| Edge cases (minimized windows, fullscreen apps, windows off-screen) | 2–4 hrs | Medium |
| Total | 15–30 hrs |
-
Async capture: Window screenshots block the GPU. Must be done on a background thread with a completion message back to the Iced loop (similar to how
app_loaderuses channels insubs.rs). -
Thumbnail caching: Don't recapture every frame. Cache thumbnails per window-ID, invalidate on window focus change or a timer.
-
Window info gathering:
CGWindowListCopyWindowInfois fast — call it on mode entry. Returns window IDs, titles, bounds — enough to build the window list. -
Layout approach: When in preview mode and an app is selected, replace or augment the normal grid with a horizontal strip of window thumbnails below the app row. User presses Tab/arrows to cycle, Enter to select.
-
Minimized windows:
CGWindowListCreateImagecan capture minimized windows (pass.optionOnScreenOnly = false), but they render as empty. UseAXUIElementto request a preview instead, or simply note them with a "Minimized" label.
- The app already requests Accessibility API permission (onboarding flow in
accessibility.rs). Window-level focusing requires this. - Screen Recording permission may be needed for
CGWindowListCreateImageon macOS 10.15+ (Catalina+), since capturing another app's window content is treated as a privacy-sensitive operation. This would need to be added to the permissions onboarding.
| Phase | Scope | Est. |
|---|---|---|
| 1 — Window list | Enumerate windows per app, show titles in the switcher UI, focus a specific window (no thumbnails) | 8–12 hrs |
| 2 — Thumbnails | Async thumbnail capture, caching, inline rendering in the UI | 6–10 hrs |
| 3 — Polish | Animations, edge cases, minimized/fullscreen windows, permissions flow | 4–8 hrs |