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