Commit Graph
62 Commits
Author SHA1 Message Date
TZGyn 6f8f436987 Add a kapture CLI so agents can capture windows and screens
kapture capture --app <name> (or --window-id / --screen) writes a PNG and
prints its path; kapture list enumerates on-screen windows. The CLI is a
thin client: requests go to the running menu-bar app over distributed
notifications plus JSON files in Application Support, so captures use the
app's Screen Recording grant and its window/display logic — no second
permission. The app launches automatically when needed, and the bundled
CLI opens its own app bundle rather than a stale installed copy.

Built as `kapture-cli` (Kapture/kapture collide on the case-insensitive
filesystem), signed nested in the app bundle, and installed on PATH as
`kapture` by the Homebrew cask's binary stanza.

Generated with OpenCode

Assisted-by: Kimi:k3
v1.12.0
2026-10-27 23:38:43 +08:00
TZGyn cac85402a3 Remember the Actual Size view choice
Persist the magnifier state in UserDefaults so every new result window
opens in the same mode and the choice survives relaunches.

Generated with OpenCode

Assisted-by: Kimi:k3
v1.11.3
2026-10-26 21:14:51 +08:00
TZGyn d4b0970ed2 Fix Actual Size: activation, window sizing, and real backing scale
Three bugs made the toggle appear dead: the result window never
activated the LSUIElement app, so the first click was consumed; the
hosting controller's Auto Layout fitting size shrank the window when
the layout branch changed; and SwiftUI's displayScale environment does
not reliably track the window's display on macOS. The window now
activates before ordering front, the hosting view is attached directly
as the contentView (no fitting-size constraints), and the actual-size
scale comes from the window's backingScaleFactor (updated when it moves
between displays).

Generated with OpenCode

Assisted-by: Kimi:k3
v1.11.2
2026-10-26 20:57:12 +08:00
TZGyn ce89d34654 Make crop a Photos-style tool with undo and actual-size view
Crop is now a mode like Draw/Redact: a draggable rect with corner and
edge handles, rule-of-thirds grid, dimmed surroundings, Cancel (Esc) and
Crop Image (Return). Re-entering crop after a crop shows the full
original with the handles on the current crop region, so it can be
dragged back outward; every crop is undoable from the toolbar.

The result window also gains an Actual Size view (Cmd+0): one image
pixel per screen pixel, scrollable, so retina captures stay crisp
instead of being resampled down to the window size.

Generated with OpenCode

Assisted-by: Kimi:k3
v1.11.1
2026-10-26 20:45:48 +08:00
TZGyn 6a2afef1d2 Add screen recording, crop, and a Cap-style result window
Recording: Ctrl+Shift+Cmd+R (or the menu) opens the same selection
flow — a marquee records that region, clicking a window records it
(following moves). Video is H.264 .mov at 60fps via SCStream ->
AVAssetWriter with the cursor included, auto-saved to ~/Movies and
revealed in Finder on stop. A red border and a floating timer/stop bar
show during recording, both excluded from the video. Audio is wired
behind a flag for later.

The result window gains a Crop button (crop to the selection;
annotations translate and stay editable) and is restyled after Cap:
sliding-pill Screenshot/Text switcher, capsule buttons in Cap blue,
circular icon buttons, and the content on a rounded card — light and
dark.

Generated with OpenCode

Assisted-by: Kimi:k3
v1.11.0
2026-10-24 21:48:59 +08:00
TZGyn 04b84e188a Retry OCR on upscaled images when nothing is read
Tiny UI text (around 7pt) reads as nothing at native size — verified
locally. When a pass returns fewer than three characters, OCR retries at
2x and 3x and keeps the fullest result. The smoke test covers a tiny-text
case so the fallback stays working.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.10.2
2026-09-30 11:44:26 +08:00
TZGyn 2e971d5f8d Darken the screen in frozen capture mode
Frozen mode showed the still image at full brightness until a selection
existed, unlike live mode's dark veil. It now draws the same 35% veil over
everything and re-draws the selected area from the snapshot, keeping the
selection bright. The hint text matches the mode.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.10.1
2026-09-25 17:43:59 +08:00
TZGyn e4e20c51d3 Add frozen-screen capture on Ctrl+Shift+Cmd+S
A second hotkey snapshots every display the moment it is pressed and
shows that still image as the overlay, so the pointer and hover states
(tooltips, open menus) are captured. Selection is marquee-only — no
window detection — and the result is cropped from the snapshot rather
than recaptured, keeping the frame exactly as it was.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.10.0
2026-09-25 17:40:12 +08:00
TZGyn 26700c59f4 Make exported stroke thickness match the preview
Strokes were sized in view points but exported in image pixels, so on
retina captures the saved/copied annotation looked several times thinner.
Stroke widths are now converted into image pixels when drawn and scaled
back for the preview, keeping preview and export identical.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.9.2
2026-09-23 17:28:38 +08:00
TZGyn 195c6a9b1f Enable automatic language detection so CJK text is recognized
Vision defaulted to the first listed language and turned Chinese into
Latin garbage (你好世界 became 177WR). automaticallyDetectsLanguage fixes
it; the smoke test now covers Chinese so it cannot regress.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.9.1
2026-09-22 10:54:54 +08:00
TZGyn ecac7885dd Keep the captured app focused during selection
The overlay used to activate Kapture, pushing the target app into
background mode so its chrome (title bar, toolbar, tabs) was captured
dimmed. Input no longer needs activation — the event tap handles it — so
the overlay is a non-activating panel shown without taking focus, and the
app keeps its active appearance in the capture.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.9.0
2026-09-20 14:46:25 +08:00
TZGyn ef44ef7ac6 Let system dialogs take clicks during capture; hint Esc on the overlay
The event tap consumes every click while selecting, which made permission
prompts unclickable when they appeared mid-capture. Clicks now pass
through when a system dialog (SecurityAgent / System Settings) is
frontmost, and the overlay shows a visible Esc hint as an escape hatch.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.8.7
2026-09-12 22:12:48 +08:00
TZGyn 715bb28c1b Fix supersample blur and click-cancellation on micro-drags
The 3x supersample resized the whole capture (blurring content); only the
corner mask is supersampled now, applied through clip(to😷) with the
image drawn once at native scale. The tap's drag branch also clears the
hovered window on sub-4px jitter, cancelling click-to-capture; the
threshold is applied there too.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.8.6
2026-09-09 15:26:19 +08:00
TZGyn 07642ca408 Auto-detect window corner radius; anti-alias the mask
Measure the picked window's real corner radius from the capture itself
(the crop's corner pixels are the desktop background; app content starts
where the color departs), correct for arc anti-aliasing, and fall back to
10pt when ambiguous. The clip mask is supersampled 3x for smooth edges.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.8.5
2026-09-09 15:20:21 +08:00
TZGyn 924a692f2b Round window-capture corners with a clip instead of masking()
The masking() call could silently fail against ScreenCaptureKit's pixel
format, leaving sharp corners. Clip to a rounded path and redraw instead,
verified headlessly for corner clipping and orientation.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.8.4
2026-09-09 15:15:21 +08:00
TZGyn ae054d70b9 Treat any same-app window as popup risk for window capture
Owned popups composite their parent even when the windows do not
overlap, so the intersection test missed DBeaver-style floating dialogs.
Any other window from the same app now routes the pick to the screen
crop.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.8.3
2026-09-09 15:09:09 +08:00
TZGyn 3078713f8f Use screen crop for owned popup windows
desktopIndependentWindow composites owned popups together with their
parent window and scales the group to the popup's size. Detect the case
at pick time — another window from the same app overlapping the picked
one — and capture the screen crop with a rounded mask instead, which
contains only the popup.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.8.2
2026-09-09 15:05:34 +08:00
TZGyn 265571d7e2 Reject window captures that composite extra windows
ScreenCaptureKit's desktopIndependentWindow filter can include owned
child windows (e.g. an app dialog plus its parent), producing a capture
much larger than the picked window. Detect the overflow by comparing the
returned image size against the window's frame and fall back to the
screen crop with rounded mask. Hover changes are logged with the picked
window id and tracked count for diagnosis.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.8.1
2026-09-09 14:55:40 +08:00
TZGyn 7633e81c52 Capture picked windows natively with real rounded corners
Window picks now capture via ScreenCaptureKit's desktopIndependentWindow
filter, so the image carries the app's actual rounded-corner alpha
instead of a fixed 10pt mask that let the desktop show through on apps
with larger radii. Falls back to crop+mask when the window is not in the
shareable content.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.8.0
2026-09-09 14:10:03 +08:00
TZGyn 83b127cf44 Detect maximized windows on secondary displays
A leftover heuristic skipped any window matching its screen's dimensions,
which made window detection silently fail on secondary monitors where the
working window is typically maximized.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.7.4
2026-09-09 14:03:52 +08:00
TZGyn 8332661504 Guard against event-tap teardown crashes; log result-window display
A tap-disabled callback force-unwrapped the tap port, which could crash
the app mid-capture (no result window, dead hotkey). Guard it and log
re-enables. Result window display is now logged with the image size.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.7.3
2026-09-09 12:29:53 +08:00
TZGyn 832a18b06c Exclude the cursor from captures
💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.7.2
2026-09-09 12:26:12 +08:00
TZGyn 6e5796e16d Check Accessibility before starting a capture; log capture geometry
The first capture used to start the overlay and the system permission
prompt at once, making both uninteractive. begin() now requires
Accessibility up front, shows an alert with a direct link to the setting,
and only starts capturing once granted. Captures log their display,
backing scale, and rect mapping for diagnosing multi-display offsets.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.7.1
2026-09-09 12:24:47 +08:00
TZGyn 7e9cc5b269 Drive capture selection through a CGEventTap
Panel event delivery is unreliable on macOS 26 (clicks pass through even
with the app active and the panel key). The selection flow is now driven
by a CGEventTap that sees and consumes mouse events system-wide: hover
highlights windows, clicks capture them (rounded), drags marquee-select,
Esc cancels. Requires Accessibility permission, prompted on first use;
falls back to panel events when unavailable.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.7.0
2026-09-09 12:08:57 +08:00
TZGyn a7cad2fc2f Terminate stale Kapture instances at launch; log topmost windows
A stale copy surviving an update keeps its own hotkey and overlays on
screen, silently eating every click meant for the new instance (matching
the empty session logs). New copies now force-terminate older instances,
and the first window scan logs the topmost on-screen windows so anything
covering the overlays is visible in diagnostics.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.6.5
2026-09-09 11:59:03 +08:00
TZGyn 69e1a7fccb Show overlays only after activation completes
The local probe confirmed activation is asynchronous on macOS 26:
isActive flips on a later runloop tick, so overlays shown in the same
tick as activate() belong to a still-inactive app and misses clicks.
Delay overlay creation by a tick and log the activation state at show
time.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.6.4
2026-09-09 11:51:13 +08:00
TZGyn e22bcb99c4 Make overlay panels activating so clicks are received
The session log showed active=false after every activation attempt on
macOS 26: cooperative activation is denied for LSUIElement apps, and an
inactive app's panels let clicks fall through to the windows below.
Remove .nonactivatingPanel so showing the overlay activates the app
(previous app focus is restored after capture as before).

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.6.3
2026-09-09 11:46:45 +08:00
TZGyn 457e303152 Force app activation on capture; log to /tmp for durable diagnostics
Cooperative NSApp.activate() can silently fail for LSUIElement apps,
leaving the app inactive so overlay clicks pass through to windows below.
Force activation with ignoringOtherApps on all OS versions. Session logs
now also append to /tmp/kapture-debug.log so they survive relaunches, and
the first window-scan result is always logged.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.6.2
2026-09-09 11:00:44 +08:00
TZGyn 345f306edf Add session event logging to capture flow for remote debugging
OverlayView now logs window tracking counts, hover changes, and
mouseDown/Up branch decisions into a session log that Copy Diagnostics
includes along with the app version, so capture failures on machines we
cannot access directly can be diagnosed from a pasted report.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.6.1
2026-09-09 10:52:16 +08:00
TZGyn 45e4962ec3 Fix multi-display window detection and click-to-capture; separate Redact tool
Window frames are now converted to AppKit space once at detection time, so
hover highlighting and picking work on every display. A click is only
treated as a drag after 4pt of movement, so clicking a detected window
captures it instead of passing a stray marquee. Redact is now a standalone
tool (Select/Draw/Redact) with its own toolbar hints instead of a toggle
inside the draw bar.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.6.0
2026-09-09 10:46:32 +08:00
TZGyn 5f137f89bf Fix window hover detection; add redaction tool
Window detection was broken by wrong CG-to-AppKit coordinate conversion
of the cursor and highlight rects — both now go through the same
cgToAppKit mapping. The draw toolbar gains a Pen/Redact toggle: redact
drags cover content with a solid black bar (participates in undo/redo
and export like pen strokes).

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.5.1
2026-09-09 10:39:26 +08:00
TZGyn b998ea210e Document brew trust step for the Gitea tap 2026-09-09 10:36:20 +08:00
TZGyn 86826f7ecc Point Homebrew install at the Gitea-hosted tap
brew tap without a URL resolves to GitHub, where the tap no longer exists;
the full Gitea URL is required.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
2026-09-09 10:32:47 +08:00
TZGyn 969cda6f14 Add window detection capture with rounded corners
Hovering during selection now highlights the window under the cursor;
clicking captures it with a rounded-corner alpha mask matching macOS
window chrome. Dragging still switches to manual marquee selection.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.5.0
2026-09-09 10:29:24 +08:00
TZGyn de642ad692 Fix flipped drawing when copying/saving annotated images
The strokes were stored in top-left image coordinates but drawn into an
AppKit CGContext with a bottom-left origin, so exports came out mirrored
and offset. Flip Y in the context before stroking so annotations land
where they were drawn.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.4.3
2026-08-24 09:08:37 +08:00
TZGyn 8b27263962 Make stroke thickness per-stroke; restore Clear button
Each drawn stroke now stores its own width and color, so changing the
thickness slider no longer retroactively resizes existing strokes. The
Clear button is back in the draw toolbar.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
2026-08-23 14:00:00 +08:00
TZGyn 1961d0dc9c Move draw controls into the draw toolbar
The color picker, undo/redo, and clear eraser now live in the dedicated
draw top bar instead of the main toolbar, keeping the primary actions
(Copy/Save/Get Text) uncluttered. Keyboard shortcuts (⌘Z/⌘⇧Z) remain.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.4.2 v1.4.1
2026-08-23 13:55:00 +08:00
TZGyn 41cb06dcb5 Add draw toolbar with thickness, undo/redo, and color picker
When drawing is enabled a dedicated top bar shows a thickness slider,
color picker, and undo/redo buttons (⌘Z / ⌘⇧Z). Strokes render and export
with the chosen width and color.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.4.0
2026-08-23 13:39:06 +08:00
TZGyn 52e36b5c5a Add drawing color picker to the result window
A compact ColorPicker appears next to the Draw button when drawing is
enabled; strokes render and export in the chosen color (defaults to
system red).

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.3.3
2026-08-23 13:33:32 +08:00
TZGyn a74a86d001 Fix vertical offset in ScreenCaptureKit crops
The captured CGImage has a top-left origin, while the crop rect was
computed in SCDisplay space (bottom-left). Flip Y within the display's
bounds when converting to pixel coordinates so the crop lands exactly
where the user selected.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.3.2
2026-08-23 13:27:41 +08:00
TZGyn f50057d951 Fix screenshot offset after pixel-exact crop change
The new CGImage.cropping path expects a top-left pixel rect; the previous
CGContext redraw had implicitly flipped Y. Keep the existing SCDisplay-space
conversion so the crop matches the selected region.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.3.1
2026-08-23 13:23:37 +08:00
TZGyn 16b876807d Add freehand drawing and fix residual capture pixelation
The screenshot window now supports toggling a pen to draw annotations
that are flattened into copied and saved images. Captures are also
cropped with a pixel-exact CGImage.cropping instead of a CGContext
redraw, removing residual blur on retina displays.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.3.0
2026-08-23 13:21:22 +08:00
TZGyn 6a2c8df180 Modernize the result window UI
Restyles the window with a cleaner layout: icon-based toolbar buttons
(Copy/Save/Get All Text, highlighted OCR Selection action), a bottom status
bar with progress indicator and contextual hints, monospaced text tab, and
a divider separating chrome from content.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.2.1
2026-08-13 09:40:51 +08:00
TZGyn c9447d2a8b Make Kapture screenshot-first with marquee text selection
The result window now works like a screenshot tool: drag on the image to
select a region, then click Get Selection Text to OCR only that area
(full-image OCR stays as Get All Text). Selection is rendered in image
coordinates so it stays accurate at any window size.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.2.0
2026-08-12 21:06:09 +08:00
TZGyn 3fdc2fca95 Fix vertical offset when capturing the secondary display
The first ScreenCaptureKit Y-flip used the wrong reference edges, shifting
cropped selections on displays below the main screen. Convert each selection
per-display as scY = appKitY - NSScreen.maxY + SCDisplay.maxY, which
matches both the main and secondary displays.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.1.8
2026-08-12 16:51:18 +08:00
TZGyn 590be2356f Fix captures on displays below the main screen
ScreenCaptureKit uses a flipped Y axis relative to AppKit NSScreen frames,
so selections on a display below the main screen produced out-of-bounds
crop rects and "Capture failed". Convert the selection into SCDisplay space
using the matching NSScreen's top edge before cropping.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.1.7
2026-08-12 16:47:48 +08:00
TZGyn af32a36a91 Add Copy Diagnostics menu item for multi-display debugging
Prints each NSScreen's frame/backing scale/CG display ID and each
SCDisplay's frame/point size, and copies it to the clipboard, so
secondary-display capture failures can be debugged from the menu bar.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.1.6
2026-08-12 16:44:56 +08:00
TZGyn 1327b61e2b Request capture pixels from the selection screen's backing scale
SCDisplay.width is in points, not pixels: the previous fix sent point
counts to SCStreamConfiguration, producing blurry main-screen captures and
failed secondary-screen captures. Multiply by the backing scale factor of
the screen containing the selection (defaulting to 1x, never forcing 2x).

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.1.5
2026-08-12 16:37:53 +08:00
TZGyn c9a807e8a0 Fix secondary display captures after retina scaling change
The 1.1.2 retina fix overrode the stream width from the display's pixel
dimensions, which made captures on secondary monitors come out at the
wrong scale or offset. Restore requesting the display's native pixel
resolution and keep the crop scale derived from the actual returned image,
plus guard against out-of-bounds crop rects.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.1.4
2026-08-12 16:34:18 +08:00
TZGyn 7bc93c2014 Anti-alias downscaled screenshots in the result window
Full-res retina captures are downscaled to fit the window; nearest-neighbor
made them look chunky. Use high-quality interpolation when shrinking, keep
pixel-crisp rendering only when upscaling small selections.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
v1.1.3
2026-08-12 16:25:23 +08:00