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
This commit is contained in:
@@ -79,16 +79,22 @@ final class CaptureController: NSObject {
|
||||
/// Frozen mode: the screen is snapshotted at hotkey press, so the pointer
|
||||
/// and any hover state (tooltips, open menus) end up in the capture.
|
||||
private(set) var isFrozenMode = false
|
||||
/// Recording mode: the selection flow ends in a recording, not a still.
|
||||
private(set) var isRecordingMode = false
|
||||
private var frozenSnapshots: [ScreenCapture.DisplaySnapshot] = []
|
||||
|
||||
func begin(frozen: Bool = false) {
|
||||
func begin(frozen: Bool = false, recording: Bool = false) {
|
||||
guard !isSelecting else { return }
|
||||
// No new captures while a recording is running (the selection
|
||||
// overlays would end up in the video).
|
||||
guard !RecordingController.shared.state.isRecording else { return }
|
||||
guard PermissionManager.isGranted else {
|
||||
PermissionManager.request()
|
||||
return
|
||||
}
|
||||
isSelecting = true
|
||||
isFrozenMode = frozen
|
||||
isFrozenMode = frozen && !recording
|
||||
isRecordingMode = recording
|
||||
previousApp = NSWorkspace.shared.frontmostApplication
|
||||
// With the event tap driving input we must NOT activate: stealing
|
||||
// focus turns the target app into background mode (dimmed chrome),
|
||||
@@ -98,7 +104,7 @@ final class CaptureController: NSObject {
|
||||
if !usesTap {
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
CaptureController.log("begin: frozen=\(frozen) usesTap=\(usesTap) active=\(NSApp.isActive)")
|
||||
CaptureController.log("begin: frozen=\(isFrozenMode) recording=\(recording) usesTap=\(usesTap) active=\(NSApp.isActive)")
|
||||
|
||||
if frozen {
|
||||
ScreenCapture.shared.snapshotAllDisplays(includeCursor: true) { [weak self] snapshots in
|
||||
@@ -137,9 +143,20 @@ final class CaptureController: NSObject {
|
||||
/// corners come from the window's own alpha).
|
||||
private func finishWindowPick(_ windowID: CGWindowID, _ rect: NSRect, ownedRisk: Bool) {
|
||||
let ids = overlayIDs
|
||||
let recording = isRecordingMode
|
||||
hideOverlays()
|
||||
isSelecting = false
|
||||
restorePreviousApp()
|
||||
if recording {
|
||||
// Owned-popup risk: record the screen region instead of the
|
||||
// window filter, which could composite the parent into the video.
|
||||
RecordingController.shared.begin(
|
||||
region: rect,
|
||||
windowID: ownedRisk ? nil : windowID,
|
||||
excludingWindowIDs: ids
|
||||
)
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async { [self] in
|
||||
if ownedRisk {
|
||||
capture(rect, excludingWindowIDs: ids, roundedCorners: true)
|
||||
@@ -153,6 +170,7 @@ final class CaptureController: NSObject {
|
||||
let ids = overlayIDs
|
||||
let frozen = frozenSnapshots
|
||||
let wasFrozen = isFrozenMode
|
||||
let recording = isRecordingMode
|
||||
hideOverlays()
|
||||
guard rect.width >= 2, rect.height >= 2 else {
|
||||
cancelSelection()
|
||||
@@ -175,6 +193,13 @@ final class CaptureController: NSObject {
|
||||
return
|
||||
}
|
||||
|
||||
if recording {
|
||||
isSelecting = false
|
||||
restorePreviousApp()
|
||||
RecordingController.shared.begin(region: rect, windowID: nil, excludingWindowIDs: ids)
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async { [self] in
|
||||
capture(rect, excludingWindowIDs: ids)
|
||||
}
|
||||
@@ -195,6 +220,7 @@ final class CaptureController: NSObject {
|
||||
marqueeStart = nil
|
||||
frozenSnapshots = []
|
||||
isFrozenMode = false
|
||||
isRecordingMode = false
|
||||
}
|
||||
|
||||
private func restorePreviousApp() {
|
||||
|
||||
Reference in New Issue
Block a user