494 lines
14 KiB
C++
494 lines
14 KiB
C++
// Copyright (c) 2013 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.
|
|
|
|
#include "cefclient/cefclient_osr_widget_gtk.h"
|
|
|
|
// This value is defined in build/common.gypi and must be undefined here
|
|
// in order for gtkglext to compile.
|
|
#undef GTK_DISABLE_SINGLE_INCLUDES
|
|
|
|
#include <gdk/gdk.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <glib-object.h>
|
|
#include <gtk/gtk.h>
|
|
#include <gtk/gtkgl.h>
|
|
#include <GL/gl.h>
|
|
|
|
#include "include/cef_runnable.h"
|
|
#include "cefclient/util.h"
|
|
|
|
namespace {
|
|
|
|
gint glarea_size_allocation(GtkWidget* widget,
|
|
GtkAllocation* allocation,
|
|
OSRWindow* window) {
|
|
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
|
|
host->WasResized();
|
|
return TRUE;
|
|
}
|
|
|
|
int get_cef_state_modifiers(guint state) {
|
|
int modifiers = 0;
|
|
if (state & GDK_SHIFT_MASK)
|
|
modifiers |= EVENTFLAG_SHIFT_DOWN;
|
|
if (state & GDK_LOCK_MASK)
|
|
modifiers |= EVENTFLAG_CAPS_LOCK_ON;
|
|
if (state & GDK_CONTROL_MASK)
|
|
modifiers |= EVENTFLAG_CONTROL_DOWN;
|
|
if (state & GDK_MOD1_MASK)
|
|
modifiers |= EVENTFLAG_ALT_DOWN;
|
|
if (state & GDK_BUTTON1_MASK)
|
|
modifiers |= EVENTFLAG_LEFT_MOUSE_BUTTON;
|
|
if (state & GDK_BUTTON2_MASK)
|
|
modifiers |= EVENTFLAG_MIDDLE_MOUSE_BUTTON;
|
|
if (state & GDK_BUTTON3_MASK)
|
|
modifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON;
|
|
return modifiers;
|
|
}
|
|
|
|
gint glarea_click_event(GtkWidget* widget,
|
|
GdkEventButton* event,
|
|
OSRWindow* window) {
|
|
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
|
|
|
|
CefBrowserHost::MouseButtonType button_type = MBT_LEFT;
|
|
switch (event->button) {
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
button_type = MBT_MIDDLE;
|
|
break;
|
|
case 3:
|
|
button_type = MBT_RIGHT;
|
|
break;
|
|
default:
|
|
// Other mouse buttons are not handled here.
|
|
return FALSE;
|
|
}
|
|
|
|
CefMouseEvent mouse_event;
|
|
mouse_event.x = event->x;
|
|
mouse_event.y = event->y;
|
|
window->ApplyPopupOffset(mouse_event.x, mouse_event.y);
|
|
mouse_event.modifiers = get_cef_state_modifiers(event->state);
|
|
|
|
bool mouse_up = (event->type == GDK_BUTTON_RELEASE);
|
|
if (!mouse_up)
|
|
gtk_widget_grab_focus(widget);
|
|
|
|
int click_count = 1;
|
|
switch (event->type) {
|
|
case GDK_2BUTTON_PRESS:
|
|
click_count = 2;
|
|
break;
|
|
case GDK_3BUTTON_PRESS:
|
|
click_count = 3;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
host->SendMouseClickEvent(mouse_event, button_type, mouse_up, click_count);
|
|
return TRUE;
|
|
}
|
|
|
|
gint glarea_move_event(GtkWidget* widget,
|
|
GdkEventMotion* event,
|
|
OSRWindow* window) {
|
|
gint x, y;
|
|
GdkModifierType state;
|
|
|
|
if (event->is_hint) {
|
|
gdk_window_get_pointer(event->window, &x, &y, &state);
|
|
} else {
|
|
x = (gint)event->x;
|
|
y = (gint)event->y;
|
|
state = (GdkModifierType)event->state;
|
|
}
|
|
|
|
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
|
|
|
|
CefMouseEvent mouse_event;
|
|
mouse_event.x = x;
|
|
mouse_event.y = y;
|
|
window->ApplyPopupOffset(mouse_event.x, mouse_event.y);
|
|
mouse_event.modifiers = get_cef_state_modifiers(state);
|
|
|
|
bool mouse_leave = (event->type == GDK_LEAVE_NOTIFY);
|
|
|
|
host->SendMouseMoveEvent(mouse_event, mouse_leave);
|
|
return TRUE;
|
|
}
|
|
|
|
gint glarea_scroll_event(GtkWidget* widget,
|
|
GdkEventScroll* event,
|
|
OSRWindow* window) {
|
|
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
|
|
|
|
CefMouseEvent mouse_event;
|
|
mouse_event.x = event->x;
|
|
mouse_event.y = event->y;
|
|
window->ApplyPopupOffset(mouse_event.x, mouse_event.y);
|
|
mouse_event.modifiers = get_cef_state_modifiers(event->state);
|
|
|
|
static const int scrollbarPixelsPerGtkTick = 40;
|
|
int deltaX = 0;
|
|
int deltaY = 0;
|
|
switch (event->direction) {
|
|
case GDK_SCROLL_UP:
|
|
deltaY = scrollbarPixelsPerGtkTick;
|
|
break;
|
|
case GDK_SCROLL_DOWN:
|
|
deltaY = -scrollbarPixelsPerGtkTick;
|
|
break;
|
|
case GDK_SCROLL_LEFT:
|
|
deltaX = scrollbarPixelsPerGtkTick;
|
|
break;
|
|
case GDK_SCROLL_RIGHT:
|
|
deltaX = -scrollbarPixelsPerGtkTick;
|
|
break;
|
|
}
|
|
|
|
host->SendMouseWheelEvent(mouse_event, deltaX, deltaY);
|
|
return TRUE;
|
|
}
|
|
|
|
gint glarea_key_event(GtkWidget* widget,
|
|
GdkEventKey* event,
|
|
OSRWindow* window) {
|
|
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
|
|
|
|
CefKeyEvent key_event;
|
|
key_event.native_key_code = event->keyval;
|
|
key_event.modifiers = get_cef_state_modifiers(event->state);
|
|
|
|
if (event->type == GDK_KEY_PRESS) {
|
|
key_event.type = KEYEVENT_RAWKEYDOWN;
|
|
host->SendKeyEvent(key_event);
|
|
} else {
|
|
// Need to send both KEYUP and CHAR events.
|
|
key_event.type = KEYEVENT_KEYUP;
|
|
host->SendKeyEvent(key_event);
|
|
key_event.type = KEYEVENT_CHAR;
|
|
host->SendKeyEvent(key_event);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gint glarea_focus_event(GtkWidget* widget,
|
|
GdkEventFocus* event,
|
|
OSRWindow* window) {
|
|
CefRefPtr<CefBrowserHost> host = window->GetBrowserHost();
|
|
host->SendFocusEvent(event->in == TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
void widget_get_rect_in_screen(GtkWidget* widget, GdkRectangle* r) {
|
|
gint x, y, w, h;
|
|
GdkRectangle extents;
|
|
|
|
GdkWindow* window = gtk_widget_get_parent_window(widget);
|
|
|
|
// Get parent's left-top screen coordinates.
|
|
gdk_window_get_root_origin(window, &x, &y);
|
|
// Get parent's width and height.
|
|
gdk_drawable_get_size(window, &w, &h);
|
|
// Get parent's extents including decorations.
|
|
gdk_window_get_frame_extents(window, &extents);
|
|
|
|
// X and Y calculations assume that left, right and bottom border sizes are
|
|
// all the same.
|
|
const gint border = (extents.width - w) / 2;
|
|
r->x = x + border + widget->allocation.x;
|
|
r->y = y + (extents.height - h) - border + widget->allocation.y;
|
|
r->width = widget->allocation.width;
|
|
r->height = widget->allocation.height;
|
|
}
|
|
|
|
class ScopedGLContext {
|
|
public:
|
|
ScopedGLContext(GtkWidget* widget, bool swap_buffers)
|
|
: swap_buffers_(swap_buffers) {
|
|
GdkGLContext* glcontext = gtk_widget_get_gl_context(widget);
|
|
gldrawable_ = gtk_widget_get_gl_drawable(widget);
|
|
is_valid_ = gdk_gl_drawable_gl_begin(gldrawable_, glcontext);
|
|
}
|
|
|
|
virtual ~ScopedGLContext() {
|
|
if (is_valid_) {
|
|
gdk_gl_drawable_gl_end(gldrawable_);
|
|
|
|
if(swap_buffers_) {
|
|
if (gdk_gl_drawable_is_double_buffered(gldrawable_))
|
|
gdk_gl_drawable_swap_buffers(gldrawable_);
|
|
else
|
|
glFlush();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsValid() const { return is_valid_; }
|
|
|
|
private:
|
|
bool swap_buffers_;
|
|
GdkGLContext* glcontext_;
|
|
GdkGLDrawable* gldrawable_;
|
|
bool is_valid_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// static
|
|
CefRefPtr<OSRWindow> OSRWindow::Create(OSRBrowserProvider* browser_provider,
|
|
bool transparent,
|
|
CefWindowHandle parentView) {
|
|
ASSERT(browser_provider);
|
|
if (!browser_provider)
|
|
return NULL;
|
|
|
|
return new OSRWindow(browser_provider, transparent, parentView);
|
|
}
|
|
|
|
// static
|
|
CefRefPtr<OSRWindow> OSRWindow::From(
|
|
CefRefPtr<ClientHandler::RenderHandler> renderHandler) {
|
|
return static_cast<OSRWindow*>(renderHandler.get());
|
|
}
|
|
|
|
void OSRWindow::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
|
|
// Disconnect all signal handlers that reference |this|.
|
|
g_signal_handlers_disconnect_matched(glarea_, G_SIGNAL_MATCH_DATA, 0, 0,
|
|
NULL, NULL, this);
|
|
|
|
DisableGL();
|
|
}
|
|
|
|
bool OSRWindow::GetViewRect(CefRefPtr<CefBrowser> browser,
|
|
CefRect& rect) {
|
|
if (!glarea_)
|
|
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.
|
|
rect.x = rect.y = 0;
|
|
rect.width = glarea_->allocation.width;
|
|
rect.height = glarea_->allocation.height;
|
|
return true;
|
|
}
|
|
|
|
bool OSRWindow::GetScreenPoint(CefRefPtr<CefBrowser> browser,
|
|
int viewX,
|
|
int viewY,
|
|
int& screenX,
|
|
int& screenY) {
|
|
GdkRectangle screen_rect;
|
|
widget_get_rect_in_screen(glarea_, &screen_rect);
|
|
screenX = screen_rect.x + viewX;
|
|
screenY = screen_rect.y + viewY;
|
|
return true;
|
|
}
|
|
|
|
void OSRWindow::OnPopupShow(CefRefPtr<CefBrowser> browser,
|
|
bool show) {
|
|
if (!show) {
|
|
CefRect dirty_rect = renderer_.popup_rect();
|
|
renderer_.ClearPopupRects();
|
|
browser->GetHost()->Invalidate(dirty_rect, PET_VIEW);
|
|
}
|
|
renderer_.OnPopupShow(browser, show);
|
|
}
|
|
|
|
void OSRWindow::OnPopupSize(CefRefPtr<CefBrowser> browser,
|
|
const CefRect& rect) {
|
|
renderer_.OnPopupSize(browser, rect);
|
|
}
|
|
|
|
void OSRWindow::OnPaint(CefRefPtr<CefBrowser> browser,
|
|
PaintElementType type,
|
|
const RectList& dirtyRects,
|
|
const void* buffer,
|
|
int width, int height) {
|
|
if (painting_popup_) {
|
|
renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
|
|
return;
|
|
}
|
|
|
|
if (!gl_enabled_)
|
|
EnableGL();
|
|
|
|
ScopedGLContext scoped_gl_context(glarea_, true);
|
|
if (!scoped_gl_context.IsValid())
|
|
return;
|
|
|
|
renderer_.OnPaint(browser, type, dirtyRects, buffer, width, height);
|
|
if (type == PET_VIEW && !renderer_.popup_rect().IsEmpty()) {
|
|
painting_popup_ = true;
|
|
CefRect client_popup_rect(0, 0,
|
|
renderer_.popup_rect().width,
|
|
renderer_.popup_rect().height);
|
|
browser->GetHost()->Invalidate(client_popup_rect, PET_POPUP);
|
|
painting_popup_ = false;
|
|
}
|
|
renderer_.Render();
|
|
}
|
|
|
|
void OSRWindow::OnCursorChange(CefRefPtr<CefBrowser> browser,
|
|
CefCursorHandle cursor) {
|
|
GtkWidget* window = gtk_widget_get_toplevel(glarea_);
|
|
GdkWindow* gdk_window = gtk_widget_get_window(window);
|
|
if (cursor->type == GDK_LAST_CURSOR)
|
|
cursor = NULL;
|
|
gdk_window_set_cursor(gdk_window, cursor);
|
|
}
|
|
|
|
void OSRWindow::Invalidate() {
|
|
if (!CefCurrentlyOn(TID_UI)) {
|
|
CefPostTask(TID_UI, NewCefRunnableMethod(this, &OSRWindow::Invalidate));
|
|
return;
|
|
}
|
|
|
|
// Don't post another task if the previous task is still pending.
|
|
if (render_task_pending_)
|
|
return;
|
|
|
|
render_task_pending_ = true;
|
|
|
|
// Render at 30fps.
|
|
static const int kRenderDelay = 1000 / 30;
|
|
CefPostDelayedTask(TID_UI, NewCefRunnableMethod(this, &OSRWindow::Render),
|
|
kRenderDelay);
|
|
}
|
|
|
|
bool OSRWindow::IsOverPopupWidget(int x, int y) const {
|
|
const CefRect& rc = renderer_.popup_rect();
|
|
int popup_right = rc.x + rc.width;
|
|
int popup_bottom = rc.y + rc.height;
|
|
return (x >= rc.x) && (x < popup_right) &&
|
|
(y >= rc.y) && (y < popup_bottom);
|
|
}
|
|
|
|
int OSRWindow::GetPopupXOffset() const {
|
|
return renderer_.original_popup_rect().x - renderer_.popup_rect().x;
|
|
}
|
|
|
|
int OSRWindow::GetPopupYOffset() const {
|
|
return renderer_.original_popup_rect().y - renderer_.popup_rect().y;
|
|
}
|
|
|
|
void OSRWindow::ApplyPopupOffset(int& x, int& y) const {
|
|
if (IsOverPopupWidget(x, y)) {
|
|
x += GetPopupXOffset();
|
|
y += GetPopupYOffset();
|
|
}
|
|
}
|
|
|
|
OSRWindow::OSRWindow(OSRBrowserProvider* browser_provider,
|
|
bool transparent,
|
|
CefWindowHandle parentView)
|
|
: renderer_(transparent),
|
|
browser_provider_(browser_provider),
|
|
gl_enabled_(false),
|
|
painting_popup_(false),
|
|
render_task_pending_(false) {
|
|
glarea_ = gtk_drawing_area_new();
|
|
ASSERT(glarea_);
|
|
|
|
GdkGLConfig* glconfig = gdk_gl_config_new_by_mode(
|
|
static_cast<GdkGLConfigMode>(GDK_GL_MODE_RGB |
|
|
GDK_GL_MODE_DEPTH |
|
|
GDK_GL_MODE_DOUBLE));
|
|
ASSERT(glconfig);
|
|
|
|
gtk_widget_set_gl_capability(glarea_, glconfig, NULL, TRUE,
|
|
GDK_GL_RGBA_TYPE);
|
|
|
|
gtk_widget_set_can_focus(glarea_, TRUE);
|
|
|
|
g_signal_connect(G_OBJECT(glarea_), "size_allocate",
|
|
G_CALLBACK(glarea_size_allocation), this);
|
|
|
|
gtk_widget_set_events(glarea_,
|
|
GDK_BUTTON_PRESS_MASK |
|
|
GDK_BUTTON_RELEASE_MASK |
|
|
GDK_KEY_PRESS_MASK |
|
|
GDK_KEY_RELEASE_MASK |
|
|
GDK_ENTER_NOTIFY_MASK |
|
|
GDK_LEAVE_NOTIFY_MASK |
|
|
GDK_POINTER_MOTION_MASK |
|
|
GDK_POINTER_MOTION_HINT_MASK |
|
|
GDK_SCROLL_MASK |
|
|
GDK_FOCUS_CHANGE_MASK);
|
|
g_signal_connect(G_OBJECT(glarea_), "button_press_event",
|
|
G_CALLBACK(glarea_click_event), this);
|
|
g_signal_connect(G_OBJECT(glarea_), "button_release_event",
|
|
G_CALLBACK(glarea_click_event), this);
|
|
g_signal_connect(G_OBJECT(glarea_), "key_press_event",
|
|
G_CALLBACK(glarea_key_event), this);
|
|
g_signal_connect(G_OBJECT(glarea_), "key_release_event",
|
|
G_CALLBACK(glarea_key_event), this);
|
|
g_signal_connect(G_OBJECT(glarea_), "enter_notify_event",
|
|
G_CALLBACK(glarea_move_event), this);
|
|
g_signal_connect(G_OBJECT(glarea_), "leave_notify_event",
|
|
G_CALLBACK(glarea_move_event), this);
|
|
g_signal_connect(G_OBJECT(glarea_), "motion_notify_event",
|
|
G_CALLBACK(glarea_move_event), this);
|
|
g_signal_connect(G_OBJECT(glarea_), "scroll_event",
|
|
G_CALLBACK(glarea_scroll_event), this);
|
|
g_signal_connect(G_OBJECT(glarea_), "focus_in_event",
|
|
G_CALLBACK(glarea_focus_event), this);
|
|
g_signal_connect(G_OBJECT(glarea_), "focus_out_event",
|
|
G_CALLBACK(glarea_focus_event), this);
|
|
|
|
gtk_container_add(GTK_CONTAINER(parentView), glarea_);
|
|
}
|
|
|
|
OSRWindow::~OSRWindow() {
|
|
}
|
|
|
|
void OSRWindow::Render() {
|
|
ASSERT(CefCurrentlyOn(TID_UI));
|
|
if (render_task_pending_)
|
|
render_task_pending_ = false;
|
|
|
|
if (!gl_enabled_)
|
|
EnableGL();
|
|
|
|
ScopedGLContext scoped_gl_context(glarea_, true);
|
|
if (!scoped_gl_context.IsValid())
|
|
return;
|
|
|
|
renderer_.Render();
|
|
}
|
|
|
|
void OSRWindow::EnableGL() {
|
|
ASSERT(CefCurrentlyOn(TID_UI));
|
|
if (gl_enabled_)
|
|
return;
|
|
|
|
ScopedGLContext scoped_gl_context(glarea_, false);
|
|
if (!scoped_gl_context.IsValid())
|
|
return;
|
|
|
|
renderer_.Initialize();
|
|
|
|
gl_enabled_ = true;
|
|
}
|
|
|
|
void OSRWindow::DisableGL() {
|
|
ASSERT(CefCurrentlyOn(TID_UI));
|
|
|
|
if (!gl_enabled_)
|
|
return;
|
|
|
|
ScopedGLContext scoped_gl_context(glarea_, false);
|
|
if (!scoped_gl_context.IsValid())
|
|
return;
|
|
|
|
renderer_.Cleanup();
|
|
|
|
gl_enabled_ = false;
|
|
}
|
|
|