18 #import <Carbon/Carbon.h>
22 "title" :
"VuoGraphicsWindow",
26 "VuoGraphicsWindowDelegate",
32 "VuoList_VuoWindowProperty"
50 static void __attribute__((constructor)) VuoGraphicsWindow_init()
60 @property(retain) NSMenuItem *recordMenuItem;
61 @property(retain) id<NSWindowDelegate> privateDelegate;
63 @property(retain) NSScreen *shouldGoFullscreen;
67 @property NSSize pendingResize;
79 userData:(
void *)userData
81 NSRect mainScreenFrame = [[NSScreen mainScreen] frame];
83 _styleMaskWhenWindowed = NSTitledWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask;
84 if (
self = [super initWithContentRect:_contentRectWhenWindowed
85 styleMask:_styleMaskWhenWindowed
86 backing:NSBackingStoreBuffered
89 self.colorSpace = NSColorSpace.sRGBColorSpace;
92 self.delegate =
self.privateDelegate;
93 self.releasedWhenClosed = NO;
95 _cursor = VuoCursor_Pointer;
99 self.acceptsMouseMovedEvents = YES;
101 self.collectionBehavior = NSWindowCollectionBehaviorFullScreenPrimary;
104 [
self registerForDraggedTypes:@[NSFilenamesPboardType]];
106 _userResizedWindow = NO;
107 _programmaticallyResizingWindow = NO;
108 _constrainToScreen = YES;
111 self.title = [NSString stringWithUTF8String:title];
114 _backingScaleFactorCached =
self.backingScaleFactor;
117 initCallback:initCallback
118 updateBackingCallback:updateBackingCallback
119 backingScaleFactor:_backingScaleFactorCached
120 resizeCallback:resizeCallback
121 drawCallback:drawCallback
123 l.contentsScale = _backingScaleFactorCached;
129 self.contentView = v;
132 _contentRectCached = l.frame;
134 _mouseMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSLeftMouseUpMask handler:^(NSEvent *event) {
135 if (event.type == NSLeftMouseDown)
136 _leftMouseDown = true;
137 else if (event.type == NSLeftMouseUp)
139 _leftMouseDown = false;
140 if (!NSEqualSizes(_pendingResize, NSZeroSize))
142 VDebugLog("The window drag has concluded; performing the deferred resize.");
143 self.contentSize = _pendingResize;
144 _pendingResize = NSZeroSize;
164 NSView *v =
self.contentView;
173 - (BOOL)canBecomeMainWindow
181 - (BOOL)canBecomeKeyWindow
186 - (void)updateFullScreenMenu
188 NSMenuItem *fullScreenMenuItem = [[[[[NSApplication sharedApplication] mainMenu] itemWithTag:VuoViewMenuItemTag] submenu] itemWithTag:VuoFullScreenMenuItemTag];
189 fullScreenMenuItem.title =
self.isFullScreen ?
@"Exit Full Screen" :
@"Enter Full Screen";
197 - (void)becomeMainWindow
199 [
super becomeMainWindow];
201 if (!_isInMacFullScreenMode)
202 [
self setFullScreenPresentation:self.isFullScreen];
204 NSMenu *fileMenu = [[[NSMenu alloc] initWithTitle:@"File"] autorelease];
205 _recordMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:@selector(toggleRecording) keyEquivalent:@"e"];
206 [_recordMenuItem setKeyEquivalentModifierMask:NSCommandKeyMask|NSAlternateKeyMask];
207 [fileMenu addItem:_recordMenuItem];
208 NSMenuItem *fileMenuItem = [[NSMenuItem new] autorelease];
209 [fileMenuItem setSubmenu:fileMenu];
211 NSMenu *viewMenu = [[[NSMenu alloc] initWithTitle:@"View"] autorelease];
212 NSMenuItem *fullScreenMenuItem = [[[NSMenuItem alloc] initWithTitle:@"" action:@selector(toggleFullScreen) keyEquivalent:@"f"] autorelease];
214 [viewMenu addItem:fullScreenMenuItem];
215 NSMenuItem *viewMenuItem = [[NSMenuItem new] autorelease];
217 [viewMenuItem setSubmenu:viewMenu];
219 NSMenu *windowMenu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease];
220 NSMenuItem *minimizeMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease];
221 NSMenuItem *zoomMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom" action:@selector(performZoom:) keyEquivalent:@""] autorelease];
222 NSMenuItem *cycleMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Cycle Through Windows" action:@selector(_cycleWindows:) keyEquivalent:@"`"] autorelease];
223 [windowMenu addItem:minimizeMenuItem];
224 [windowMenu addItem:zoomMenuItem];
225 [windowMenu addItem:cycleMenuItem];
226 NSMenuItem *windowMenuItem = [[NSMenuItem new] autorelease];
227 [windowMenuItem setSubmenu:windowMenu];
229 NSMutableArray *windowMenuItems = [NSMutableArray arrayWithCapacity:3];
230 [windowMenuItems addObject:fileMenuItem];
231 [windowMenuItems addObject:viewMenuItem];
232 [windowMenuItems addObject:windowMenuItem];
235 [
self updateFullScreenMenu];
243 - (void)resignMainWindow
245 [
super resignMainWindow];
264 - (void)keyDown:(NSEvent *)event
266 if ([event keyCode] == kVK_Escape && [
self isFullScreen])
267 [
super keyDown:event];
276 _recordMenuItem.title =
@"Stop Recording…";
278 _recordMenuItem.title =
@"Start Recording";
296 NSView *v =
self.contentView;
317 NSView *v =
self.contentView;
332 NSView *v =
self.contentView;
337 _updatedWindow = NULL;
338 _showedWindow = NULL;
339 _requestedFrame = NULL;
349 __block NSUInteger styleMask;
351 styleMask =
self.styleMask;
353 return _isInMacFullScreenMode || styleMask == 0;
360 - (void)setTitle:(NSString *)title
362 self.titleBackup = title;
374 for (
unsigned int i = 1; i <= propertyCount; ++i)
379 if (property.type == VuoWindowProperty_Title)
381 self.title =
property.title ? [NSString stringWithUTF8String:property.title] :
@"";
389 else if (property.type == VuoWindowProperty_FullScreen)
394 bool wantsFullScreen =
property.fullScreen;
398 if (wantsFullScreen && !requestedScreen)
401 NSInteger requestedDeviceId = [[[requestedScreen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
402 NSInteger currentDeviceId = [[[
self.screen deviceDescription] objectForKey:@"NSScreenNumber"] integerValue];
403 bool changingScreen = requestedDeviceId != currentDeviceId;
409 [
self setFullScreen:NO onScreen:nil];
412 if (NSScreen.screensHaveSeparateSpaces)
414 self.shouldGoFullscreen = requestedScreen;
417 [
self setFullScreen:YES onScreen:requestedScreen];
420 [
self setFullScreen:property.fullScreen onScreen:requestedScreen];
423 [
self setFrameOrigin:(NSPoint){ NSMidX(requestedScreen.visibleFrame) - self.frame.size.width / 2,
424 NSMidY(requestedScreen.visibleFrame) - self.frame.size.height /2 }];
426 else if (property.type == VuoWindowProperty_Position)
428 NSRect propertyInPoints = NSMakeRect(property.left, property.top, 0, 0);
429 if (property.unit == VuoCoordinateUnit_Pixels)
430 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
432 NSRect mainScreenRect = [[[NSScreen screens] objectAtIndex:0] frame];
434 _contentRectWhenWindowed.origin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - _contentRectWhenWindowed.size.height - propertyInPoints.origin.y);
437 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
438 self.frameOrigin = NSMakePoint(propertyInPoints.origin.x, mainScreenRect.size.height - contentRect.size.height - propertyInPoints.origin.y);
441 else if (property.type == VuoWindowProperty_Size)
443 NSRect propertyInPoints = NSMakeRect(0, 0, property.width, property.height);
444 if (property.unit == VuoCoordinateUnit_Pixels)
445 propertyInPoints = [
self.contentView convertRectFromBacking:propertyInPoints];
446 _maintainsPixelSizeWhenBackingChanges = (
property.unit == VuoCoordinateUnit_Pixels);
449 _contentRectWhenWindowed.size = propertyInPoints.size;
452 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
456 contentRect.origin.y += contentRect.size.height - propertyInPoints.size.height;
458 contentRect.size = NSMakeSize(propertyInPoints.size.width, propertyInPoints.size.height);
461 _constrainToScreen = NO;
462 [
self setFrame:[
self frameRectForContentRect:contentRect] display:YES animate:NO];
463 _constrainToScreen = YES;
465 @catch (NSException *e)
469 VUserLog(
"Error: Couldn't change window size to %lldx%lld: %s", property.width, property.height, description);
473 else if (property.type == VuoWindowProperty_AspectRatio)
475 if (property.aspectRatio < 1./10000
476 || property.aspectRatio > 10000)
478 VUserLog(
"Error: Couldn't change window aspect ratio to %g since it's unrealistically narrow.", property.aspectRatio);
482 NSRect contentRect = [
self contentRectForFrameRect:[
self frame]];
483 [
self setAspectRatioToWidth:contentRect.size.width height:contentRect.size.width/property.aspectRatio];
485 else if (property.type == VuoWindowProperty_AspectRatioReset)
486 [
self unlockAspectRatio];
487 else if (property.type == VuoWindowProperty_Resizable)
489 [[
self standardWindowButton:NSWindowZoomButton] setEnabled:property.resizable];
491 _styleMaskWhenWindowed =
property.resizable ? (_styleMaskWhenWindowed | NSResizableWindowMask) : (_styleMaskWhenWindowed & ~NSResizableWindowMask);
493 self.styleMask =
property.resizable ? ([
self styleMask] | NSResizableWindowMask) : ([
self styleMask] & ~NSResizableWindowMask);
495 else if (property.type == VuoWindowProperty_Cursor)
497 _cursor =
property.cursor;
498 [
self invalidateCursorRectsForView:self.contentView];
500 else if (property.type == VuoWindowProperty_Level)
502 if (property.level == VuoWindowLevel_Background)
503 self.level = CGWindowLevelForKey(kCGBackstopMenuLevelKey);
504 else if (property.level == VuoWindowLevel_Normal)
505 self.level = CGWindowLevelForKey(kCGNormalWindowLevelKey);
506 else if (property.level == VuoWindowLevel_Floating)
507 self.level = CGWindowLevelForKey(kCGFloatingWindowLevelKey);
521 extern OSStatus GetWindowBounds(WindowRef window, WindowRegionCode regionCode, Rect *globalBounds);
523 GetWindowBounds([
self windowRef], kWindowStructureRgn, &qdRect);
525 return NSMakeRect(qdRect.left,
526 (
float)CGDisplayPixelsHigh(kCGDirectMainDisplay) - qdRect.bottom,
527 qdRect.right - qdRect.left,
528 qdRect.bottom - qdRect.top);
541 - (void)setAspectRatioToWidth:(
unsigned int)pixelsWide height:(
unsigned int)pixelsHigh
546 NSSize newAspect = NSMakeSize(pixelsWide, pixelsHigh);
547 if (NSEqualSizes([
self contentAspectRatio], newAspect))
551 self.contentAspectRatio = newAspect;
566 CGFloat desiredWidth = pixelsWide;
567 CGFloat desiredHeight = pixelsHigh;
568 CGRect windowFrame = [
self liveFrame];
569 CGFloat aspectRatio = (CGFloat)pixelsWide / (CGFloat)pixelsHigh;
572 if (_userResizedWindow)
575 desiredWidth = CGRectGetWidth(windowFrame);
576 desiredHeight = CGRectGetWidth(windowFrame) / aspectRatio;
580 NSRect screenFrame = [[
self screen] visibleFrame];
581 NSRect maxContentRect = [
self contentRectForFrameRect:screenFrame];
582 if (desiredWidth > maxContentRect.size.width || desiredHeight > maxContentRect.size.height)
584 CGFloat maxContentAspectRatio = maxContentRect.size.width / maxContentRect.size.height;
585 if (aspectRatio >= maxContentAspectRatio)
588 desiredWidth = maxContentRect.size.width;
589 desiredHeight = maxContentRect.size.width / aspectRatio;
594 desiredWidth = maxContentRect.size.height * aspectRatio;
595 desiredHeight = maxContentRect.size.height;
599 NSSize newContentSize = NSMakeSize(desiredWidth, desiredHeight);
600 NSSize newWindowSize = [
self frameRectForContentRect:NSMakeRect(0, 0, newContentSize.width, newContentSize.height)].size;
603 CGFloat topY = CGRectGetMinY(windowFrame) + CGRectGetHeight(windowFrame);
604 NSRect newWindowFrame = NSMakeRect(CGRectGetMinX(windowFrame), topY - newWindowSize.height, newWindowSize.width, newWindowSize.height);
606 _programmaticallyResizingWindow = YES;
607 [
self setFrame:newWindowFrame display:YES];
608 _programmaticallyResizingWindow = NO;
627 self.resizeIncrements = NSMakeSize(1,1);
634 - (NSRect)constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen *)screen
636 if (_constrainToScreen)
637 return [
super constrainFrameRect:frameRect toScreen:screen];
645 - (void)setFullScreenPresentation:(
bool)enabled
653 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
658 ((NSApplication *)NSApp).presentationOptions = NSApplicationPresentationDefault;
698 - (void)setFullScreen:(BOOL)wantsFullScreen onScreen:(NSScreen *)screen
700 _programmaticallyResizingWindow =
true;
708 _contentRectWhenWindowed = [
self contentRectForFrameRect:self.frame];
709 _styleMaskWhenWindowed = [
self styleMask];
713 if (screen && ![[
self screen] isEqualTo:screen])
715 NSRect windowFrame =
self.frame;
716 NSRect screenFrame = screen.frame;
717 self.frameOrigin = NSMakePoint(screenFrame.origin.x + screenFrame.size.width/2 - windowFrame.size.width/2,
718 screenFrame.origin.y + screenFrame.size.height/2 - windowFrame.size.height/2);
725 bool useMacFullScreenMode =
true;
729 if (!NSScreen.screensHaveSeparateSpaces)
730 useMacFullScreenMode =
false;
733 if (useMacFullScreenMode)
735 _isInMacFullScreenMode = YES;
740 self.styleMask |= NSResizableWindowMask;
742 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
744 dispatch_async(dispatch_get_main_queue(), ^{
745 _programmaticallyTransitioningFullScreen =
true;
746 [
self toggleFullScreen:nil];
747 [
self updateFullScreenMenu];
753 [
self setFullScreenPresentation:YES];
755 NSSize car =
self.contentAspectRatio;
757 if (!NSEqualSizes(car, NSMakeSize(0,0)))
758 self.contentAspectRatio = car;
761 [
self setFrame:self.screen.frame display:YES];
763 [
self finishFullScreenTransition];
764 [
self updateFullScreenMenu];
771 if (_isInMacFullScreenMode)
773 _isInMacFullScreenMode = NO;
775 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
777 dispatch_async(dispatch_get_main_queue(), ^{
778 _programmaticallyTransitioningFullScreen =
true;
779 [
self toggleFullScreen:nil];
780 self.styleMask = _styleMaskWhenWindowed;
781 [
self updateFullScreenMenu];
787 [
self setFullScreenPresentation:NO];
789 NSSize car =
self.contentAspectRatio;
790 self.styleMask = _styleMaskWhenWindowed;
791 self.title = _titleBackup;
792 if (!NSEqualSizes(car, NSMakeSize(0,0)))
793 self.contentAspectRatio = car;
795 [
self setFrame:[
self frameRectForContentRect:_contentRectWhenWindowed] display:YES];
797 [
self finishFullScreenTransition];
798 [
self updateFullScreenMenu];
810 _programmaticallyResizingWindow =
false;
812 [
self.delegate windowDidResize:nil];
814 [
self makeFirstResponder:self];
816 if (
self.shouldGoFullscreen)
817 dispatch_async(dispatch_get_main_queue(), ^{
818 NSScreen *s = [
self.shouldGoFullscreen retain];
819 self.shouldGoFullscreen = nil;
820 [
self setFullScreen:true onScreen:s];
830 - (void)toggleFullScreen
832 [
self setFullScreen:![
self isFullScreen] onScreen:nil];
839 - (void)scheduleResize:(NSSize)newSize
841 VDebugLog(
"Requested to resize to %gx%g points.",newSize.width,newSize.height);
844 VDebugLog(
" The window is being dragged; deferring the resize.");
845 _pendingResize = newSize;
849 VDebugLog(
" The window is not being dragged; resizing now.");
850 self.contentSize = newSize;
859 - (void)toggleRecording
864 self.temporaryMovieURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]];
865 _recorder = [[
VuoWindowRecorder alloc] initWithWindow:self url:_temporaryMovieURL];
870 [
self stopRecording];
876 - (void)promptToSaveMovie
878 NSSavePanel *sp = [NSSavePanel savePanel];
879 [sp setTitle:@"Save Movie"];
880 [sp setNameFieldLabel:@"Save Movie To:"];
881 [sp setPrompt:@"Save"];
882 [sp setAllowedFileTypes:@[@"mov"]];
885 sp.nameFieldStringValue = [NSString stringWithUTF8String:title];
888 if ([sp runModal] == NSFileHandlingPanelCancelButton)
892 if (![[NSFileManager defaultManager] moveItemAtURL:_temporaryMovieURL toURL:[sp URL] error:&error])
894 if ([error code] == NSFileWriteFileExistsError)
897 if (![[NSFileManager defaultManager] replaceItemAtURL:[sp URL]
898 withItemAtURL:_temporaryMovieURL
905 NSAlert *alert = [NSAlert alertWithError:error];
909 [
self promptToSaveMovie];
914 NSAlert *alert = [NSAlert alertWithError:error];
919 self.temporaryMovieURL = nil;
929 - (void)stopRecording
940 [
self setFullScreen:NO onScreen:nil];
942 [
self promptToSaveMovie];
950 - (void)cancelOperation:(
id)sender
952 [
self setFullScreen:NO onScreen:nil];
960 NSView *v =
self.contentView;
972 [NSEvent removeMonitor:_mouseMonitor];
976 [
self makeFirstResponder:nil];