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_, ¤t_width, ¤t_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_, ¤t_width, ¤t_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_, ¤t_width, ¤t_height); 676 gint current_x = 0; 677 gint current_y = 0; 678 gdk_window_get_position(gdk_window, ¤t_x, ¤t_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