// // WebWindow.m // NativeHost // // Created by Francisco Tolmasky on 10/18/09. // Copyright 2009 280 North, Inc.. All rights reserved. // #import "WebWindow.h" static NSMutableArray * DisabledWindows; CFMachPortRef tap_port; CGEventRef headTapCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { // It's dangerous to fail in this code: could disable mousedown system-wide. So just try catch it all. @try { if (type == kCGEventLeftMouseDown) { CGPoint location = CGEventGetLocation(event); NSPoint NSLocation = NSMakePoint(location.x, location.y); for (NSWindow * window in [NSApp windows]) { if ([window isKindOfClass:[WebWindow class]] && ![(WebWindow *)window hitTest:NSLocation]) { [window setIgnoresMouseEvents:YES]; [window performSelector:@selector(setIgnoresMouseEvents:) withObject:NO afterDelay:1.0]; [DisabledWindows addObject:window]; } } } else if (type == kCGEventTapDisabledByTimeout) CGEventTapEnable(tap_port, YES); } @catch (NSException * anException) { } return event; } @interface WebWindowContentView : NSView { } @end @implementation WebWindow + (void)enableAllWindows { [DisabledWindows makeObjectsPerformSelector:@selector(stopIgnoringMouseEvents)]; [DisabledWindows removeAllObjects]; } + (void)initialize { if (self != [WebWindow class]) return; DisabledWindows = [[NSMutableArray alloc] init]; //| CGEventMaskBit(kCGEventTapDisabledByTimeout) tap_port = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, CGEventMaskBit(kCGEventLeftMouseDown), headTapCallback, NULL); if (NULL == tap_port) { fprintf(stderr, "Error creating event tap\n"); return; } /* Create a run loop source from the tap port, add it to my run loop * and start executing the loop */ CFRunLoopSourceRef tap_source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap_port, 0); if (NULL == tap_source) { fprintf(stderr, "Error converting port to run loop source\n"); if (tap_port != NULL) CFRelease(tap_port); return; } CFRunLoopAddSource(CFRunLoopGetCurrent(), tap_source, kCFRunLoopCommonModes); } - (id)initWithContentRect:(NSRect)aContentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)aBufferingType defer:(BOOL)shouldDeferCreation { self = [super initWithContentRect:aContentRect styleMask:NSBorderlessWindowMask backing:aBufferingType defer:shouldDeferCreation]; if (self) { id delegate = [NSApp delegate]; shadowView = [[WebWindowContentView alloc] init]; [self setContentView:shadowView]; webView = [[WebView alloc] initWithFrame:NSZeroRect]; [webView setDrawsBackground:NO]; [webView setUIDelegate:delegate]; [webView setFrameLoadDelegate:delegate]; [webView setResourceLoadDelegate:delegate]; [webView setPolicyDelegate:delegate]; [webView setShouldCloseWithWindow:YES]; [shadowView addSubview:webView]; [self setBackgroundColor:[NSColor clearColor]]; [self setOpaque:NO]; [self setIgnoresMouseEvents:NO]; [self setReleasedWhenClosed:YES]; [super setHasShadow:NO]; } return self; } - (void)setFrame:(NSRect)aFrame display:(BOOL)shouldDisplay { [webView setFrame:NSMakeRect(33.0, 33.0, NSWidth(aFrame), NSHeight(aFrame))]; [super setFrame:NSInsetRect(aFrame, -33.0, -33.0) display:YES]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResizeNotification object:self]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidBecomeKeyNotification object:self]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidResignKeyNotification object:self]; [shadowView release]; [webView release]; [super dealloc]; } - (BOOL)canBecomeKeyWindow { // Necessary since this apparently returns NO for borderless windows. return YES; } - (NSView *)webHitTest:(NSPoint)aPoint { NSPoint locationInWebView = [[self contentView] convertPoint:aPoint fromView:nil]; return [(NSView *)[self contentView] hitTest:locationInWebView]; } - (void)sendEvent:(NSEvent *)anEvent { NSInteger type = [anEvent type]; if (type == NSLeftMouseDown) { NSView * view = [self webHitTest:[anEvent locationInWindow]]; leftMouseDownView = view; [view mouseDown:anEvent]; } else if (type == NSLeftMouseDragged) [leftMouseDownView mouseDragged:anEvent]; else if (type == NSLeftMouseUp) [leftMouseDownView mouseUp:anEvent]; else if (type == NSRightMouseDown) { NSView * view = [self webHitTest:[anEvent locationInWindow]]; rightMouseDownView = view; [view rightMouseDown:anEvent]; } else if (type == NSRightMouseDragged) [rightMouseDownView rightMouseDragged:anEvent]; else if (type == NSRightMouseUp) [rightMouseDownView rightMouseUp:anEvent]; else [super sendEvent:anEvent]; // FIXME: other } // Override this to avoid the beep from unhandled events. - (void)keyDown:(NSEvent *)anEvent { } + (WebWindow *)webWindow { return [[WebWindow alloc] initWithContentRect:NSZeroRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES]; } - (WebView *)webView { return webView; } - (void)updateShadow { [super setHasShadow:hasShadow && shadowStyle == CPCustomWindowShadowStyle]; [shadowView setNeedsDisplay:YES]; } - (BOOL)hasShadow { return hasShadow; } - (void)setHasShadow:(BOOL)shouldHaveShadow { if (hasShadow == shouldHaveShadow) return; hasShadow = shouldHaveShadow; [self updateShadow]; } - (CPWindowShadowStyle)shadowStyle { return shadowStyle; } - (void)setShadowStyle:(CPWindowShadowStyle)aStyle { if (shadowStyle == aStyle) return; shadowStyle = aStyle; [self updateShadow]; } - (BOOL)hitTest:(NSPoint)aPoint { NSInteger windowNumber = [[[webView windowScriptObject] valueForKey:@"cpWindowNumber"] intValue]; #if LOGGING WebScriptObject * frame = [[[NSApp delegate] windowScriptObject] evaluateWebScript:[NSString stringWithFormat:@"(objj_msgSend(objj_msgSend(CPApp, \"windowWithWindowNumber:\", %d), \"frame\"))", windowNumber]], * origin = [frame valueForKey:@"origin"], * size = [frame valueForKey:@"size"]; NSLog(@"(%f, %f) in { %f, %f, %f, %f }", aPoint.x, aPoint.y, [[origin valueForKey:@"x"] floatValue], [[origin valueForKey:@"y"] floatValue], [[size valueForKey:@"width"] floatValue], [[size valueForKey:@"height"] floatValue]); #endif return [[[[NSApp delegate] windowScriptObject] evaluateWebScript:[NSString stringWithFormat:@"(CGRectContainsPoint(objj_msgSend(objj_msgSend(CPApp, \"windowWithWindowNumber:\", %d), \"frame\"), CGPointMake(%f, %f)))", windowNumber, aPoint.x, aPoint.y]] boolValue]; } - (void)stopIgnoringMouseEvents { [self setIgnoresMouseEvents:NO]; } - (void)becomeKeyWindow { [super becomeKeyWindow]; [self updateShadow]; } - (void)resignKeyWindow { [super resignKeyWindow]; [self updateShadow]; } @end @interface NSImage (Additions) - (void)drawInRect:(NSRect)aRect slices:(CGFloat [])slices; @end @implementation NSImage (Additions) - (void)drawInRect:(NSRect)aRect slices:(CGFloat [])slices { NSRect imageBounds = NSMakeRect(0.0, 0.0, [self size].width, [self size].height); [self drawInRect:NSMakeRect(0.0, 0.0, slices[0], slices[1]) fromRect:NSMakeRect(0.0, 0.0, slices[0], slices[1]) operation:NSCompositeSourceOver fraction:1.0]; [self drawInRect:NSMakeRect(NSMaxX(aRect) - slices[2], 0.0, slices[2], slices[1]) fromRect:NSMakeRect(NSMaxX(imageBounds) - slices[2], 0.0, slices[2], slices[1]) operation:NSCompositeSourceOver fraction:1.0]; [self drawInRect:NSMakeRect(0.0, NSMaxY(aRect) - slices[3], slices[0], slices[3]) fromRect:NSMakeRect(0.0, NSMaxY(imageBounds) - slices[3], slices[0], slices[3]) operation:NSCompositeSourceOver fraction:1.0]; [self drawInRect:NSMakeRect(NSMaxX(aRect) - slices[2], NSMaxY(aRect) - slices[3], slices[2], slices[3]) fromRect:NSMakeRect(NSMaxX(imageBounds) - slices[2], NSMaxY(imageBounds) - slices[3], slices[2], slices[3]) operation:NSCompositeSourceOver fraction:1.0]; [self drawInRect:NSMakeRect(slices[0], 0.0, NSWidth(aRect) - slices[0] - slices[2], slices[1]) fromRect:NSMakeRect(slices[0], 0.0, 1.0, slices[1]) operation:NSCompositeSourceOver fraction:1.0]; [self drawInRect:NSMakeRect(0.0, slices[1], slices[0], NSHeight(aRect) - slices[1] - slices[3]) fromRect:NSMakeRect(0.0, slices[1], slices[0], 1.0) operation:NSCompositeSourceOver fraction:1.0]; [self drawInRect:NSMakeRect(NSMaxX(aRect) - slices[2], slices[1], slices[2], NSHeight(aRect) - slices[1] - slices[3]) fromRect:NSMakeRect(NSMaxX(imageBounds) - slices[2], slices[1], slices[2], 1.0) operation:NSCompositeSourceOver fraction:1.0]; [self drawInRect:NSMakeRect(slices[0], NSMaxY(aRect) - slices[3], NSWidth(aRect) - slices[0] - slices[2], slices[3]) fromRect:NSMakeRect(slices[0], NSMaxY(imageBounds) - slices[3], 1.0, slices[3]) operation:NSCompositeSourceOver fraction:1.0]; } @end @implementation WebWindowContentView : NSView { } static float OUTlINE_HORIZONTAL_INSET = 33.0; static float OUTlINE_VERTICAL_INSET = 33.0; - (void)drawStandardActiveShadowInRect:(NSRect)aRect { CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; CGRect bounds = CGRectInset(*(CGRect *)&aRect, OUTlINE_HORIZONTAL_INSET - 0.5, OUTlINE_VERTICAL_INSET - 0.5); CGColorRef shadowColor = CGColorCreateGenericGray(0.0, 0.8); CGContextSetShadowWithColor (context, CGSizeMake(0.0, -10.0), 100.0, shadowColor); CGColorRelease(shadowColor); CGColorRef fillColor = CGColorCreateGenericGray(1.0, 1.0); CGColorRef strokeColor = CGColorCreateGenericGray(0.0, 0.2); CGContextSetFillColorWithColor(context, fillColor); CGContextSetStrokeColorWithColor(context, strokeColor); CGColorRelease(fillColor); CGColorRelease(strokeColor); CGContextBeginPath(context); CGContextAddArc(context, CGRectGetMinX(bounds) + 5.0, CGRectGetMaxY(bounds) - 5.0, 5.0, M_PI, M_PI_2, YES); CGContextAddArc(context, CGRectGetMaxX(bounds) - 5.0, CGRectGetMaxY(bounds) - 5.0, 5.0, M_PI_2, 0, YES); CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), CGRectGetMinY(bounds)); CGContextAddLineToPoint(context, CGRectGetMinX(bounds), CGRectGetMinY(bounds)); CGContextClosePath(context); CGContextDrawPath(context, kCGPathFillStroke); } - (void)drawStandardInactiveShadowInRect:(NSRect)aRect { CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; CGRect bounds = CGRectInset(*(CGRect *)&aRect, OUTlINE_HORIZONTAL_INSET - 0.5, OUTlINE_VERTICAL_INSET - 0.5); CGColorRef shadowColor = CGColorCreateGenericGray(0.0, 0.5); CGContextSetShadowWithColor (context, CGSizeMake(0.0, -10.0), 40.0, shadowColor); CGColorRelease(shadowColor); CGColorRef fillColor = CGColorCreateGenericGray(1.0, 1.0); CGColorRef strokeColor = CGColorCreateGenericGray(0.0, 0.2); CGContextSetFillColorWithColor(context, fillColor); CGContextSetStrokeColorWithColor(context, strokeColor); CGColorRelease(fillColor); CGColorRelease(strokeColor); CGContextBeginPath(context); CGContextAddArc(context, CGRectGetMinX(bounds) + 5.0, CGRectGetMaxY(bounds) - 5.0, 5.0, M_PI, M_PI_2, YES); CGContextAddArc(context, CGRectGetMaxX(bounds) - 5.0, CGRectGetMaxY(bounds) - 5.0, 5.0, M_PI_2, 0, YES); CGContextAddLineToPoint(context, CGRectGetMaxX(bounds), CGRectGetMinY(bounds)); CGContextAddLineToPoint(context, CGRectGetMinX(bounds), CGRectGetMinY(bounds)); CGContextClosePath(context); CGContextDrawPath(context, kCGPathFillStroke); } - (void)drawMenuShadowInRect:(NSRect)aRect { CGContextRef context = [[NSGraphicsContext currentContext] graphicsPort]; CGRect bounds = CGRectInset(*(CGRect *)&aRect, OUTlINE_HORIZONTAL_INSET - 0.5, OUTlINE_VERTICAL_INSET - 0.5); CGColorRef shadowColor = CGColorCreateGenericGray(0.0, 0.5); CGContextSetShadowWithColor (context, CGSizeMake(0.0, -10.0), 30.0, shadowColor); CGColorRelease(shadowColor); CGColorRef fillColor = CGColorCreateGenericGray(1.0, 1.0); CGColorRef strokeColor = CGColorCreateGenericGray(0.0, 0.2); CGContextSetFillColorWithColor(context, fillColor); CGContextSetStrokeColorWithColor(context, strokeColor); CGColorRelease(fillColor); CGColorRelease(strokeColor); CGContextBeginPath(context); CGContextAddArc(context, CGRectGetMinX(bounds) + 3.0, CGRectGetMaxY(bounds) - 3.0, 2.0, M_PI, M_PI_2, YES); CGContextAddArc(context, CGRectGetMaxX(bounds) - 3.0, CGRectGetMaxY(bounds) - 3.0, 2.0, M_PI_2, 0, YES); CGContextAddArc(context, CGRectGetMaxX(bounds) - 3.0, CGRectGetMinY(bounds) + 3.0, 2.0, 0, 3 * M_PI_2, YES); CGContextAddArc(context, CGRectGetMinX(bounds) + 3.0, CGRectGetMinY(bounds) + 3.0, 2.0, 3 * M_PI_2, M_PI, YES); CGContextClosePath(context); CGContextDrawPath(context, kCGPathFill);//Stroke); /* CGContextBeginPath(context); CGContextAddArc(context, CGRectGetMinX(bounds) + 3.0, CGRectGetMaxY(bounds) - 3.0, 2.0, M_PI, M_PI_2, YES); CGContextAddArc(context, CGRectGetMaxX(bounds) - 3.0, CGRectGetMaxY(bounds) - 3.0, 2.0, M_PI_2, 0, YES); CGContextAddArc(context, CGRectGetMaxX(bounds) - 3.0, CGRectGetMinY(bounds) + 3.0, 2.0, 0, 3 * M_PI_2, YES); CGContextAddArc(context, CGRectGetMinX(bounds) + 3.0, CGRectGetMinY(bounds) + 3.0, 2.0, 3 * M_PI_2, M_PI, YES); CGContextClosePath(context); CGContextSetFillColorWithColor(context, CGColorGetConstantColor(kCGColorClear)); CGContextSetBlendMode(context, kCGBlendModeCopy); CGContextDrawPath(context, kCGPathFill); */ } - (NSImage *)shadowImageWithSelector:(SEL)aSelector { static NSMutableDictionary * images = nil; if (!images) images = [[NSMutableDictionary alloc] init]; NSString * name = NSStringFromSelector(aSelector); NSImage * image = [images objectForKey:name]; if (!image) { image = [[NSImage alloc] initWithSize:NSMakeSize(100.0, 100.0)]; [image lockFocus]; objc_msgSend(self, aSelector, NSMakeRect(0.0, 0.0, 100.0, 100.0)); [image unlockFocus]; [images setObject:image forKey:name]; [image release]; } return image; } - (void)drawShadowWithSelector:(SEL)aSelector inRect:(NSRect)aRect { CGFloat slices[] = { 40.0, 49.0, 40.0, 49.0 }; if (NSWidth(aRect) > 100.0 || NSHeight(aRect) > 100.0) [[self shadowImageWithSelector:aSelector] drawInRect:aRect slices:slices]; else objc_msgSend(self, aSelector, aRect); } - (void)drawRect:(NSRect)aRect { WebWindow * window = (WebWindow *)[self window]; NSRect bounds = [self bounds]; if (![window hasShadow]) return; if ([window shadowStyle] == CPStandardWindowShadowStyle) if ([window isKeyWindow]) return [self drawShadowWithSelector:@selector(drawStandardActiveShadowInRect:) inRect:bounds]; else return [self drawShadowWithSelector:@selector(drawStandardInactiveShadowInRect:) inRect:bounds]; if ([window shadowStyle] == CPMenuWindowShadowStyle) return [self drawShadowWithSelector:@selector(drawMenuShadowInRect:) inRect:bounds]; } @end