| // Copyright 2013 The Chromium 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 "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::ShellWindow; |
| |
| namespace { |
| |
| // The timeout in milliseconds before we'll get the true window position with |
| // gtk_window_get_position() after the last GTK configure-event signal. |
| const int kDebounceTimeoutMilliseconds = 100; |
| |
| const char* kAtomsToCache[] = { |
| "_NET_WM_STATE", |
| "_NET_WM_STATE_HIDDEN", |
| NULL |
| }; |
| |
| } // namespace |
| |
| NativeAppWindowGtk::NativeAppWindowGtk(ShellWindow* shell_window, |
| const ShellWindow::CreateParams& params) |
| : shell_window_(shell_window), |
| window_(NULL), |
| state_(GDK_WINDOW_STATE_WITHDRAWN), |
| is_active_(false), |
| content_thinks_its_fullscreen_(false), |
| maximize_pending_(false), |
| frameless_(params.frame == ShellWindow::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); |
| |
| if (params.bounds.x() != INT_MIN && params.bounds.y() != INT_MIN) |
| gtk_window_move(window_, params.bounds.x(), params.bounds.y()); |
| |
| // This is done to avoid a WM "feature" where setting the window size to |
| // the monitor size causes the WM to set the EWMH for full screen mode. |
| int win_height = params.bounds.height(); |
| if (frameless_ && |
| gtk_window_util::BoundsMatchMonitorSize(window_, params.bounds)) { |
| win_height -= 1; |
| } |
| gtk_window_set_default_size(window_, params.bounds.width(), win_height); |
| |
| resizable_ = params.resizable; |
| if (!resizable_) { |
| // If the window doesn't have a size request when we set resizable to |
| // false, GTK will shrink the window to 1x1px. |
| gtk_widget_set_size_request(GTK_WIDGET(window_), |
| params.bounds.width(), win_height); |
| gtk_window_set_resizable(window_, FALSE); |
| } |
| |
| // make sure bounds_ and restored_bounds_ have correct values until we |
| // get our first configure-event |
| bounds_ = restored_bounds_ = params.bounds; |
| gint x, y; |
| gtk_window_get_position(window_, &x, &y); |
| bounds_.set_origin(gfx::Point(x, y)); |
| |
| // Hide titlebar when {frame: 'none'} specified on ShellWindow. |
| if (frameless_) |
| gtk_window_set_decorated(window_, false); |
| |
| if (always_on_top_) |
| gtk_window_set_keep_above(window_, TRUE); |
| |
| UpdateWindowMinMaxSize(); |
| |
| // In some (older) versions of compiz, raising top-level windows when they |
| // are partially off-screen causes them to get snapped back on screen, not |
| // always even on the current virtual desktop. If we are running under |
| // compiz, suppress such raises, as they are not necessary in compiz anyway. |
| 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); |
| } |
| |
| // If _NET_WM_STATE_HIDDEN is in _NET_SUPPORTED, listen for XEvent to work |
| // around GTK+ not reporting minimization state changes. See comment in the |
| // |OnXEvent|. |
| 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; |
| } |
| } |
| |
| // Add the keybinding registry. |
| extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryGtk( |
| shell_window_->profile(), |
| window_, |
| extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY, |
| shell_window_)); |
| |
| 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_; |
| |
| // This still works even though we don't get the activation notification. |
| 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 { |
| // :GetBounds() is expecting the outer window bounds to be returned (ie. |
| // including window decorations). The internal |bounds_| is not including them |
| // and trying to add the decoration to |bounds_| would require calling |
| // gdk_window_get_frame_extents. The best thing to do is to directly get the |
| // frame bounds and only use the internal value if we can't. |
| 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() { |
| shell_window_->OnNativeWindowChanged(); |
| |
| // Cancel any pending callback from the window configure debounce timer. |
| window_configure_debounce_timer_.Stop(); |
| |
| GtkWidget* window = GTK_WIDGET(window_); |
| // To help catch bugs in any event handlers that might get fired during the |
| // destruction, set window_ to NULL before any handlers will run. |
| window_ = NULL; |
| |
| // OnNativeClose does a delete this so no other members should |
| // be accessed after. gtk_widget_destroy is safe (and must |
| // be last). |
| shell_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() { |
| // Represent the window first in order to keep the maximization behavior |
| // consistency with Windows platform. Otherwise the window will be hidden if |
| // it has been minimized. |
| gtk_window_present(window_); |
| |
| if (!resizable_) { |
| // When the window is not resizable, we still want to make this call succeed |
| // but gtk will not allow it if the window is not resizable. The actual |
| // maximization will happen with the subsequent OnConfigureDebounced call, |
| // that will be triggered when the window manager's resizable property |
| // changes. |
| 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_); |
| |
| // Represent the window to keep restoration behavior consistency with Windows |
| // platform. |
| // TODO(zhchbin): verify whether we need this until http://crbug.com/261013 is |
| // fixed. |
| 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); |
| } |
| // TODO(jeremya): set_size_request doesn't honor min/max size, so the |
| // bounds should be constrained manually. |
| 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) { |
| // Work around GTK+ not reporting minimization state changes. Listen |
| // for _NET_WM_STATE property changes and use _NET_WM_STATE_HIDDEN's |
| // presence to set or clear the iconified bit if _NET_WM_STATE_HIDDEN |
| // is supported. http://crbug.com/162794. |
| 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_) { |
| shell_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) { |
| // gdk_window_get_state() does not give us the correct value for the |
| // GDK_WINDOW_STATE_ABOVE bit. Cache the current state. |
| 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) { |
| // Do nothing if we're in the process of closing the browser window. |
| if (!window_) |
| return; |
| |
| is_active_ = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; |
| if (is_active_) |
| shell_window_->OnNativeWindowActivated(); |
| } |
| |
| // Callback for the delete event. This event is fired when the user tries to |
| // close the window (e.g., clicking on the X in the window manager title bar). |
| gboolean NativeAppWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget, |
| GdkEvent* event) { |
| Close(); |
| |
| // Return true to prevent the GTK window from being destroyed. Close will |
| // destroy it for us. |
| return TRUE; |
| } |
| |
| gboolean NativeAppWindowGtk::OnConfigure(GtkWidget* widget, |
| GdkEventConfigure* event) { |
| // We update |bounds_| but not |restored_bounds_| here. The latter needs |
| // to be updated conditionally when the window is non-maximized and non- |
| // fullscreen, but whether those state updates have been processed yet is |
| // window-manager specific. We update |restored_bounds_| in the debounced |
| // handler below, after the window state has been updated. |
| bounds_.SetRect(event->x, event->y, event->width, event->height); |
| |
| // The GdkEventConfigure* we get here doesn't have quite the right |
| // coordinates though (they're relative to the drawable window area, rather |
| // than any window manager decorations, if enabled), so we need to call |
| // gtk_window_get_position() to get the right values. (Otherwise session |
| // restore, if enabled, will restore windows to incorrect positions.) That's |
| // a round trip to the X server though, so we set a debounce timer and only |
| // call it (in OnConfigureDebounced() below) after we haven't seen a |
| // reconfigure event in a short while. |
| // We don't use Reset() because the timer may not yet be running. |
| // (In that case Stop() is a no-op.) |
| 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_); |
| shell_window_->OnNativeWindowChanged(); |
| |
| FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver, |
| observer_list_, |
| OnPositionRequiresUpdate()); |
| |
| // Fullscreen of non-resizable windows requires them to be made resizable |
| // first. After that takes effect and OnConfigure is called we transition |
| // to fullscreen. |
| if (!IsFullscreen() && IsFullscreenOrPending()) { |
| gtk_window_fullscreen(window_); |
| } |
| |
| // maximize_pending_ is the boolean that lets us know that the window is in |
| // the process of being maximized but was set as not resizable. |
| // This function will be called twice during the maximization process: |
| // 1. gtk_window_maximize() is called to maximize the window; |
| // 2. gtk_set_resizable(, FALSE) is called to make the window no longer |
| // resizable. |
| // gtk_window_maximize() will cause ::OnConfigureDebounced to be called |
| // again, at which time we will run into the second step. |
| 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); |
| } |
| } |
| } |
| |
| 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_) { |
| // Reset the cursor. |
| if (frame_cursor_) { |
| frame_cursor_ = NULL; |
| gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL); |
| } |
| return FALSE; |
| } |
| |
| if (!resizable_) |
| return FALSE; |
| |
| // Update the cursor if we're on the custom frame border. |
| 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_); |
| // Make the button press coordinate relative to the browser window. |
| 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) { |
| // Raise the window after a click on either the titlebar or the border to |
| // match the behavior of most window managers, unless that behavior has |
| // been suppressed. |
| 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_) { |
| // Maximize/restore on double click. |
| 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; |
| } |
| |
| // NativeAppWindow implementation: |
| |
| void NativeAppWindowGtk::SetFullscreen(int fullscreen_types) { |
| bool fullscreen = (fullscreen_types != ShellWindow::FULLSCREEN_TYPE_NONE); |
| content_thinks_its_fullscreen_ = fullscreen; |
| if (fullscreen) { |
| if (resizable_) { |
| gtk_window_fullscreen(window_); |
| } else { |
| // We must first make the window resizable. That won't take effect |
| // immediately, so OnConfigureDebounced completes the fullscreen call. |
| gtk_window_set_resizable(window_, TRUE); |
| } |
| } else { |
| gtk_window_unfullscreen(window_); |
| if (!resizable_) |
| gtk_window_set_resizable(window_, FALSE); |
| } |
| } |
| |
| bool NativeAppWindowGtk::IsFullscreenOrPending() const { |
| // |content_thinks_its_fullscreen_| is used when transitioning, and when |
| // the state change will not be made for some time. However, it is possible |
| // for a state update to be made before the final fullscreen state comes. |
| // In that case, |content_thinks_its_fullscreen_| will be cleared, but we |
| // will fall back to |IsFullscreen| which will soon have the correct state. |
| return content_thinks_its_fullscreen_ || IsFullscreen(); |
| } |
| |
| bool NativeAppWindowGtk::IsDetached() const { |
| return false; |
| } |
| |
| void NativeAppWindowGtk::UpdateWindowIcon() { |
| Profile* profile = shell_window_->profile(); |
| gfx::Image app_icon = shell_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 = shell_window_->GetTitle(); |
| gtk_window_set_title(window_, UTF16ToUTF8(title).c_str()); |
| } |
| |
| void NativeAppWindowGtk::UpdateDraggableRegions( |
| const std::vector<extensions::DraggableRegion>& regions) { |
| // Draggable region is not supported for non-frameless window. |
| if (!frameless_) |
| return; |
| |
| draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions)); |
| } |
| |
| SkRegion* NativeAppWindowGtk::GetDraggableRegion() { |
| return draggable_region_.get(); |
| } |
| |
| void NativeAppWindowGtk::UpdateShape(scoped_ptr<SkRegion> region) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void NativeAppWindowGtk::HandleKeyboardEvent( |
| const content::NativeWebKeyboardEvent& event) { |
| // No-op. |
| } |
| |
| bool NativeAppWindowGtk::IsFrameless() const { |
| return frameless_; |
| } |
| |
| 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::UpdateWindowMinMaxSize() { |
| GdkGeometry hints; |
| int hints_mask = 0; |
| if (shell_window_->size_constraints().HasMinimumSize()) { |
| gfx::Size min_size = shell_window_->size_constraints().GetMinimumSize(); |
| hints.min_height = min_size.height(); |
| hints.min_width = min_size.width(); |
| hints_mask |= GDK_HINT_MIN_SIZE; |
| } |
| if (shell_window_->size_constraints().HasMaximumSize()) { |
| gfx::Size max_size = shell_window_->size_constraints().GetMaximumSize(); |
| const int kUnboundedSize = ShellWindow::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(); |
| hints_mask |= GDK_HINT_MAX_SIZE; |
| } |
| if (hints_mask) { |
| gtk_window_set_geometry_hints( |
| window_, |
| GTK_WIDGET(window_), |
| &hints, |
| static_cast<GdkWindowHints>(hints_mask)); |
| } |
| } |