// Copyright (c) 2012 The Chromium Embedded Framework Authors. All rights // reserved. Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file. #import #include #include "cefclient/osrtest_mac.h" #include "include/cef_browser.h" #include "cefclient/cefclient.h" #include "cefclient/client_popup_handler.h" #include "cefclient/osrenderer.h" #include "cefclient/resource_util.h" #include "cefclient/util.h" // The client OpenGL view. @interface ClientOpenGLView : NSOpenGLView { @public NSTrackingArea* tracking_area_; CefRefPtr browser_; ClientOSRenderer* renderer_; NSPoint last_mouse_pos_; NSPoint cur_mouse_pos_; bool rotating_; } - (id)initWithFrame:(NSRect)frame andTransparency:(bool)transparency; - (NSPoint)getClickPointForEvent:(NSEvent*)event; - (void)getKeyInfo:(CefKeyInfo&)info forEvent:(NSEvent*)event; - (int)getModifiersForEvent:(NSEvent*)event; - (BOOL)isKeyUpEvent:(NSEvent*)event; - (BOOL)isKeyPadEvent:(NSEvent*)event; @end namespace { // Handler for off-screen rendering windows. class ClientOSRHandler : public CefClient, public CefLifeSpanHandler, public CefLoadHandler, public CefRequestHandler, public CefDisplayHandler, public CefRenderHandler { public: explicit ClientOSRHandler(ClientOpenGLView* view) : view_(view) { } ~ClientOSRHandler() { } void Disconnect() { view_ = nil; } // CefClient methods virtual CefRefPtr GetLifeSpanHandler() OVERRIDE { return this; } virtual CefRefPtr GetLoadHandler() OVERRIDE { return this; } virtual CefRefPtr GetRequestHandler() OVERRIDE { return this; } virtual CefRefPtr GetDisplayHandler() OVERRIDE { return this; } virtual CefRefPtr GetRenderHandler() OVERRIDE { return this; } // CefLifeSpanHandler methods virtual bool OnBeforePopup(CefRefPtr parentBrowser, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, const CefString& url, CefRefPtr& client, CefBrowserSettings& settings) OVERRIDE { REQUIRE_UI_THREAD(); windowInfo.m_bWindowRenderingDisabled = TRUE; client = new ClientPopupHandler(view_->browser_); return false; } virtual void OnAfterCreated(CefRefPtr browser) OVERRIDE { REQUIRE_UI_THREAD(); // Set the view size to match the window size. const NSRect bounds = [view_ bounds]; browser->SetSize(PET_VIEW, bounds.size.width, bounds.size.height); view_->browser_ = browser; } virtual void OnBeforeClose(CefRefPtr browser) OVERRIDE { if (view_) view_->browser_ = NULL; } // CefLoadHandler methods virtual void OnLoadStart(CefRefPtr browser, CefRefPtr frame) OVERRIDE { REQUIRE_UI_THREAD(); if (!browser->IsPopup() && frame->IsMain()) { // We've just started loading a page SetLoading(true); } } virtual void OnLoadEnd(CefRefPtr browser, CefRefPtr frame, int httpStatusCode) OVERRIDE { REQUIRE_UI_THREAD(); if (!browser->IsPopup() && frame->IsMain()) { // We've just finished loading a page SetLoading(false); } } // CefRequestHandler methods virtual bool OnBeforeResourceLoad(CefRefPtr browser, CefRefPtr request, CefString& redirectUrl, CefRefPtr& resourceStream, CefRefPtr response, int loadFlags) OVERRIDE { REQUIRE_IO_THREAD(); std::string url = request->GetURL(); if (url == "http://tests/osrtest") { // Show the osrtest HTML contents resourceStream = GetBinaryResourceReader("osrtest.html"); response->SetMimeType("text/html"); response->SetStatus(200); } else if (url == "http://tests/transparency") { // Show the osrtest HTML contents resourceStream = GetBinaryResourceReader("transparency.html"); response->SetMimeType("text/html"); response->SetStatus(200); } else if (strstr(url.c_str(), "/logoball.png") != NULL) { // Load the "logoball.png" image resource. resourceStream = GetBinaryResourceReader("logoball.png"); response->SetMimeType("image/png"); response->SetStatus(200); } return false; } // CefDisplayHandler methods virtual void OnNavStateChange(CefRefPtr browser, bool canGoBack, bool canGoForward) OVERRIDE { REQUIRE_UI_THREAD(); } virtual void OnAddressChange(CefRefPtr browser, CefRefPtr frame, const CefString& url) OVERRIDE { REQUIRE_UI_THREAD(); } virtual void OnTitleChange(CefRefPtr browser, const CefString& title) OVERRIDE { REQUIRE_UI_THREAD(); if (!view_) return; // Set the frame window title bar NSWindow* window = [view_ window]; std::string titleStr(title); NSString* str = [NSString stringWithUTF8String:titleStr.c_str()]; [window setTitle:str]; } // CefRenderHandler methods virtual bool GetViewRect(CefRefPtr browser, CefRect& rect) OVERRIDE { REQUIRE_UI_THREAD(); if (!view_) return false; // The simulated screen and view rectangle are the same. This is necessary // for popup menus to be located and sized inside the view. const NSRect bounds = [view_ bounds]; rect.x = rect.y = 0; rect.width = bounds.size.width; rect.height = bounds.size.height; return true; } virtual bool GetScreenRect(CefRefPtr browser, CefRect& rect) OVERRIDE { return GetViewRect(browser, rect); } virtual bool GetScreenPoint(CefRefPtr browser, int viewX, int viewY, int& screenX, int& screenY) OVERRIDE { REQUIRE_UI_THREAD(); if (!view_) return false; // Convert the point from view coordinates to actual screen coordinates. NSRect bounds = [view_ bounds]; NSPoint view_pt = {viewX, bounds.size.height - viewY}; NSPoint window_pt = [view_ convertPoint:view_pt toView:nil]; NSPoint screen_pt = [[view_ window] convertBaseToScreen:window_pt]; screenX = screen_pt.x; screenY = screen_pt.y; return true; } virtual void OnPopupShow(CefRefPtr browser, bool show) OVERRIDE { REQUIRE_UI_THREAD(); if (!view_) return; view_->renderer_->OnPopupShow(browser, show); } virtual void OnPopupSize(CefRefPtr browser, const CefRect& rect) OVERRIDE { REQUIRE_UI_THREAD(); if (!view_) return; view_->renderer_->OnPopupSize(browser, rect); } virtual void OnPaint(CefRefPtr browser, PaintElementType type, const RectList& dirtyRects, const void* buffer) OVERRIDE { REQUIRE_UI_THREAD(); if (!view_) return; NSOpenGLContext* context = [view_ openGLContext]; [context makeCurrentContext]; view_->renderer_->OnPaint(browser, type, dirtyRects, buffer); view_->renderer_->Render(); [context flushBuffer]; } virtual void OnCursorChange(CefRefPtr browser, CefCursorHandle cursor) OVERRIDE { REQUIRE_UI_THREAD(); [cursor set]; } private: void SetLoading(bool isLoading) { } ClientOpenGLView* view_; // Include the default reference counting implementation. IMPLEMENT_REFCOUNTING(ClientOSRPlugin); }; } // namespace @implementation ClientOpenGLView - (id)initWithFrame:(NSRect)frame andTransparency:(bool)transparency { NSOpenGLPixelFormat * pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:(NSOpenGLPixelFormatAttribute[]) { NSOpenGLPFAWindow, NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, 32, 0}]; [pixelFormat autorelease]; self = [super initWithFrame:frame pixelFormat:pixelFormat]; if (self) { renderer_ = new ClientOSRenderer(transparency); rotating_ = false; tracking_area_ = [[NSTrackingArea alloc] initWithRect:frame options:NSTrackingMouseMoved | NSTrackingActiveInActiveApp | NSTrackingInVisibleRect owner:self userInfo:nil]; [self addTrackingArea:tracking_area_]; } return self; } - (void)dealloc { if (browser_) { static_cast(browser_->GetClient().get())->Disconnect(); browser_->CloseBrowser(); browser_ = NULL; } if (renderer_) delete renderer_; [super dealloc]; } - (void)setFrame:(NSRect)frameRect { [super setFrame:frameRect]; int width = frameRect.size.width; int height = frameRect.size.height; if (browser_) browser_->SetSize(PET_VIEW, width, height); } - (void)mouseDown:(NSEvent *)event { if (!browser_) return; NSPoint point = [self getClickPointForEvent:event]; int clickCount = [event clickCount]; browser_->SendMouseClickEvent(point.x, point.y, MBT_LEFT, false, clickCount); } - (void)rightMouseDown:(NSEvent *)event { if (!browser_) return; NSPoint point = [self getClickPointForEvent:event]; if ([event modifierFlags] & NSShiftKeyMask) { // Start rotation effect. last_mouse_pos_ = cur_mouse_pos_ = point; rotating_ = true; return; } int clickCount = [event clickCount]; browser_->SendMouseClickEvent(point.x, point.y, MBT_RIGHT, false, clickCount); } - (void)otherMouseDown:(NSEvent *)event { if (!browser_) return; NSPoint point = [self getClickPointForEvent:event]; int clickCount = [event clickCount]; browser_->SendMouseClickEvent(point.x, point.y, MBT_MIDDLE, false, clickCount); } - (void)mouseUp:(NSEvent *)event { if (!browser_) return; NSPoint point = [self getClickPointForEvent:event]; int clickCount = [event clickCount]; browser_->SendMouseClickEvent(point.x, point.y, MBT_LEFT, true, clickCount); } - (void)rightMouseUp:(NSEvent *)event { if (!browser_) return; if (rotating_) { // End rotation effect. renderer_->SetSpin(0, 0); rotating_ = false; [self setNeedsDisplay:YES]; return; } NSPoint point = [self getClickPointForEvent:event]; int clickCount = [event clickCount]; browser_->SendMouseClickEvent(point.x, point.y, MBT_RIGHT, true, clickCount); } - (void)otherMouseUp:(NSEvent *)event { if (!browser_) return; NSPoint point = [self getClickPointForEvent:event]; int clickCount = [event clickCount]; browser_->SendMouseClickEvent(point.x, point.y, MBT_MIDDLE, true, clickCount); } - (void)mouseMoved:(NSEvent *)event { if (!browser_) return; NSPoint point = [self getClickPointForEvent:event]; if (rotating_) { // Apply rotation effect. cur_mouse_pos_ = point; renderer_->IncrementSpin((cur_mouse_pos_.x - last_mouse_pos_.x), (cur_mouse_pos_.y - last_mouse_pos_.y)); last_mouse_pos_ = cur_mouse_pos_; [self setNeedsDisplay:YES]; return; } browser_->SendMouseMoveEvent(point.x, point.y, false); } - (void)mouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)rightMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)otherMouseDragged:(NSEvent *)event { [self mouseMoved:event]; } - (void)mouseEntered:(NSEvent *)event { [self mouseMoved:event]; } - (void)mouseExited:(NSEvent *)event { if (!browser_) return; NSPoint point = [self getClickPointForEvent:event]; browser_->SendMouseMoveEvent(point.x, point.y, true); } - (void)keyDown:(NSEvent *)event { if (!browser_) return; CefKeyInfo keyInfo; [self getKeyInfo:keyInfo forEvent:event]; int modifiers = [self getModifiersForEvent:event]; browser_->SendKeyEvent(KT_KEYDOWN, keyInfo, modifiers); if ([event modifierFlags] & (NSNumericPadKeyMask | NSFunctionKeyMask)) { // Don't send a Char event for non-char keys like arrows, function keys and // clear. switch (keyInfo.keyCode) { case 81: // = case 75: // / case 67: // * case 78: // - case 69: // + case 76: // Enter case 65: // . case 82: // 0 case 83: // 1 case 84: // 2 case 85: // 3 case 86: // 4 case 87: // 5 case 88: // 6 case 89: // 7 case 91: // 8 case 92: // 9 break; default: return; } } browser_->SendKeyEvent(KT_CHAR, keyInfo, modifiers); } - (void)keyUp:(NSEvent *)event { if (!browser_) return; CefKeyInfo keyInfo; [self getKeyInfo:keyInfo forEvent:event]; int modifiers = [self getModifiersForEvent:event]; browser_->SendKeyEvent(KT_KEYUP, keyInfo, modifiers); } - (void)flagsChanged:(NSEvent *)event { if ([self isKeyUpEvent:event]) [self keyUp:event]; else [self keyDown:event]; } - (void)scrollWheel:(NSEvent *)event { if (!browser_) return; CGEventRef cgEvent = [event CGEvent]; ASSERT(cgEvent); NSPoint point = [self getClickPointForEvent:event]; int deltaX = CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventPointDeltaAxis2); int deltaY = CGEventGetIntegerValueField(cgEvent, kCGScrollWheelEventPointDeltaAxis1); browser_->SendMouseWheelEvent(point.x, point.y, deltaX, deltaY); } - (BOOL)canBecomeKeyView { return (browser_ != NULL); } - (BOOL)acceptsFirstResponder { return (browser_ != NULL); } - (BOOL)becomeFirstResponder { if (browser_) { browser_->SendFocusEvent(true); return [super becomeFirstResponder]; } return NO; } - (BOOL)resignFirstResponder { if (browser_) { browser_->SendFocusEvent(false); return [super resignFirstResponder]; } return NO; } - (void)undo:(id)sender { if (browser_) browser_->GetFocusedFrame()->Undo(); } - (void)redo:(id)sender { if (browser_) browser_->GetFocusedFrame()->Redo(); } - (void)cut:(id)sender { if (browser_) browser_->GetFocusedFrame()->Cut(); } - (void)copy:(id)sender { if (browser_) browser_->GetFocusedFrame()->Copy(); } - (void)paste:(id)sender { if (browser_) browser_->GetFocusedFrame()->Paste(); } - (void)delete:(id)sender { if (browser_) browser_->GetFocusedFrame()->Delete(); } - (void)selectAll:(id)sender { if (browser_) browser_->GetFocusedFrame()->SelectAll(); } - (NSPoint)getClickPointForEvent:(NSEvent*)event { NSPoint windowLocal = [event locationInWindow]; NSPoint contentLocal = [self convertPoint:windowLocal fromView:nil]; NSPoint point; point.x = contentLocal.x; point.y = [self frame].size.height - contentLocal.y; // Flip y. return point; } - (void)getKeyInfo:(CefKeyInfo&)info forEvent:(NSEvent*)event { if ([event type] == NSKeyDown || [event type] == NSKeyUp) { NSString* s = [event characters]; if ([s length] > 0) info.character = [s characterAtIndex:0]; s = [event charactersIgnoringModifiers]; if ([s length] > 0) info.characterNoModifiers = [s characterAtIndex:0]; } info.keyCode = [event keyCode]; } - (int)getModifiersForEvent:(NSEvent*)event { int modifiers = 0; if ([event modifierFlags] & NSControlKeyMask) modifiers |= KEY_CTRL; if ([event modifierFlags] & NSShiftKeyMask) modifiers |= KEY_SHIFT; if ([event modifierFlags] & NSAlternateKeyMask) modifiers |= KEY_ALT; if ([event modifierFlags] & NSCommandKeyMask) modifiers |= KEY_META; if ([self isKeyPadEvent:event]) modifiers |= KEY_KEYPAD; return modifiers; } - (BOOL)isKeyUpEvent:(NSEvent*)event { if ([event type] != NSFlagsChanged) return [event type] == NSKeyUp; // FIXME: This logic fails if the user presses both Shift keys at once, for // example: we treat releasing one of them as keyDown. switch ([event keyCode]) { case 54: // Right Command case 55: // Left Command return ([event modifierFlags] & NSCommandKeyMask) == 0; case 57: // Capslock return ([event modifierFlags] & NSAlphaShiftKeyMask) == 0; case 56: // Left Shift case 60: // Right Shift return ([event modifierFlags] & NSShiftKeyMask) == 0; case 58: // Left Alt case 61: // Right Alt return ([event modifierFlags] & NSAlternateKeyMask) == 0; case 59: // Left Ctrl case 62: // Right Ctrl return ([event modifierFlags] & NSControlKeyMask) == 0; case 63: // Function return ([event modifierFlags] & NSFunctionKeyMask) == 0; } return false; } - (BOOL)isKeyPadEvent:(NSEvent*)event { if ([event modifierFlags] & NSNumericPadKeyMask) return true; switch ([event keyCode]) { case 71: // Clear case 81: // = case 75: // / case 67: // * case 78: // - case 69: // + case 76: // Enter case 65: // . case 82: // 0 case 83: // 1 case 84: // 2 case 85: // 3 case 86: // 4 case 87: // 5 case 88: // 6 case 89: // 7 case 91: // 8 case 92: // 9 return true; } return false; } @end namespace osrtest { void RunTest(bool transparent) { NSRect screen_rect = [[NSScreen mainScreen] visibleFrame]; NSRect window_rect = {{0, screen_rect.size.height}, {700, 700}}; NSWindow* newWnd = [[NSWindow alloc] initWithContentRect:window_rect styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSUnifiedTitleAndToolbarWindowMask) backing:NSBackingStoreBuffered defer:NO]; ASSERT(newWnd); ClientOpenGLView* view = [[ClientOpenGLView alloc] initWithFrame:window_rect andTransparency:transparent]; [view setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)]; [newWnd setContentView:view]; [view release]; CefWindowInfo info; // Initialize the window info as off-screen. info.SetAsOffScreen(view); info.SetTransparentPainting(transparent); CefBrowserSettings settings; AppGetBrowserSettings(settings); // Creat the browser window. CefBrowser::CreateBrowser(info, new ClientOSRHandler(view), "http://tests/osrtest", settings); [newWnd makeKeyAndOrderFront: nil]; } } // namespace osrtest