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