Compare commits

..
2 Commits
Author SHA1 Message Date
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
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
2026-08-12 16:37:53 +08:00
3 changed files with 50 additions and 6 deletions
+7
View File
@@ -48,6 +48,13 @@ struct MenuView: View {
Divider() Divider()
Button("Copy Diagnostics") {
let diag = CaptureController.shared.diagnostics()
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(diag, forType: .string)
}
Button(LoginItem.isEnabled ? "Disable Launch at Login" : "Launch at Login") { Button(LoginItem.isEnabled ? "Disable Launch at Login" : "Launch at Login") {
if !LoginItem.toggle() { if !LoginItem.toggle() {
let alert = NSAlert() let alert = NSAlert()
+41 -4
View File
@@ -10,6 +10,38 @@ final class CaptureController: NSObject {
private var previousApp: NSRunningApplication? private var previousApp: NSRunningApplication?
private var overlayIDs: [Int] = [] private var overlayIDs: [Int] = []
/// Diagnostic snapshot for "Copy Diagnostics" menu item.
func diagnostics() -> String {
var lines: [String] = []
lines.append("Kapture diagnostics")
for (i, s) in NSScreen.screens.enumerated() {
let scale = s.backingScaleFactor
let cgID = (s.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value ?? 0
lines.append("screen[\(i)]: frame=\(s.frame) scale=\(scale) backingSize=\(s.frame.width*scale)x\(s.frame.height*scale) cgID=\(cgID)")
}
if #available(macOS 14.0, *) {
Task {
if let content = try? await SCShareableContent.current {
for (i, d) in content.displays.enumerated() {
lines.append("SCDisplay[\(i)]: frame=\(d.frame) width=\(d.width)x\(d.height) id=\(d.displayID)")
}
} else {
lines.append("SCShareableContent: unavailable (no Screen Recording permission?)")
}
let text = lines.joined(separator: "\n")
let pb = NSPasteboard.general
pb.clearContents()
pb.setString(text, forType: .string)
}
} else {
let text = lines.joined(separator: "\n")
let pb = NSPasteboard.general
pb.clearContents()
pb.setString(text, forType: .string)
}
return lines.joined(separator: "\n")
}
func begin() { func begin() {
guard !isSelecting else { return } guard !isSelecting else { return }
guard PermissionManager.isGranted else { guard PermissionManager.isGranted else {
@@ -141,10 +173,15 @@ final class ScreenCapture {
let excluded = content.windows.filter { excludedSet.contains($0.windowID) } let excluded = content.windows.filter { excludedSet.contains($0.windowID) }
let filter = SCContentFilter(display: display, excludingWindows: excluded) let filter = SCContentFilter(display: display, excludingWindows: excluded)
let config = SCStreamConfiguration() let config = SCStreamConfiguration()
// display.width/height are pixels; request the display's full // SCDisplay.width/height are in POINTS; SCStreamConfiguration
// native resolution so ScreenCaptureKit gives us every pixel. // width/height are output PIXELS. Multiply by the backing scale
config.width = Int(display.width) // of the screen containing the selection so retina displays are
config.height = Int(display.height) // captured at full resolution and 1x displays stay 1x.
let backingScale = NSScreen.screens
.first(where: { $0.frame.contains(NSPoint(x: rect.midX, y: rect.midY)) })?
.backingScaleFactor ?? 1
config.width = Int(CGFloat(display.width) * backingScale)
config.height = Int(CGFloat(display.height) * backingScale)
config.showsCursor = true config.showsCursor = true
guard let image = try? await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config) else { guard let image = try? await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config) else {
+2 -2
View File
@@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST'
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>Icon</string> <string>Icon</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.1.4</string> <string>1.1.6</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>14</string> <string>16</string>
<key>LSUIElement</key> <key>LSUIElement</key>
<true/> <true/>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>