This source file includes following definitions.
- is_x_event_listened_
- IsActive
- IsMaximized
- IsMinimized
- IsFullscreen
- GetNativeWindow
- GetRestoredBounds
- GetRestoredState
- GetBounds
- Show
- ShowInactive
- Hide
- Close
- Activate
- Deactivate
- Maximize
- Minimize
- Restore
- SetBounds
- OnXEvent
- FlashFrame
- IsAlwaysOnTop
- RenderViewHostChanged
- SetAlwaysOnTop
- GetHostView
- GetDialogPosition
- GetMaximumDialogSize
- AddObserver
- RemoveObserver
- ActiveWindowChanged
- OnMainWindowDeleteEvent
- OnConfigure
- OnConfigureDebounced
- UpdateContentMinMaxSize
- OnWindowState
- GetWindowEdge
- OnMouseMoveEvent
- OnButtonPress
- SetFullscreen
- IsFullscreenOrPending
- IsDetached
- UpdateWindowIcon
- UpdateWindowTitle
- UpdateBadgeIcon
- UpdateDraggableRegions
- GetDraggableRegion
- UpdateShape
- HandleKeyboardEvent
- IsFrameless
- HasFrameColor
- FrameColor
- GetFrameInsets
- HideWithApp
- ShowWithApp
- UpdateShelfMenu
- GetContentMinimumSize
- GetContentMaximumSize
- SetContentSizeConstraints
#include "chrome/browser/ui/gtk/apps/native_app_window_gtk.h"
#include <gdk/gdkx.h>
#include <vector>
#include "base/message_loop/message_pump_gtk.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/gtk/extensions/extension_keybinding_registry_gtk.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "chrome/browser/ui/gtk/gtk_window_util.h"
#include "chrome/browser/web_applications/web_app.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "extensions/common/extension.h"
#include "ui/base/x/active_window_watcher_x.h"
#include "ui/gfx/gtk_util.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/rect.h"
using apps::AppWindow;
namespace {
const int kDebounceTimeoutMilliseconds = 100;
const char* kAtomsToCache[] = {
"_NET_WM_STATE",
"_NET_WM_STATE_HIDDEN",
NULL
};
}
NativeAppWindowGtk::NativeAppWindowGtk(AppWindow* app_window,
const AppWindow::CreateParams& params)
: app_window_(app_window),
window_(NULL),
state_(GDK_WINDOW_STATE_WITHDRAWN),
is_active_(false),
content_thinks_its_fullscreen_(false),
maximize_pending_(false),
frameless_(params.frame == AppWindow::FRAME_NONE),
always_on_top_(params.always_on_top),
frame_cursor_(NULL),
atom_cache_(base::MessagePumpGtk::GetDefaultXDisplay(), kAtomsToCache),
is_x_event_listened_(false) {
Observe(web_contents());
window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
gfx::NativeView native_view =
web_contents()->GetView()->GetNativeView();
gtk_container_add(GTK_CONTAINER(window_), native_view);
gfx::Insets frame_insets = GetFrameInsets();
gfx::Rect initial_bounds = params.GetInitialWindowBounds(frame_insets);
typedef apps::AppWindow::BoundsSpecification BoundsSpecification;
if (initial_bounds.x() != BoundsSpecification::kUnspecifiedPosition &&
initial_bounds.y() != BoundsSpecification::kUnspecifiedPosition) {
gtk_window_move(window_, initial_bounds.x(), initial_bounds.y());
}
int win_height = initial_bounds.height();
if (frameless_ &&
gtk_window_util::BoundsMatchMonitorSize(window_, initial_bounds)) {
win_height -= 1;
}
gtk_window_set_default_size(window_, initial_bounds.width(), win_height);
resizable_ = params.resizable;
if (!resizable_) {
gtk_widget_set_size_request(GTK_WIDGET(window_),
initial_bounds.width(), win_height);
gtk_window_set_resizable(window_, FALSE);
}
bounds_ = restored_bounds_ = initial_bounds;
gint x, y;
gtk_window_get_position(window_, &x, &y);
bounds_.set_origin(gfx::Point(x, y));
if (frameless_)
gtk_window_set_decorated(window_, false);
if (always_on_top_)
gtk_window_set_keep_above(window_, TRUE);
SetContentSizeConstraints(params.GetContentMinimumSize(frame_insets),
params.GetContentMaximumSize(frame_insets));
if (ui::GuessWindowManager() == ui::WM_COMPIZ)
suppress_window_raise_ = true;
gtk_window_set_title(window_, extension()->name().c_str());
std::string app_name = web_app::GenerateApplicationNameFromExtensionId(
extension()->id());
gtk_window_util::SetWindowCustomClass(window_,
web_app::GetWMClassFromAppName(app_name));
g_signal_connect(window_, "delete-event",
G_CALLBACK(OnMainWindowDeleteEventThunk), this);
g_signal_connect(window_, "configure-event",
G_CALLBACK(OnConfigureThunk), this);
g_signal_connect(window_, "window-state-event",
G_CALLBACK(OnWindowStateThunk), this);
if (frameless_) {
g_signal_connect(window_, "button-press-event",
G_CALLBACK(OnButtonPressThunk), this);
g_signal_connect(window_, "motion-notify-event",
G_CALLBACK(OnMouseMoveEventThunk), this);
}
std::vector< ::Atom> supported_atoms;
if (ui::GetAtomArrayProperty(ui::GetX11RootWindow(),
"_NET_SUPPORTED",
&supported_atoms)) {
if (std::find(supported_atoms.begin(),
supported_atoms.end(),
atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN")) !=
supported_atoms.end()) {
GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(window_));
gdk_window_add_filter(window,
&NativeAppWindowGtk::OnXEventThunk,
this);
is_x_event_listened_ = true;
}
}
extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryGtk(
Profile::FromBrowserContext(app_window_->browser_context()),
window_,
extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
NULL));
ui::ActiveWindowWatcherX::AddObserver(this);
}
NativeAppWindowGtk::~NativeAppWindowGtk() {
ui::ActiveWindowWatcherX::RemoveObserver(this);
if (is_x_event_listened_) {
gdk_window_remove_filter(NULL,
&NativeAppWindowGtk::OnXEventThunk,
this);
}
}
bool NativeAppWindowGtk::IsActive() const {
if (ui::ActiveWindowWatcherX::WMSupportsActivation())
return is_active_;
return gtk_window_is_active(window_);
}
bool NativeAppWindowGtk::IsMaximized() const {
return (state_ & GDK_WINDOW_STATE_MAXIMIZED);
}
bool NativeAppWindowGtk::IsMinimized() const {
return (state_ & GDK_WINDOW_STATE_ICONIFIED);
}
bool NativeAppWindowGtk::IsFullscreen() const {
return (state_ & GDK_WINDOW_STATE_FULLSCREEN);
}
gfx::NativeWindow NativeAppWindowGtk::GetNativeWindow() {
return window_;
}
gfx::Rect NativeAppWindowGtk::GetRestoredBounds() const {
gfx::Rect window_bounds = restored_bounds_;
window_bounds.Inset(-GetFrameInsets());
return window_bounds;
}
ui::WindowShowState NativeAppWindowGtk::GetRestoredState() const {
if (IsMaximized())
return ui::SHOW_STATE_MAXIMIZED;
if (IsFullscreen())
return ui::SHOW_STATE_FULLSCREEN;
return ui::SHOW_STATE_NORMAL;
}
gfx::Rect NativeAppWindowGtk::GetBounds() const {
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
if (!gdk_window)
return bounds_;
GdkRectangle window_bounds = {0};
gdk_window_get_frame_extents(gdk_window, &window_bounds);
return gfx::Rect(window_bounds.x, window_bounds.y,
window_bounds.width, window_bounds.height);
}
void NativeAppWindowGtk::Show() {
gtk_window_present(window_);
}
void NativeAppWindowGtk::ShowInactive() {
gtk_window_set_focus_on_map(window_, false);
gtk_widget_show(GTK_WIDGET(window_));
}
void NativeAppWindowGtk::Hide() {
gtk_widget_hide(GTK_WIDGET(window_));
}
void NativeAppWindowGtk::Close() {
app_window_->OnNativeWindowChanged();
window_configure_debounce_timer_.Stop();
GtkWidget* window = GTK_WIDGET(window_);
window_ = NULL;
app_window_->OnNativeClose();
gtk_widget_destroy(window);
}
void NativeAppWindowGtk::Activate() {
gtk_window_present(window_);
}
void NativeAppWindowGtk::Deactivate() {
gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_)));
}
void NativeAppWindowGtk::Maximize() {
gtk_window_present(window_);
if (!resizable_) {
maximize_pending_ = true;
gtk_window_set_resizable(window_, TRUE);
} else {
gtk_window_maximize(window_);
}
}
void NativeAppWindowGtk::Minimize() {
gtk_window_iconify(window_);
}
void NativeAppWindowGtk::Restore() {
if (IsMaximized())
gtk_window_unmaximize(window_);
else if (IsMinimized())
gtk_window_deiconify(window_);
gtk_window_present(window_);
}
void NativeAppWindowGtk::SetBounds(const gfx::Rect& bounds) {
gfx::Rect content_bounds = bounds;
gtk_window_move(window_, content_bounds.x(), content_bounds.y());
if (!resizable_) {
if (frameless_ &&
gtk_window_util::BoundsMatchMonitorSize(window_, content_bounds)) {
content_bounds.set_height(content_bounds.height() - 1);
}
gtk_widget_set_size_request(GTK_WIDGET(window_),
content_bounds.width(), content_bounds.height());
} else {
gtk_window_util::SetWindowSize(window_,
gfx::Size(bounds.width(), bounds.height()));
}
}
GdkFilterReturn NativeAppWindowGtk::OnXEvent(GdkXEvent* gdk_x_event,
GdkEvent* gdk_event) {
XEvent* x_event = static_cast<XEvent*>(gdk_x_event);
std::vector< ::Atom> atom_list;
if (x_event->type == PropertyNotify &&
x_event->xproperty.atom == atom_cache_.GetAtom("_NET_WM_STATE") &&
GTK_WIDGET(window_)->window &&
ui::GetAtomArrayProperty(GDK_WINDOW_XWINDOW(GTK_WIDGET(window_)->window),
"_NET_WM_STATE",
&atom_list)) {
std::vector< ::Atom>::iterator it =
std::find(atom_list.begin(),
atom_list.end(),
atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN"));
GdkWindowState previous_state = state_;
state_ = (it != atom_list.end()) ? GDK_WINDOW_STATE_ICONIFIED :
static_cast<GdkWindowState>(state_ & ~GDK_WINDOW_STATE_ICONIFIED);
if (previous_state != state_) {
app_window_->OnNativeWindowChanged();
}
}
return GDK_FILTER_CONTINUE;
}
void NativeAppWindowGtk::FlashFrame(bool flash) {
gtk_window_set_urgency_hint(window_, flash);
}
bool NativeAppWindowGtk::IsAlwaysOnTop() const {
return always_on_top_;
}
void NativeAppWindowGtk::RenderViewHostChanged(
content::RenderViewHost* old_host,
content::RenderViewHost* new_host) {
web_contents()->GetView()->Focus();
}
void NativeAppWindowGtk::SetAlwaysOnTop(bool always_on_top) {
if (always_on_top_ != always_on_top) {
always_on_top_ = always_on_top;
gtk_window_set_keep_above(window_, always_on_top_ ? TRUE : FALSE);
}
}
gfx::NativeView NativeAppWindowGtk::GetHostView() const {
NOTIMPLEMENTED();
return NULL;
}
gfx::Point NativeAppWindowGtk::GetDialogPosition(const gfx::Size& size) {
gint current_width = 0;
gint current_height = 0;
gtk_window_get_size(window_, ¤t_width, ¤t_height);
return gfx::Point(current_width / 2 - size.width() / 2,
current_height / 2 - size.height() / 2);
}
gfx::Size NativeAppWindowGtk::GetMaximumDialogSize() {
gint current_width = 0;
gint current_height = 0;
gtk_window_get_size(window_, ¤t_width, ¤t_height);
return gfx::Size(current_width, current_height);
}
void NativeAppWindowGtk::AddObserver(
web_modal::ModalDialogHostObserver* observer) {
observer_list_.AddObserver(observer);
}
void NativeAppWindowGtk::RemoveObserver(
web_modal::ModalDialogHostObserver* observer) {
observer_list_.RemoveObserver(observer);
}
void NativeAppWindowGtk::ActiveWindowChanged(GdkWindow* active_window) {
if (!window_)
return;
is_active_ = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window;
if (is_active_)
app_window_->OnNativeWindowActivated();
}
gboolean NativeAppWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget,
GdkEvent* event) {
Close();
return TRUE;
}
gboolean NativeAppWindowGtk::OnConfigure(GtkWidget* widget,
GdkEventConfigure* event) {
bounds_.SetRect(event->x, event->y, event->width, event->height);
window_configure_debounce_timer_.Stop();
window_configure_debounce_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), this,
&NativeAppWindowGtk::OnConfigureDebounced);
return FALSE;
}
void NativeAppWindowGtk::OnConfigureDebounced() {
gtk_window_util::UpdateWindowPosition(this, &bounds_, &restored_bounds_);
app_window_->OnNativeWindowChanged();
FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver,
observer_list_,
OnPositionRequiresUpdate());
if (!IsFullscreen() && IsFullscreenOrPending()) {
gtk_window_fullscreen(window_);
}
if (maximize_pending_) {
if (!(state_ & GDK_WINDOW_STATE_MAXIMIZED)) {
gtk_window_maximize(window_);
} else {
maximize_pending_ = false;
if (!resizable_)
gtk_window_set_resizable(window_, FALSE);
}
}
}
void NativeAppWindowGtk::UpdateContentMinMaxSize() {
GdkGeometry hints;
int hints_mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
gfx::Size min_size = size_constraints_.GetMinimumSize();
hints.min_height = min_size.height();
hints.min_width = min_size.width();
gfx::Size max_size = size_constraints_.GetMaximumSize();
const int kUnboundedSize = apps::SizeConstraints::kUnboundedSize;
hints.max_height = max_size.height() == kUnboundedSize ?
G_MAXINT : max_size.height();
hints.max_width = max_size.width() == kUnboundedSize ?
G_MAXINT : max_size.width();
gtk_window_set_geometry_hints(
window_,
GTK_WIDGET(window_),
&hints,
static_cast<GdkWindowHints>(hints_mask));
}
gboolean NativeAppWindowGtk::OnWindowState(GtkWidget* sender,
GdkEventWindowState* event) {
state_ = event->new_window_state;
if (content_thinks_its_fullscreen_ &&
!(state_ & GDK_WINDOW_STATE_FULLSCREEN)) {
content_thinks_its_fullscreen_ = false;
content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
if (rvh)
rvh->ExitFullscreen();
}
return FALSE;
}
bool NativeAppWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) {
if (!frameless_)
return false;
if (IsMaximized() || IsFullscreen())
return false;
return gtk_window_util::GetWindowEdge(bounds_.size(), 0, x, y, edge);
}
gboolean NativeAppWindowGtk::OnMouseMoveEvent(GtkWidget* widget,
GdkEventMotion* event) {
if (!frameless_) {
if (frame_cursor_) {
frame_cursor_ = NULL;
gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL);
}
return FALSE;
}
if (!resizable_)
return FALSE;
GdkWindowEdge edge;
bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x),
static_cast<int>(event->y), &edge);
GdkCursorType new_cursor = GDK_LAST_CURSOR;
if (has_hit_edge)
new_cursor = gtk_window_util::GdkWindowEdgeToGdkCursorType(edge);
GdkCursorType last_cursor = GDK_LAST_CURSOR;
if (frame_cursor_)
last_cursor = frame_cursor_->type;
if (last_cursor != new_cursor) {
frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL;
gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)),
frame_cursor_);
}
return FALSE;
}
gboolean NativeAppWindowGtk::OnButtonPress(GtkWidget* widget,
GdkEventButton* event) {
DCHECK(frameless_);
int win_x, win_y;
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
gdk_window_get_origin(gdk_window, &win_x, &win_y);
GdkWindowEdge edge;
gfx::Point point(static_cast<int>(event->x_root - win_x),
static_cast<int>(event->y_root - win_y));
bool has_hit_edge = resizable_ && GetWindowEdge(point.x(), point.y(), &edge);
bool has_hit_titlebar =
draggable_region_ && draggable_region_->contains(event->x, event->y);
if (event->button == 1) {
if (GDK_BUTTON_PRESS == event->type) {
if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_)
gdk_window_raise(GTK_WIDGET(widget)->window);
if (has_hit_edge) {
gtk_window_begin_resize_drag(window_, edge, event->button,
static_cast<gint>(event->x_root),
static_cast<gint>(event->y_root),
event->time);
return TRUE;
} else if (has_hit_titlebar) {
return gtk_window_util::HandleTitleBarLeftMousePress(
window_, bounds_, event);
}
} else if (GDK_2BUTTON_PRESS == event->type) {
if (has_hit_titlebar && resizable_) {
if (IsMaximized()) {
gtk_window_util::UnMaximize(GTK_WINDOW(widget),
bounds_, restored_bounds_);
} else {
gtk_window_maximize(window_);
}
return TRUE;
}
}
} else if (event->button == 2) {
if (has_hit_titlebar || has_hit_edge)
gdk_window_lower(gdk_window);
return TRUE;
}
return FALSE;
}
void NativeAppWindowGtk::SetFullscreen(int fullscreen_types) {
bool fullscreen = (fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE);
content_thinks_its_fullscreen_ = fullscreen;
if (fullscreen) {
if (resizable_) {
gtk_window_fullscreen(window_);
} else {
gtk_window_set_resizable(window_, TRUE);
}
} else {
gtk_window_unfullscreen(window_);
if (!resizable_)
gtk_window_set_resizable(window_, FALSE);
}
}
bool NativeAppWindowGtk::IsFullscreenOrPending() const {
return content_thinks_its_fullscreen_ || IsFullscreen();
}
bool NativeAppWindowGtk::IsDetached() const {
return false;
}
void NativeAppWindowGtk::UpdateWindowIcon() {
Profile* profile =
Profile::FromBrowserContext(app_window_->browser_context());
gfx::Image app_icon = app_window_->app_icon();
if (!app_icon.IsEmpty())
gtk_util::SetWindowIcon(window_, profile, app_icon.ToGdkPixbuf());
else
gtk_util::SetWindowIcon(window_, profile);
}
void NativeAppWindowGtk::UpdateWindowTitle() {
base::string16 title = app_window_->GetTitle();
gtk_window_set_title(window_, base::UTF16ToUTF8(title).c_str());
}
void NativeAppWindowGtk::UpdateBadgeIcon() {
NOTIMPLEMENTED();
}
void NativeAppWindowGtk::UpdateDraggableRegions(
const std::vector<extensions::DraggableRegion>& regions) {
if (!frameless_)
return;
draggable_region_.reset(AppWindow::RawDraggableRegionsToSkRegion(regions));
}
SkRegion* NativeAppWindowGtk::GetDraggableRegion() {
return draggable_region_.get();
}
void NativeAppWindowGtk::UpdateShape(scoped_ptr<SkRegion> region) {
NOTIMPLEMENTED();
}
void NativeAppWindowGtk::HandleKeyboardEvent(
const content::NativeWebKeyboardEvent& event) {
}
bool NativeAppWindowGtk::IsFrameless() const {
return frameless_;
}
bool NativeAppWindowGtk::HasFrameColor() const {
return false;
}
SkColor NativeAppWindowGtk::FrameColor() const {
return SkColor();
}
gfx::Insets NativeAppWindowGtk::GetFrameInsets() const {
if (frameless_)
return gfx::Insets();
GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
if (!gdk_window)
return gfx::Insets();
gint current_width = 0;
gint current_height = 0;
gtk_window_get_size(window_, ¤t_width, ¤t_height);
gint current_x = 0;
gint current_y = 0;
gdk_window_get_position(gdk_window, ¤t_x, ¤t_y);
GdkRectangle rect_with_decorations = {0};
gdk_window_get_frame_extents(gdk_window,
&rect_with_decorations);
int left_inset = current_x - rect_with_decorations.x;
int top_inset = current_y - rect_with_decorations.y;
return gfx::Insets(
top_inset,
left_inset,
rect_with_decorations.height - current_height - top_inset,
rect_with_decorations.width - current_width - left_inset);
}
void NativeAppWindowGtk::HideWithApp() {}
void NativeAppWindowGtk::ShowWithApp() {}
void NativeAppWindowGtk::UpdateShelfMenu() {
NOTIMPLEMENTED();
}
gfx::Size NativeAppWindowGtk::GetContentMinimumSize() const {
return size_constraints_.GetMinimumSize();
}
gfx::Size NativeAppWindowGtk::GetContentMaximumSize() const {
return size_constraints_.GetMaximumSize();
}
void NativeAppWindowGtk::SetContentSizeConstraints(
const gfx::Size& min_size, const gfx::Size& max_size) {
bool changed = size_constraints_.GetMinimumSize() != min_size ||
size_constraints_.GetMaximumSize() != max_size;
if (!changed)
return;
size_constraints_.set_minimum_size(min_size);
size_constraints_.set_maximum_size(max_size);
UpdateContentMinMaxSize();
}