Home | History | Annotate | Download | only in apps
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/gtk/apps/native_app_window_gtk.h"
      6 
      7 #include <gdk/gdkx.h>
      8 #include <vector>
      9 
     10 #include "base/message_loop/message_pump_gtk.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/ui/gtk/extensions/extension_keybinding_registry_gtk.h"
     14 #include "chrome/browser/ui/gtk/gtk_util.h"
     15 #include "chrome/browser/ui/gtk/gtk_window_util.h"
     16 #include "chrome/browser/web_applications/web_app.h"
     17 #include "chrome/common/extensions/extension.h"
     18 #include "content/public/browser/render_view_host.h"
     19 #include "content/public/browser/render_widget_host_view.h"
     20 #include "content/public/browser/web_contents.h"
     21 #include "content/public/browser/web_contents_view.h"
     22 #include "ui/base/x/active_window_watcher_x.h"
     23 #include "ui/gfx/gtk_util.h"
     24 #include "ui/gfx/image/image.h"
     25 #include "ui/gfx/rect.h"
     26 
     27 using apps::ShellWindow;
     28 
     29 namespace {
     30 
     31 // The timeout in milliseconds before we'll get the true window position with
     32 // gtk_window_get_position() after the last GTK configure-event signal.
     33 const int kDebounceTimeoutMilliseconds = 100;
     34 
     35 const char* kAtomsToCache[] = {
     36   "_NET_WM_STATE",
     37   "_NET_WM_STATE_HIDDEN",
     38   NULL
     39 };
     40 
     41 } // namespace
     42 
     43 NativeAppWindowGtk::NativeAppWindowGtk(ShellWindow* shell_window,
     44                                        const ShellWindow::CreateParams& params)
     45     : shell_window_(shell_window),
     46       window_(NULL),
     47       state_(GDK_WINDOW_STATE_WITHDRAWN),
     48       is_active_(false),
     49       content_thinks_its_fullscreen_(false),
     50       frameless_(params.frame == ShellWindow::FRAME_NONE),
     51       frame_cursor_(NULL),
     52       atom_cache_(base::MessagePumpGtk::GetDefaultXDisplay(), kAtomsToCache),
     53       is_x_event_listened_(false) {
     54   window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
     55 
     56   gfx::NativeView native_view =
     57       web_contents()->GetView()->GetNativeView();
     58   gtk_container_add(GTK_CONTAINER(window_), native_view);
     59 
     60   if (params.bounds.x() != INT_MIN && params.bounds.y() != INT_MIN)
     61     gtk_window_move(window_, params.bounds.x(), params.bounds.y());
     62 
     63   // This is done to avoid a WM "feature" where setting the window size to
     64   // the monitor size causes the WM to set the EWMH for full screen mode.
     65   int win_height = params.bounds.height();
     66   if (frameless_ &&
     67       gtk_window_util::BoundsMatchMonitorSize(window_, params.bounds)) {
     68     win_height -= 1;
     69   }
     70   gtk_window_set_default_size(window_, params.bounds.width(), win_height);
     71 
     72   resizable_ = params.resizable;
     73   if (!resizable_) {
     74     // If the window doesn't have a size request when we set resizable to
     75     // false, GTK will shrink the window to 1x1px.
     76     gtk_widget_set_size_request(GTK_WIDGET(window_),
     77         params.bounds.width(), win_height);
     78     gtk_window_set_resizable(window_, FALSE);
     79   }
     80 
     81   // make sure bounds_ and restored_bounds_ have correct values until we
     82   // get our first configure-event
     83   bounds_ = restored_bounds_ = params.bounds;
     84   gint x, y;
     85   gtk_window_get_position(window_, &x, &y);
     86   bounds_.set_origin(gfx::Point(x, y));
     87 
     88   // Hide titlebar when {frame: 'none'} specified on ShellWindow.
     89   if (frameless_)
     90     gtk_window_set_decorated(window_, false);
     91 
     92   int min_width = params.minimum_size.width();
     93   int min_height = params.minimum_size.height();
     94   int max_width = params.maximum_size.width();
     95   int max_height = params.maximum_size.height();
     96   GdkGeometry hints;
     97   int hints_mask = 0;
     98   if (min_width || min_height) {
     99     hints.min_height = min_height;
    100     hints.min_width = min_width;
    101     hints_mask |= GDK_HINT_MIN_SIZE;
    102   }
    103   if (max_width || max_height) {
    104     hints.max_height = max_height ? max_height : G_MAXINT;
    105     hints.max_width = max_width ? max_width : G_MAXINT;
    106     hints_mask |= GDK_HINT_MAX_SIZE;
    107   }
    108   if (hints_mask) {
    109     gtk_window_set_geometry_hints(
    110         window_,
    111         GTK_WIDGET(window_),
    112         &hints,
    113         static_cast<GdkWindowHints>(hints_mask));
    114   }
    115 
    116   // In some (older) versions of compiz, raising top-level windows when they
    117   // are partially off-screen causes them to get snapped back on screen, not
    118   // always even on the current virtual desktop.  If we are running under
    119   // compiz, suppress such raises, as they are not necessary in compiz anyway.
    120   if (ui::GuessWindowManager() == ui::WM_COMPIZ)
    121     suppress_window_raise_ = true;
    122 
    123   gtk_window_set_title(window_, extension()->name().c_str());
    124 
    125   std::string app_name = web_app::GenerateApplicationNameFromExtensionId(
    126       extension()->id());
    127   gtk_window_util::SetWindowCustomClass(window_,
    128       web_app::GetWMClassFromAppName(app_name));
    129 
    130   g_signal_connect(window_, "delete-event",
    131                    G_CALLBACK(OnMainWindowDeleteEventThunk), this);
    132   g_signal_connect(window_, "configure-event",
    133                    G_CALLBACK(OnConfigureThunk), this);
    134   g_signal_connect(window_, "window-state-event",
    135                    G_CALLBACK(OnWindowStateThunk), this);
    136   if (frameless_) {
    137     g_signal_connect(window_, "button-press-event",
    138                      G_CALLBACK(OnButtonPressThunk), this);
    139     g_signal_connect(window_, "motion-notify-event",
    140                      G_CALLBACK(OnMouseMoveEventThunk), this);
    141   }
    142 
    143   // If _NET_WM_STATE_HIDDEN is in _NET_SUPPORTED, listen for XEvent to work
    144   // around GTK+ not reporting minimization state changes. See comment in the
    145   // |OnXEvent|.
    146   std::vector< ::Atom> supported_atoms;
    147   if (ui::GetAtomArrayProperty(ui::GetX11RootWindow(),
    148                                "_NET_SUPPORTED",
    149                                &supported_atoms)) {
    150     if (std::find(supported_atoms.begin(),
    151                   supported_atoms.end(),
    152                   atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN")) !=
    153         supported_atoms.end()) {
    154       GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(window_));
    155       gdk_window_add_filter(window,
    156                             &NativeAppWindowGtk::OnXEventThunk,
    157                             this);
    158       is_x_event_listened_ = true;
    159     }
    160   }
    161 
    162   // Add the keybinding registry.
    163   extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryGtk(
    164       shell_window_->profile(),
    165       window_,
    166       extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY,
    167       shell_window_));
    168 
    169   ui::ActiveWindowWatcherX::AddObserver(this);
    170 }
    171 
    172 NativeAppWindowGtk::~NativeAppWindowGtk() {
    173   ui::ActiveWindowWatcherX::RemoveObserver(this);
    174   if (is_x_event_listened_) {
    175     gdk_window_remove_filter(NULL,
    176                              &NativeAppWindowGtk::OnXEventThunk,
    177                              this);
    178   }
    179 }
    180 
    181 bool NativeAppWindowGtk::IsActive() const {
    182   if (ui::ActiveWindowWatcherX::WMSupportsActivation())
    183     return is_active_;
    184 
    185   // This still works even though we don't get the activation notification.
    186   return gtk_window_is_active(window_);
    187 }
    188 
    189 bool NativeAppWindowGtk::IsMaximized() const {
    190   return (state_ & GDK_WINDOW_STATE_MAXIMIZED);
    191 }
    192 
    193 bool NativeAppWindowGtk::IsMinimized() const {
    194   return (state_ & GDK_WINDOW_STATE_ICONIFIED);
    195 }
    196 
    197 bool NativeAppWindowGtk::IsFullscreen() const {
    198   return (state_ & GDK_WINDOW_STATE_FULLSCREEN);
    199 }
    200 
    201 gfx::NativeWindow NativeAppWindowGtk::GetNativeWindow() {
    202   return window_;
    203 }
    204 
    205 gfx::Rect NativeAppWindowGtk::GetRestoredBounds() const {
    206   gfx::Rect window_bounds = restored_bounds_;
    207   window_bounds.Inset(-GetFrameInsets());
    208   return window_bounds;
    209 }
    210 
    211 ui::WindowShowState NativeAppWindowGtk::GetRestoredState() const {
    212   if (IsMaximized())
    213     return ui::SHOW_STATE_MAXIMIZED;
    214   return ui::SHOW_STATE_NORMAL;
    215 }
    216 
    217 gfx::Rect NativeAppWindowGtk::GetBounds() const {
    218   gfx::Rect window_bounds = bounds_;
    219   window_bounds.Inset(-GetFrameInsets());
    220   return window_bounds;
    221 }
    222 
    223 void NativeAppWindowGtk::Show() {
    224   gtk_window_present(window_);
    225 }
    226 
    227 void NativeAppWindowGtk::ShowInactive() {
    228   gtk_window_set_focus_on_map(window_, false);
    229   gtk_widget_show(GTK_WIDGET(window_));
    230 }
    231 
    232 void NativeAppWindowGtk::Hide() {
    233   gtk_widget_hide(GTK_WIDGET(window_));
    234 }
    235 
    236 void NativeAppWindowGtk::Close() {
    237   shell_window_->OnNativeWindowChanged();
    238 
    239   // Cancel any pending callback from the window configure debounce timer.
    240   window_configure_debounce_timer_.Stop();
    241 
    242   GtkWidget* window = GTK_WIDGET(window_);
    243   // To help catch bugs in any event handlers that might get fired during the
    244   // destruction, set window_ to NULL before any handlers will run.
    245   window_ = NULL;
    246 
    247   // OnNativeClose does a delete this so no other members should
    248   // be accessed after. gtk_widget_destroy is safe (and must
    249   // be last).
    250   shell_window_->OnNativeClose();
    251   gtk_widget_destroy(window);
    252 }
    253 
    254 void NativeAppWindowGtk::Activate() {
    255   gtk_window_present(window_);
    256 }
    257 
    258 void NativeAppWindowGtk::Deactivate() {
    259   gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_)));
    260 }
    261 
    262 void NativeAppWindowGtk::Maximize() {
    263   // Represent the window first in order to keep the maximization behavior
    264   // consistency with Windows platform. Otherwise the window will be hidden if
    265   // it has been minimized.
    266   gtk_window_present(window_);
    267   gtk_window_maximize(window_);
    268 }
    269 
    270 void NativeAppWindowGtk::Minimize() {
    271   gtk_window_iconify(window_);
    272 }
    273 
    274 void NativeAppWindowGtk::Restore() {
    275   if (IsMaximized())
    276     gtk_window_unmaximize(window_);
    277   else if (IsMinimized())
    278     gtk_window_deiconify(window_);
    279 
    280   // Represent the window to keep restoration behavior consistency with Windows
    281   // platform.
    282   // TODO(zhchbin): verify whether we need this until http://crbug.com/261013 is
    283   // fixed.
    284   gtk_window_present(window_);
    285 }
    286 
    287 void NativeAppWindowGtk::SetBounds(const gfx::Rect& bounds) {
    288   gfx::Rect content_bounds = bounds;
    289   content_bounds.Inset(GetFrameInsets());
    290   gtk_window_move(window_, content_bounds.x(), content_bounds.y());
    291   if (!resizable_) {
    292     if (frameless_ &&
    293         gtk_window_util::BoundsMatchMonitorSize(window_, content_bounds)) {
    294       content_bounds.set_height(content_bounds.height() - 1);
    295     }
    296     // TODO(jeremya): set_size_request doesn't honor min/max size, so the
    297     // bounds should be constrained manually.
    298     gtk_widget_set_size_request(GTK_WIDGET(window_),
    299         content_bounds.width(), content_bounds.height());
    300   } else {
    301     gtk_window_util::SetWindowSize(window_,
    302         gfx::Size(bounds.width(), bounds.height()));
    303   }
    304 }
    305 
    306 GdkFilterReturn NativeAppWindowGtk::OnXEvent(GdkXEvent* gdk_x_event,
    307                                              GdkEvent* gdk_event) {
    308   // Work around GTK+ not reporting minimization state changes. Listen
    309   // for _NET_WM_STATE property changes and use _NET_WM_STATE_HIDDEN's
    310   // presence to set or clear the iconified bit if _NET_WM_STATE_HIDDEN
    311   // is supported. http://crbug.com/162794.
    312   XEvent* x_event = static_cast<XEvent*>(gdk_x_event);
    313   std::vector< ::Atom> atom_list;
    314 
    315   if (x_event->type == PropertyNotify &&
    316       x_event->xproperty.atom == atom_cache_.GetAtom("_NET_WM_STATE") &&
    317       ui::GetAtomArrayProperty(GDK_WINDOW_XWINDOW(GTK_WIDGET(window_)->window),
    318                                "_NET_WM_STATE",
    319                                &atom_list)) {
    320     std::vector< ::Atom>::iterator it =
    321         std::find(atom_list.begin(),
    322                   atom_list.end(),
    323                   atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN"));
    324     state_ = (it != atom_list.end()) ? GDK_WINDOW_STATE_ICONIFIED :
    325         static_cast<GdkWindowState>(state_ & ~GDK_WINDOW_STATE_ICONIFIED);
    326 
    327     shell_window_->OnNativeWindowChanged();
    328   }
    329 
    330   return GDK_FILTER_CONTINUE;
    331 }
    332 
    333 void NativeAppWindowGtk::FlashFrame(bool flash) {
    334   gtk_window_set_urgency_hint(window_, flash);
    335 }
    336 
    337 bool NativeAppWindowGtk::IsAlwaysOnTop() const {
    338   return false;
    339 }
    340 
    341 void NativeAppWindowGtk::RenderViewHostChanged() {
    342   web_contents()->GetView()->Focus();
    343 }
    344 
    345 gfx::Insets NativeAppWindowGtk::GetFrameInsets() const {
    346   if (frameless_)
    347     return gfx::Insets();
    348   GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
    349   if (!gdk_window)
    350     return gfx::Insets();
    351 
    352   gint current_width = 0;
    353   gint current_height = 0;
    354   gtk_window_get_size(window_, &current_width, &current_height);
    355   gint current_x = 0;
    356   gint current_y = 0;
    357   gdk_window_get_position(gdk_window, &current_x, &current_y);
    358   GdkRectangle rect_with_decorations = {0};
    359   gdk_window_get_frame_extents(gdk_window,
    360                                &rect_with_decorations);
    361 
    362   int left_inset = current_x - rect_with_decorations.x;
    363   int top_inset = current_y - rect_with_decorations.y;
    364   return gfx::Insets(
    365       top_inset,
    366       left_inset,
    367       rect_with_decorations.height - current_height - top_inset,
    368       rect_with_decorations.width - current_width - left_inset);
    369 }
    370 
    371 gfx::NativeView NativeAppWindowGtk::GetHostView() const {
    372   NOTIMPLEMENTED();
    373   return NULL;
    374 }
    375 
    376 gfx::Point NativeAppWindowGtk::GetDialogPosition(const gfx::Size& size) {
    377   gint current_width = 0;
    378   gint current_height = 0;
    379   gtk_window_get_size(window_, &current_width, &current_height);
    380   return gfx::Point(current_width / 2 - size.width() / 2,
    381                     current_height / 2 - size.height() / 2);
    382 }
    383 
    384 void NativeAppWindowGtk::AddObserver(
    385     web_modal::WebContentsModalDialogHostObserver* observer) {
    386   observer_list_.AddObserver(observer);
    387 }
    388 
    389 void NativeAppWindowGtk::RemoveObserver(
    390     web_modal::WebContentsModalDialogHostObserver* observer) {
    391   observer_list_.RemoveObserver(observer);
    392 }
    393 
    394 void NativeAppWindowGtk::ActiveWindowChanged(GdkWindow* active_window) {
    395   // Do nothing if we're in the process of closing the browser window.
    396   if (!window_)
    397     return;
    398 
    399   is_active_ = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window;
    400   if (is_active_)
    401     shell_window_->OnNativeWindowActivated();
    402 }
    403 
    404 // Callback for the delete event.  This event is fired when the user tries to
    405 // close the window (e.g., clicking on the X in the window manager title bar).
    406 gboolean NativeAppWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget,
    407                                                      GdkEvent* event) {
    408   Close();
    409 
    410   // Return true to prevent the GTK window from being destroyed.  Close will
    411   // destroy it for us.
    412   return TRUE;
    413 }
    414 
    415 gboolean NativeAppWindowGtk::OnConfigure(GtkWidget* widget,
    416                                          GdkEventConfigure* event) {
    417   // We update |bounds_| but not |restored_bounds_| here.  The latter needs
    418   // to be updated conditionally when the window is non-maximized and non-
    419   // fullscreen, but whether those state updates have been processed yet is
    420   // window-manager specific.  We update |restored_bounds_| in the debounced
    421   // handler below, after the window state has been updated.
    422   bounds_.SetRect(event->x, event->y, event->width, event->height);
    423 
    424   // The GdkEventConfigure* we get here doesn't have quite the right
    425   // coordinates though (they're relative to the drawable window area, rather
    426   // than any window manager decorations, if enabled), so we need to call
    427   // gtk_window_get_position() to get the right values. (Otherwise session
    428   // restore, if enabled, will restore windows to incorrect positions.) That's
    429   // a round trip to the X server though, so we set a debounce timer and only
    430   // call it (in OnConfigureDebounced() below) after we haven't seen a
    431   // reconfigure event in a short while.
    432   // We don't use Reset() because the timer may not yet be running.
    433   // (In that case Stop() is a no-op.)
    434   window_configure_debounce_timer_.Stop();
    435   window_configure_debounce_timer_.Start(FROM_HERE,
    436       base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), this,
    437       &NativeAppWindowGtk::OnConfigureDebounced);
    438 
    439   return FALSE;
    440 }
    441 
    442 void NativeAppWindowGtk::OnConfigureDebounced() {
    443   gtk_window_util::UpdateWindowPosition(this, &bounds_, &restored_bounds_);
    444   shell_window_->OnNativeWindowChanged();
    445 
    446   FOR_EACH_OBSERVER(web_modal::WebContentsModalDialogHostObserver,
    447                     observer_list_,
    448                     OnPositionRequiresUpdate());
    449 
    450   // Fullscreen of non-resizable windows requires them to be made resizable
    451   // first. After that takes effect and OnConfigure is called we transition
    452   // to fullscreen.
    453   if (!IsFullscreen() && IsFullscreenOrPending()) {
    454     gtk_window_fullscreen(window_);
    455   }
    456 }
    457 
    458 gboolean NativeAppWindowGtk::OnWindowState(GtkWidget* sender,
    459                                            GdkEventWindowState* event) {
    460   state_ = event->new_window_state;
    461 
    462   if (content_thinks_its_fullscreen_ &&
    463       !(state_ & GDK_WINDOW_STATE_FULLSCREEN)) {
    464     content_thinks_its_fullscreen_ = false;
    465     content::RenderViewHost* rvh = web_contents()->GetRenderViewHost();
    466     if (rvh)
    467       rvh->ExitFullscreen();
    468   }
    469 
    470   return FALSE;
    471 }
    472 
    473 bool NativeAppWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) {
    474   if (!frameless_)
    475     return false;
    476 
    477   if (IsMaximized() || IsFullscreen())
    478     return false;
    479 
    480   return gtk_window_util::GetWindowEdge(bounds_.size(), 0, x, y, edge);
    481 }
    482 
    483 gboolean NativeAppWindowGtk::OnMouseMoveEvent(GtkWidget* widget,
    484                                               GdkEventMotion* event) {
    485   if (!frameless_) {
    486     // Reset the cursor.
    487     if (frame_cursor_) {
    488       frame_cursor_ = NULL;
    489       gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL);
    490     }
    491     return FALSE;
    492   }
    493 
    494   if (!resizable_)
    495     return FALSE;
    496 
    497   // Update the cursor if we're on the custom frame border.
    498   GdkWindowEdge edge;
    499   bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x),
    500                                     static_cast<int>(event->y), &edge);
    501   GdkCursorType new_cursor = GDK_LAST_CURSOR;
    502   if (has_hit_edge)
    503     new_cursor = gtk_window_util::GdkWindowEdgeToGdkCursorType(edge);
    504 
    505   GdkCursorType last_cursor = GDK_LAST_CURSOR;
    506   if (frame_cursor_)
    507     last_cursor = frame_cursor_->type;
    508 
    509   if (last_cursor != new_cursor) {
    510     frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL;
    511     gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)),
    512                           frame_cursor_);
    513   }
    514   return FALSE;
    515 }
    516 
    517 gboolean NativeAppWindowGtk::OnButtonPress(GtkWidget* widget,
    518                                            GdkEventButton* event) {
    519   DCHECK(frameless_);
    520   // Make the button press coordinate relative to the browser window.
    521   int win_x, win_y;
    522   GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
    523   gdk_window_get_origin(gdk_window, &win_x, &win_y);
    524 
    525   GdkWindowEdge edge;
    526   gfx::Point point(static_cast<int>(event->x_root - win_x),
    527                    static_cast<int>(event->y_root - win_y));
    528   bool has_hit_edge = resizable_ && GetWindowEdge(point.x(), point.y(), &edge);
    529   bool has_hit_titlebar =
    530       draggable_region_ && draggable_region_->contains(event->x, event->y);
    531 
    532   if (event->button == 1) {
    533     if (GDK_BUTTON_PRESS == event->type) {
    534       // Raise the window after a click on either the titlebar or the border to
    535       // match the behavior of most window managers, unless that behavior has
    536       // been suppressed.
    537       if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_)
    538         gdk_window_raise(GTK_WIDGET(widget)->window);
    539 
    540       if (has_hit_edge) {
    541         gtk_window_begin_resize_drag(window_, edge, event->button,
    542                                      static_cast<gint>(event->x_root),
    543                                      static_cast<gint>(event->y_root),
    544                                      event->time);
    545         return TRUE;
    546       } else if (has_hit_titlebar) {
    547         return gtk_window_util::HandleTitleBarLeftMousePress(
    548             window_, bounds_, event);
    549       }
    550     } else if (GDK_2BUTTON_PRESS == event->type) {
    551       if (has_hit_titlebar && resizable_) {
    552         // Maximize/restore on double click.
    553         if (IsMaximized()) {
    554           gtk_window_util::UnMaximize(GTK_WINDOW(widget),
    555               bounds_, restored_bounds_);
    556         } else {
    557           gtk_window_maximize(window_);
    558         }
    559         return TRUE;
    560       }
    561     }
    562   } else if (event->button == 2) {
    563     if (has_hit_titlebar || has_hit_edge)
    564       gdk_window_lower(gdk_window);
    565     return TRUE;
    566   }
    567 
    568   return FALSE;
    569 }
    570 
    571 void NativeAppWindowGtk::SetFullscreen(bool fullscreen) {
    572   content_thinks_its_fullscreen_ = fullscreen;
    573   if (fullscreen){
    574     if (resizable_) {
    575       gtk_window_fullscreen(window_);
    576     } else {
    577       // We must first make the window resizable. That won't take effect
    578       // immediately, so OnConfigureDebounced completes the fullscreen call.
    579       gtk_window_set_resizable(window_, TRUE);
    580     }
    581   } else {
    582     gtk_window_unfullscreen(window_);
    583     if (!resizable_)
    584       gtk_window_set_resizable(window_, FALSE);
    585   }
    586 }
    587 
    588 bool NativeAppWindowGtk::IsFullscreenOrPending() const {
    589   return content_thinks_its_fullscreen_;
    590 }
    591 
    592 bool NativeAppWindowGtk::IsDetached() const {
    593   return false;
    594 }
    595 
    596 void NativeAppWindowGtk::UpdateWindowIcon() {
    597   Profile* profile = shell_window_->profile();
    598   gfx::Image app_icon = shell_window_->app_icon();
    599   if (!app_icon.IsEmpty())
    600     gtk_util::SetWindowIcon(window_, profile, app_icon.ToGdkPixbuf());
    601   else
    602     gtk_util::SetWindowIcon(window_, profile);
    603 }
    604 
    605 void NativeAppWindowGtk::UpdateWindowTitle() {
    606   string16 title = shell_window_->GetTitle();
    607   gtk_window_set_title(window_, UTF16ToUTF8(title).c_str());
    608 }
    609 
    610 void NativeAppWindowGtk::HandleKeyboardEvent(
    611     const content::NativeWebKeyboardEvent& event) {
    612   // No-op.
    613 }
    614 
    615 void NativeAppWindowGtk::UpdateDraggableRegions(
    616     const std::vector<extensions::DraggableRegion>& regions) {
    617   // Draggable region is not supported for non-frameless window.
    618   if (!frameless_)
    619     return;
    620 
    621   draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions));
    622 }
    623