fix: can not screen shot in extended display on macos

This commit is contained in:
2026-06-11 17:09:58 +08:00
parent ab4ba8c417
commit 952f1497c8
+64 -4
View File
@@ -428,6 +428,43 @@ static id nativeOverlayKeyMonitor = nil;
MAX(0, MIN(p.y - _selection.origin.y, _selection.size.height))); MAX(0, MIN(p.y - _selection.origin.y, _selection.size.height)));
} }
// Translate a selection rectangle expressed in view-local coordinates into
// the global top-left screen coordinate system used by
// `/usr/sbin/screencapture -R`.
//
// Why this is needed:
// - The overlay window is created with the host screen's AppKit frame and
// its flipped content view yields top-left local coordinates relative
// to that screen only.
// - macOS combines all attached displays into a single virtual coordinate
// space whose origin is the top-left corner of the primary display
// (Y grows downward). `screencapture -R` interprets its arguments in
// that space.
// - Without this translation, a selection on a secondary display is sent
// as if it were on the primary, so screencapture crops the wrong area.
//
// Translation steps:
// 1. globalX = screenFrame.origin.x + sel.origin.x
// (NSScreen frame origin.x is already in primary-anchored global X.)
// 2. globalY = (primaryHeight - (screenFrame.origin.y + screenFrame.size.height)) + sel.origin.y
// Converts the screen's AppKit bottom-left origin to a top-left global
// origin, then adds the already-top-left local Y inside the view.
- (NSRect)globalRectForSelection:(NSRect)sel {
NSScreen *screen = [nativeOverlayWindow screen];
if (screen == nil) {
screen = [NSScreen mainScreen];
}
if (screen == nil) {
return sel;
}
NSRect screenFrame = [screen frame];
NSScreen *primary = [[NSScreen screens] firstObject];
CGFloat primaryHeight = primary != nil ? [primary frame].size.height : screenFrame.size.height;
CGFloat globalX = screenFrame.origin.x + sel.origin.x;
CGFloat globalY = (primaryHeight - (screenFrame.origin.y + screenFrame.size.height)) + sel.origin.y;
return NSMakeRect(globalX, globalY, sel.size.width, sel.size.height);
}
- (void)mouseDown:(NSEvent *)event { - (void)mouseDown:(NSEvent *)event {
NSPoint p = [self convertPoint:[event locationInWindow] fromView:nil]; NSPoint p = [self convertPoint:[event locationInWindow] fromView:nil];
if ([event clickCount] == 2 && _hasSelection && NSPointInRect(p, _selection)) { if ([event clickCount] == 2 && _hasSelection && NSPointInRect(p, _selection)) {
@@ -701,7 +738,7 @@ static id nativeOverlayKeyMonitor = nil;
if (!_hasSelection) { if (!_hasSelection) {
return; return;
} }
NSRect r = _selection; NSRect r = [self globalRectForSelection:_selection];
[self closeOverlayWindow]; [self closeOverlayWindow];
NSString *json = [self annotationsJSON]; NSString *json = [self annotationsJSON];
nativeOverlayConfirm((int)llround(r.origin.x), (int)llround(r.origin.y), (int)llround(r.size.width), (int)llround(r.size.height), [json UTF8String]); nativeOverlayConfirm((int)llround(r.origin.x), (int)llround(r.origin.y), (int)llround(r.size.width), (int)llround(r.size.height), [json UTF8String]);
@@ -711,7 +748,7 @@ static id nativeOverlayKeyMonitor = nil;
if (!_hasSelection) { if (!_hasSelection) {
return; return;
} }
NSRect r = _selection; NSRect r = [self globalRectForSelection:_selection];
[self closeOverlayWindow]; [self closeOverlayWindow];
NSString *json = [self annotationsJSON]; NSString *json = [self annotationsJSON];
nativeOverlayCopy((int)llround(r.origin.x), (int)llround(r.origin.y), (int)llround(r.size.width), (int)llround(r.size.height), [json UTF8String]); nativeOverlayCopy((int)llround(r.origin.x), (int)llround(r.origin.y), (int)llround(r.size.width), (int)llround(r.size.height), [json UTF8String]);
@@ -721,7 +758,7 @@ static id nativeOverlayKeyMonitor = nil;
if (!_hasSelection) { if (!_hasSelection) {
return; return;
} }
NSRect r = _selection; NSRect r = [self globalRectForSelection:_selection];
NSString *json = [self annotationsJSON]; NSString *json = [self annotationsJSON];
[self closeOverlayWindow]; [self closeOverlayWindow];
@@ -761,9 +798,32 @@ static id nativeOverlayKeyMonitor = nil;
} }
@end @end
// screenContainingCursor returns the NSScreen the cursor is currently on,
// falling back to the main screen and then the first attached screen.
//
// Rationale: when an external display is attached, users expect the capture
// overlay to appear on whichever display they were just working on.
// `[NSEvent mouseLocation]` reports the cursor in the same global Cocoa
// (bottom-left-origin) screen space used by `NSScreen.frame`, so a simple
// `NSPointInRect` test against each screen's frame yields the right one.
// Iteration over `[NSScreen screens]` is cheap (typically <=4 displays).
static NSScreen *snipScreenContainingCursor(void) {
NSPoint cursor = [NSEvent mouseLocation];
for (NSScreen *candidate in [NSScreen screens]) {
if (NSPointInRect(cursor, [candidate frame])) {
return candidate;
}
}
NSScreen *fallback = [NSScreen mainScreen];
if (fallback != nil) {
return fallback;
}
return [[NSScreen screens] firstObject];
}
static void snipShowNativeOverlay(int width, int height) { static void snipShowNativeOverlay(int width, int height) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
NSScreen *screen = [NSScreen mainScreen]; NSScreen *screen = snipScreenContainingCursor();
if (screen == nil) { if (screen == nil) {
nativeOverlayCancel(); nativeOverlayCancel();
return; return;