17#import <Carbon/Carbon.h>
21 "title" :
"VuoGraphicsWindow",
25 "VuoGraphicsWindowDelegate",
31 "VuoList_VuoWindowProperty"
49static void __attribute__((constructor)) VuoGraphicsWindow_init()
59@property(retain) NSMenuItem *recordMenuItem;
60@property(retain) id<NSWindowDelegate> privateDelegate;
62@property(retain) NSScreen *shouldGoFullscreen;
66@property NSSize pendingResize;
78 userData:(
void *)userData
80 NSRect mainScreenFrame = [[NSScreen mainScreen] frame];
82 _styleMaskWhenWindowed = NSWindowStyleMaskTitled | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable;
83 if (
self = [super initWithContentRect:_contentRectWhenWindowed
84 styleMask:_styleMaskWhenWindowed
85 backing:NSBackingStoreBuffered
88 self.colorSpace = NSColorSpace.sRGBColorSpace;
91 self.delegate =
self.privateDelegate;
92 self.releasedWhenClosed = NO;
94 _cursor = VuoCursor_Pointer;
98 self.acceptsMouseMovedEvents = YES;
100 self.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
103#pragma clang diagnostic push
104#pragma clang diagnostic ignored "-Wdeprecated-declarations"
106 [
self registerForDraggedTypes:@[NSFilenamesPboardType]];
107#pragma clang diagnostic pop
109 _userResizedWindow = NO;
110 _programmaticallyResizingWindow = NO;
111 _constrainToScreen = YES;
114 self.title = [NSString stringWithUTF8String:title];
117 _backingScaleFactorCached =
self.backingScaleFactor;
120 initCallback:initCallback
121 updateBackingCallback:updateBackingCallback
122 backingScaleFactor:_backingScaleFactorCached
123 resizeCallback:resizeCallback
124 drawCallback:drawCallback
126 l.contentsScale = _backingScaleFactorCached;
132 self.contentView = v;
135 _contentRectCached = l.frame;
137 _mouseMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown|NSEventMaskLeftMouseUp handler:^(NSEvent *event) {
138 if (event.type == NSEventTypeLeftMouseDown)
139 _leftMouseDown = true;
140 else if (event.type == NSEventTypeLeftMouseUp)
142 _leftMouseDown = false;
143 if (!NSEqualSizes(_pendingResize, NSZeroSize))
145 VDebugLog("The window drag has concluded; performing the deferred resize.");
146 self.contentSize = _pendingResize;
147 _pendingResize = NSZeroSize;
167 NSView *v =
self.contentView;
176- (
BOOL)canBecomeMainWindow
184- (
BOOL)canBecomeKeyWindow
189- (void)updateFullScreenMenu
191 NSMenuItem *fullScreenMenuItem = [[[[[NSApplication sharedApplication] mainMenu] itemWithTag:VuoViewMenuItemTag] submenu] itemWithTag:VuoFullScreenMenuItemTag];
192 fullScreenMenuItem.title =
self.isFullScreen ?
@"Exit Full Screen" :
@"Enter Full Screen";
200- (void)becomeMainWindow
202 [
super becomeMainWindow];
204 if (!_isInMacFullScreenMode)
205 [
self setFullScreenPresentation:self.isFullScreen];
207 NSMenu *fileMenu = [[[NSMenu alloc] initWithTitle:@"File"] autorelease];
208 _recordMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:@selector(toggleRecording) keyEquivalent:@"e"];
209 [_recordMenuItem setKeyEquivalentModifierMask:NSEventModifierFlagCommand|NSEventModifierFlagOption];
210 [fileMenu addItem:_recordMenuItem];
211 NSMenuItem *fileMenuItem = [[NSMenuItem new] autorelease];
212 [fileMenuItem setSubmenu:fileMenu];
214 NSMenu *viewMenu = [[[NSMenu alloc] initWithTitle:@"View"] autorelease];
215 NSMenuItem *fullScreenMenuItem = [[[NSMenuItem alloc] initWithTitle:@"" action:@selector(toggleFullScreen) keyEquivalent:@"f"] autorelease];
217 [viewMenu addItem:fullScreenMenuItem];
218 NSMenuItem *viewMenuItem = [[NSMenuItem new] autorelease];
220 [viewMenuItem setSubmenu:viewMenu];
222 NSMenu *windowMenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
223 NSMenuItem *minimizeMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease];
224 NSMenuItem *zoomMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""] autorelease];
225 NSMenuItem *cycleMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Cycle Through Windows" action:@selector(_cycleWindows:) keyEquivalent:@"`"] autorelease];
226 [windowMenu addItem:minimizeMenuItem];
227 [windowMenu addItem:zoomMenuItem];
228 [windowMenu addItem:cycleMenuItem];
229 NSMenuItem *windowMenuItem = [[NSMenuItem new] autorelease];
230 [windowMenuItem setSubmenu:windowMenu];
232 NSMutableArray *windowMenuItems = [NSMutableArray arrayWithCapacity:3];
233 [windowMenuItems addObject:fileMenuItem];
234 [windowMenuItems addObject:viewMenuItem];
235 [windowMenuItems addObject:windowMenuItem];
238 [
self updateFullScreenMenu];
246- (void)resignMainWindow
248 [
super resignMainWindow];
267- (void)keyDown:(NSEvent *)event
269 if ([event keyCode] == kVK_Escape && [
self isFullScreen])
270 [
super keyDown:event];
279 _recordMenuItem.title =
@"Stop Recording…";
281 _recordMenuItem.title =
@"Start Recording";
299 NSView *v =
self.contentView;
320 NSView *v =
self.contentView;
335 NSView *v =
self.contentView;
340 _updatedWindow = NULL;
341 _showedWindow = NULL;
342 _requestedFrame = NULL;
352 __block NSUInteger styleMask;
354 styleMask =
self.styleMask;
356 return _isInMacFullScreenMode || styleMask == 0;
363- (void)setTitle:(NSString *)title
365 self.titleBackup = title;
377 for (
unsigned int i = 1; i <= propertyCount; ++i)
382 if (property.type == VuoWindowProperty_Title)
384 self.title =
property.title ? [NSString stringWithUTF8String:property.title] :
@"";
392 else if (property.type == VuoWindowProperty_FullScreen)
397 bool wantsFullScreen =
property.fullScreen;
401 if (wantsFullScreen && !requestedScreen)
404 NSInteger requestedDeviceId = [[[requestedScreen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
405 NSInteger currentDeviceId = [[[
self.screen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
406 bool changingScreen = requestedDeviceId != currentDeviceId;
412 [
self setFullScreen:NO onScreen:nil];
415 if (NSScreen.screensHaveSeparateSpaces)
417 self.shouldGoFullscreen = requestedScreen;
420 [
self setFullScreen:YES onScreen:requestedScreen];
423 [
self setFullScreen:property.fullScreen onScreen:requestedScreen];
426 [
self setFrameOrigin:(NSPoint){ NSMidX(requestedScreen.visibleFrame) - self.frame.size.width / 2,
427 NSMidY(requestedScreen.visibleFrame) - self.frame.size.height /2 }];
429 else if (property.type == VuoWindowProperty_Position)
431 NSRect propertyInPoints = NSMakeRect(property.left, property.top, 0, 0);
432 if (property.unit == VuoCoordinateUnit_Pixels)
433 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
435 NSRect mainScreenRect = [[[NSScreen screens] objectAtIndex:0] frame];
437 _contentRectWhenWindowed.origin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - _contentRectWhenWindowed.size.height - propertyInPoints.origin.y);
440 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
441 self.frameOrigin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - contentRect.size.height - propertyInPoints.origin.y);
444 else if (property.type == VuoWindowProperty_Size)
446 NSRect propertyInPoints = NSMakeRect(0, 0, property.width, property.height);
447 if (property.unit == VuoCoordinateUnit_Pixels)
448 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
449 _maintainsPixelSizeWhenBackingChanges = (
property.unit == VuoCoordinateUnit_Pixels);
452 _contentRectWhenWindowed.size = propertyInPoints.size;
455 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
459 contentRect.origin.y += contentRect.size.height - propertyInPoints.size.height;
461 contentRect.size = NSMakeSize(propertyInPoints.size.width, propertyInPoints.size.height);
464 _constrainToScreen = NO;
465 [
self setFrame:[
self frameRectForContentRect:contentRect] display:YES animate:NO];
466 _constrainToScreen = YES;
468 @catch (NSException *e)
472 VUserLog(
"Error: Couldn't change window size to %lldx%lld: %s", property.width, property.height, description);
476 else if (property.type == VuoWindowProperty_AspectRatio)
478 if (property.aspectRatio < 1./10000
479 || property.aspectRatio > 10000)
481 VUserLog(
"Error: Couldn't change window aspect ratio to %g since it's unrealistically narrow.", property.aspectRatio);
485 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
486 [
self setAspectRatioToWidth:contentRect.size.width height:contentRect.size.width/property.aspectRatio];
488 else if (property.type == VuoWindowProperty_AspectRatioReset)
489 [
self unlockAspectRatio];
490 else if (property.type == VuoWindowProperty_Resizable)
492 [[
self standardWindowButton:NSWindowZoomButton] setEnabled:property.resizable];
494 _styleMaskWhenWindowed =
property.resizable ? (_styleMaskWhenWindowed | NSWindowStyleMaskResizable) : (_styleMaskWhenWindowed & ~NSWindowStyleMaskResizable);
496 self.styleMask =
property.resizable ? ([
self styleMask] | NSWindowStyleMaskResizable) : ([self styleMask] & ~NSWindowStyleMaskResizable);
498 else if (property.type == VuoWindowProperty_Cursor)
500 _cursor =
property.cursor;
501 [
self invalidateCursorRectsForView:self.contentView];
503 else if (property.type == VuoWindowProperty_Level)
505 if (property.level == VuoWindowLevel_Background)
506 self.level = CGWindowLevelForKey(kCGBackstopMenuLevelKey);
507 else if (property.level == VuoWindowLevel_Normal)
508 self.level = CGWindowLevelForKey(kCGNormalWindowLevelKey);
509 else if (property.level == VuoWindowLevel_Floating)
510 self.level = CGWindowLevelForKey(kCGFloatingWindowLevelKey);
524 extern OSStatus GetWindowBounds(WindowRef window, WindowRegionCode regionCode, Rect *globalBounds);
526 if (GetWindowBounds(
self.windowRef, kWindowStructureRgn, &qdRect) == noErr)
527 return NSMakeRect(qdRect.left,
528 (
float)CGDisplayPixelsHigh(kCGDirectMainDisplay) - qdRect.bottom,
529 qdRect.right - qdRect.left,
530 qdRect.bottom - qdRect.top);
546- (void)setAspectRatioToWidth:(
unsigned int)pixelsWide height:(
unsigned int)pixelsHigh
551 NSSize newAspect = NSMakeSize(pixelsWide, pixelsHigh);
552 if (NSEqualSizes([
self contentAspectRatio], newAspect))
556 self.contentAspectRatio = newAspect;
571 CGFloat desiredWidth = pixelsWide;
572 CGFloat desiredHeight = pixelsHigh;
573 CGRect windowFrame = [
self liveFrame];
574 CGFloat aspectRatio = (CGFloat)pixelsWide / (CGFloat)pixelsHigh;
577 if (_userResizedWindow)
580 desiredWidth = CGRectGetWidth(windowFrame);
581 desiredHeight = CGRectGetWidth(windowFrame) / aspectRatio;
585 NSRect screenFrame = [[
self screen] visibleFrame];
586 NSRect maxContentRect = [
self contentRectForFrameRect:screenFrame];
587 if (desiredWidth > maxContentRect.size.width || desiredHeight > maxContentRect.size.height)
589 CGFloat maxContentAspectRatio = maxContentRect.size.width / maxContentRect.size.height;
590 if (aspectRatio >= maxContentAspectRatio)
593 desiredWidth = maxContentRect.size.width;
594 desiredHeight = maxContentRect.size.width / aspectRatio;
599 desiredWidth = maxContentRect.size.height * aspectRatio;
600 desiredHeight = maxContentRect.size.height;
604 NSSize newContentSize = NSMakeSize(desiredWidth, desiredHeight);
605 NSSize newWindowSize = [
self frameRectForContentRect:NSMakeRect(0, 0, newContentSize.width, newContentSize.height)].size;
608 CGFloat topY = CGRectGetMinY(windowFrame) + CGRectGetHeight(windowFrame);
609 NSRect newWindowFrame = NSMakeRect(CGRectGetMinX(windowFrame), topY - newWindowSize.height, newWindowSize.width, newWindowSize.height);
611 _programmaticallyResizingWindow = YES;
612 [
self setFrame:newWindowFrame display:YES];
613 _programmaticallyResizingWindow = NO;
632 self.resizeIncrements = NSMakeSize(1,1);
639- (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
641 if (_constrainToScreen)
642 return [
super constrainFrameRect:frameRect toScreen:screen];
650- (void)setFullScreenPresentation:(
bool)enabled
658 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
663 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationDefault;
703- (void)setFullScreen:(
BOOL)wantsFullScreen onScreen:(NSScreen *)screen
705 _programmaticallyResizingWindow =
true;
713 _contentRectWhenWindowed = [
self contentRectForFrameRect:self.frame];
714 _styleMaskWhenWindowed = [
self styleMask];
718 if (screen && ![[
self screen] isEqualTo:screen])
720 NSRect windowFrame =
self.frame;
721 NSRect screenFrame = screen.frame;
722 self.frameOrigin = NSMakePoint(screenFrame.origin.x + screenFrame.size.width/2 - windowFrame.size.width/2,
723 screenFrame.origin.y + screenFrame.size.height/2 - windowFrame.size.height/2);
730 bool useMacFullScreenMode =
true;
734 if (!NSScreen.screensHaveSeparateSpaces)
735 useMacFullScreenMode =
false;
738 if (useMacFullScreenMode)
740 _isInMacFullScreenMode = YES;
745 self.styleMask |= NSWindowStyleMaskResizable;
747 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
749 dispatch_async(dispatch_get_main_queue(), ^{
750 _programmaticallyTransitioningFullScreen =
true;
751 [
self toggleFullScreen:nil];
752 [
self updateFullScreenMenu];
758 [
self setFullScreenPresentation:YES];
760 NSSize car =
self.contentAspectRatio;
762 if (!NSEqualSizes(car, NSMakeSize(0,0)))
763 self.contentAspectRatio = car;
766 [
self setFrame:self.screen.frame display:YES];
768 [
self finishFullScreenTransition];
769 [
self updateFullScreenMenu];
776 if (_isInMacFullScreenMode)
778 _isInMacFullScreenMode = NO;
780 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
782 dispatch_async(dispatch_get_main_queue(), ^{
783 _programmaticallyTransitioningFullScreen =
true;
784 [
self toggleFullScreen:nil];
785 self.styleMask = _styleMaskWhenWindowed;
786 [
self updateFullScreenMenu];
792 [
self setFullScreenPresentation:NO];
794 NSSize car =
self.contentAspectRatio;
795 self.styleMask = _styleMaskWhenWindowed;
796 self.title = _titleBackup;
797 if (!NSEqualSizes(car, NSMakeSize(0,0)))
798 self.contentAspectRatio = car;
800 [
self setFrame:[
self frameRectForContentRect:_contentRectWhenWindowed] display:YES];
802 [
self finishFullScreenTransition];
803 [
self updateFullScreenMenu];
815 _programmaticallyResizingWindow =
false;
817 NSNotification *n = [NSNotification notificationWithName:@"unused" object:nil];
818 [
self.delegate windowDidResize:n];
820 [
self makeFirstResponder:self];
822 if (
self.shouldGoFullscreen)
823 dispatch_async(dispatch_get_main_queue(), ^{
824 NSScreen *s = [
self.shouldGoFullscreen retain];
825 self.shouldGoFullscreen = nil;
826 [
self setFullScreen:true onScreen:s];
836- (void)toggleFullScreen
838 [
self setFullScreen:![
self isFullScreen] onScreen:nil];
845- (void)scheduleResize:(NSSize)newSize
847 VDebugLog(
"Requested to resize to %gx%g points.",newSize.width,newSize.height);
850 VDebugLog(
" The window is being dragged; deferring the resize.");
851 _pendingResize = newSize;
855 VDebugLog(
" The window is not being dragged; resizing now.");
856 self.contentSize = newSize;
865- (void)toggleRecording
870 self.temporaryMovieURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
871 _recorder = [[
VuoWindowRecorder alloc] initWithWindow:self url:_temporaryMovieURL];
876 [
self stopRecording];
882- (void)promptToSaveMovie
884 NSSavePanel *sp = [NSSavePanel savePanel];
885 [sp setTitle:@"Save Movie"];
886 [sp setNameFieldLabel:@"Save Movie To:"];
887 [sp setPrompt:@"Save"];
888#pragma clang diagnostic push
889#pragma clang diagnostic ignored "-Wdeprecated-declarations"
891 [sp setAllowedFileTypes:@[@"mov"]];
892#pragma clang diagnostic pop
895 sp.nameFieldStringValue = [NSString stringWithUTF8String:title];
898 if ([sp runModal] == NSModalResponseCancel)
902 if (![[NSFileManager defaultManager] moveItemAtURL:_temporaryMovieURL toURL:[sp URL] error:&error])
904 if ([error code] == NSFileWriteFileExistsError)
907 if (![[NSFileManager defaultManager] replaceItemAtURL:[sp URL]
908 withItemAtURL:_temporaryMovieURL
915 NSAlert *alert = [NSAlert alertWithError:error];
919 [
self promptToSaveMovie];
924 NSAlert *alert = [NSAlert alertWithError:error];
929 self.temporaryMovieURL = nil;
950 [
self setFullScreen:NO onScreen:nil];
953 [
self promptToSaveMovie];
962- (void)cancelOperation:(
id)sender
964 [
self setFullScreen:NO onScreen:nil];
972 NSView *v =
self.contentView;
984 [NSEvent removeMonitor:_mouseMonitor];
988 [
self makeFirstResponder:nil];