| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/extensions/shell_window_gtk.h" | |
| 6 | |
| 7 #include "base/utf_string_conversions.h" | |
| 8 #include "chrome/browser/profiles/profile.h" | |
| 9 #include "chrome/browser/ui/gtk/extensions/extension_keybinding_registry_gtk.h" | |
| 10 #include "chrome/browser/ui/gtk/gtk_util.h" | |
| 11 #include "chrome/browser/ui/gtk/gtk_window_util.h" | |
| 12 #include "chrome/browser/web_applications/web_app.h" | |
| 13 #include "chrome/common/extensions/extension.h" | |
| 14 #include "content/public/browser/render_view_host.h" | |
| 15 #include "content/public/browser/render_widget_host_view.h" | |
| 16 #include "content/public/browser/web_contents.h" | |
| 17 #include "content/public/browser/web_contents_view.h" | |
| 18 #include "ui/base/x/active_window_watcher_x.h" | |
| 19 #include "ui/gfx/image/image.h" | |
| 20 #include "ui/gfx/rect.h" | |
| 21 | |
| 22 namespace { | |
| 23 | |
| 24 // The timeout in milliseconds before we'll get the true window position with | |
| 25 // gtk_window_get_position() after the last GTK configure-event signal. | |
| 26 const int kDebounceTimeoutMilliseconds = 100; | |
| 27 | |
| 28 } // namespace | |
| 29 | |
| 30 ShellWindowGtk::ShellWindowGtk(ShellWindow* shell_window, | |
| 31 const ShellWindow::CreateParams& params) | |
| 32 : shell_window_(shell_window), | |
| 33 window_(NULL), | |
| 34 state_(GDK_WINDOW_STATE_WITHDRAWN), | |
| 35 is_active_(false), | |
| 36 content_thinks_its_fullscreen_(false), | |
| 37 frameless_(params.frame == ShellWindow::CreateParams::FRAME_NONE) { | |
| 38 window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); | |
| 39 | |
| 40 gfx::NativeView native_view = | |
| 41 web_contents()->GetView()->GetNativeView(); | |
| 42 gtk_container_add(GTK_CONTAINER(window_), native_view); | |
| 43 | |
| 44 if (params.bounds.x() != INT_MIN && params.bounds.y() != INT_MIN) | |
| 45 gtk_window_move(window_, params.bounds.x(), params.bounds.y()); | |
| 46 | |
| 47 // This is done to avoid a WM "feature" where setting the window size to | |
| 48 // the monitor size causes the WM to set the EWMH for full screen mode. | |
| 49 if (frameless_ && | |
| 50 gtk_window_util::BoundsMatchMonitorSize(window_, params.bounds)) { | |
| 51 gtk_window_set_default_size( | |
| 52 window_, params.bounds.width(), params.bounds.height() - 1); | |
| 53 } else { | |
| 54 gtk_window_set_default_size( | |
| 55 window_, params.bounds.width(), params.bounds.height()); | |
| 56 } | |
| 57 | |
| 58 // make sure bounds_ and restored_bounds_ have correct values until we | |
| 59 // get our first configure-event | |
| 60 bounds_ = restored_bounds_ = params.bounds; | |
| 61 gint x, y; | |
| 62 gtk_window_get_position(window_, &x, &y); | |
| 63 bounds_.set_origin(gfx::Point(x, y)); | |
| 64 | |
| 65 // Hide titlebar when {frame: 'none'} specified on ShellWindow. | |
| 66 if (frameless_) | |
| 67 gtk_window_set_decorated(window_, false); | |
| 68 | |
| 69 int min_width = params.minimum_size.width(); | |
| 70 int min_height = params.minimum_size.height(); | |
| 71 int max_width = params.maximum_size.width(); | |
| 72 int max_height = params.maximum_size.height(); | |
| 73 GdkGeometry hints; | |
| 74 int hints_mask = 0; | |
| 75 if (min_width || min_height) { | |
| 76 hints.min_height = min_height; | |
| 77 hints.min_width = min_width; | |
| 78 hints_mask |= GDK_HINT_MIN_SIZE; | |
| 79 } | |
| 80 if (max_width || max_height) { | |
| 81 hints.max_height = max_height ? max_height : G_MAXINT; | |
| 82 hints.max_width = max_width ? max_width : G_MAXINT; | |
| 83 hints_mask |= GDK_HINT_MAX_SIZE; | |
| 84 } | |
| 85 if (hints_mask) { | |
| 86 gtk_window_set_geometry_hints( | |
| 87 window_, | |
| 88 GTK_WIDGET(window_), | |
| 89 &hints, | |
| 90 static_cast<GdkWindowHints>(hints_mask)); | |
| 91 } | |
| 92 | |
| 93 // In some (older) versions of compiz, raising top-level windows when they | |
| 94 // are partially off-screen causes them to get snapped back on screen, not | |
| 95 // always even on the current virtual desktop. If we are running under | |
| 96 // compiz, suppress such raises, as they are not necessary in compiz anyway. | |
| 97 if (ui::GuessWindowManager() == ui::WM_COMPIZ) | |
| 98 suppress_window_raise_ = true; | |
| 99 | |
| 100 gtk_window_set_title(window_, extension()->name().c_str()); | |
| 101 | |
| 102 gtk_window_util::SetWindowCustomClass(window_, | |
| 103 web_app::GetWMClassFromAppName(extension()->name())); | |
| 104 | |
| 105 g_signal_connect(window_, "delete-event", | |
| 106 G_CALLBACK(OnMainWindowDeleteEventThunk), this); | |
| 107 g_signal_connect(window_, "configure-event", | |
| 108 G_CALLBACK(OnConfigureThunk), this); | |
| 109 g_signal_connect(window_, "window-state-event", | |
| 110 G_CALLBACK(OnWindowStateThunk), this); | |
| 111 if (frameless_) { | |
| 112 g_signal_connect(window_, "button-press-event", | |
| 113 G_CALLBACK(OnButtonPressThunk), this); | |
| 114 } | |
| 115 | |
| 116 // Add the keybinding registry. | |
| 117 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryGtk( | |
| 118 shell_window_->profile(), | |
| 119 window_, | |
| 120 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY, | |
| 121 shell_window_)); | |
| 122 | |
| 123 ui::ActiveWindowWatcherX::AddObserver(this); | |
| 124 } | |
| 125 | |
| 126 ShellWindowGtk::~ShellWindowGtk() { | |
| 127 ui::ActiveWindowWatcherX::RemoveObserver(this); | |
| 128 } | |
| 129 | |
| 130 bool ShellWindowGtk::IsActive() const { | |
| 131 if (ui::ActiveWindowWatcherX::WMSupportsActivation()) | |
| 132 return is_active_; | |
| 133 | |
| 134 // This still works even though we don't get the activation notification. | |
| 135 return gtk_window_is_active(window_); | |
| 136 } | |
| 137 | |
| 138 bool ShellWindowGtk::IsMaximized() const { | |
| 139 return (state_ & GDK_WINDOW_STATE_MAXIMIZED); | |
| 140 } | |
| 141 | |
| 142 bool ShellWindowGtk::IsMinimized() const { | |
| 143 return (state_ & GDK_WINDOW_STATE_ICONIFIED); | |
| 144 } | |
| 145 | |
| 146 bool ShellWindowGtk::IsFullscreen() const { | |
| 147 return false; | |
| 148 } | |
| 149 | |
| 150 gfx::NativeWindow ShellWindowGtk::GetNativeWindow() { | |
| 151 return window_; | |
| 152 } | |
| 153 | |
| 154 gfx::Rect ShellWindowGtk::GetRestoredBounds() const { | |
| 155 return restored_bounds_; | |
| 156 } | |
| 157 | |
| 158 gfx::Rect ShellWindowGtk::GetBounds() const { | |
| 159 return bounds_; | |
| 160 } | |
| 161 | |
| 162 void ShellWindowGtk::Show() { | |
| 163 gtk_window_present(window_); | |
| 164 } | |
| 165 | |
| 166 void ShellWindowGtk::ShowInactive() { | |
| 167 gtk_window_set_focus_on_map(window_, false); | |
| 168 gtk_widget_show(GTK_WIDGET(window_)); | |
| 169 } | |
| 170 | |
| 171 void ShellWindowGtk::Hide() { | |
| 172 gtk_widget_hide(GTK_WIDGET(window_)); | |
| 173 } | |
| 174 | |
| 175 void ShellWindowGtk::Close() { | |
| 176 shell_window_->SaveWindowPosition(); | |
| 177 | |
| 178 // Cancel any pending callback from the window configure debounce timer. | |
| 179 window_configure_debounce_timer_.Stop(); | |
| 180 | |
| 181 GtkWidget* window = GTK_WIDGET(window_); | |
| 182 // To help catch bugs in any event handlers that might get fired during the | |
| 183 // destruction, set window_ to NULL before any handlers will run. | |
| 184 window_ = NULL; | |
| 185 | |
| 186 // OnNativeClose does a delete this so no other members should | |
| 187 // be accessed after. gtk_widget_destroy is safe (and must | |
| 188 // be last). | |
| 189 shell_window_->OnNativeClose(); | |
| 190 gtk_widget_destroy(window); | |
| 191 } | |
| 192 | |
| 193 void ShellWindowGtk::Activate() { | |
| 194 gtk_window_present(window_); | |
| 195 } | |
| 196 | |
| 197 void ShellWindowGtk::Deactivate() { | |
| 198 gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_))); | |
| 199 } | |
| 200 | |
| 201 void ShellWindowGtk::Maximize() { | |
| 202 gtk_window_maximize(window_); | |
| 203 } | |
| 204 | |
| 205 void ShellWindowGtk::Minimize() { | |
| 206 gtk_window_iconify(window_); | |
| 207 } | |
| 208 | |
| 209 void ShellWindowGtk::Restore() { | |
| 210 if (IsMaximized()) | |
| 211 gtk_window_unmaximize(window_); | |
| 212 else if (IsMinimized()) | |
| 213 gtk_window_deiconify(window_); | |
| 214 } | |
| 215 | |
| 216 void ShellWindowGtk::SetBounds(const gfx::Rect& bounds) { | |
| 217 gtk_window_move(window_, bounds.x(), bounds.y()); | |
| 218 gtk_window_util::SetWindowSize(window_, | |
| 219 gfx::Size(bounds.width(), bounds.height())); | |
| 220 } | |
| 221 | |
| 222 void ShellWindowGtk::FlashFrame(bool flash) { | |
| 223 gtk_window_set_urgency_hint(window_, flash); | |
| 224 } | |
| 225 | |
| 226 bool ShellWindowGtk::IsAlwaysOnTop() const { | |
| 227 return false; | |
| 228 } | |
| 229 | |
| 230 void ShellWindowGtk::ActiveWindowChanged(GdkWindow* active_window) { | |
| 231 // Do nothing if we're in the process of closing the browser window. | |
| 232 if (!window_) | |
| 233 return; | |
| 234 | |
| 235 is_active_ = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; | |
| 236 } | |
| 237 | |
| 238 // Callback for the delete event. This event is fired when the user tries to | |
| 239 // close the window (e.g., clicking on the X in the window manager title bar). | |
| 240 gboolean ShellWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget, | |
| 241 GdkEvent* event) { | |
| 242 Close(); | |
| 243 | |
| 244 // Return true to prevent the GTK window from being destroyed. Close will | |
| 245 // destroy it for us. | |
| 246 return TRUE; | |
| 247 } | |
| 248 | |
| 249 gboolean ShellWindowGtk::OnConfigure(GtkWidget* widget, | |
| 250 GdkEventConfigure* event) { | |
| 251 // We update |bounds_| but not |restored_bounds_| here. The latter needs | |
| 252 // to be updated conditionally when the window is non-maximized and non- | |
| 253 // fullscreen, but whether those state updates have been processed yet is | |
| 254 // window-manager specific. We update |restored_bounds_| in the debounced | |
| 255 // handler below, after the window state has been updated. | |
| 256 bounds_.SetRect(event->x, event->y, event->width, event->height); | |
| 257 | |
| 258 // The GdkEventConfigure* we get here doesn't have quite the right | |
| 259 // coordinates though (they're relative to the drawable window area, rather | |
| 260 // than any window manager decorations, if enabled), so we need to call | |
| 261 // gtk_window_get_position() to get the right values. (Otherwise session | |
| 262 // restore, if enabled, will restore windows to incorrect positions.) That's | |
| 263 // a round trip to the X server though, so we set a debounce timer and only | |
| 264 // call it (in OnDebouncedBoundsChanged() below) after we haven't seen a | |
| 265 // reconfigure event in a short while. | |
| 266 // We don't use Reset() because the timer may not yet be running. | |
| 267 // (In that case Stop() is a no-op.) | |
| 268 window_configure_debounce_timer_.Stop(); | |
| 269 window_configure_debounce_timer_.Start(FROM_HERE, | |
| 270 base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), this, | |
| 271 &ShellWindowGtk::OnDebouncedBoundsChanged); | |
| 272 | |
| 273 return FALSE; | |
| 274 } | |
| 275 | |
| 276 void ShellWindowGtk::OnDebouncedBoundsChanged() { | |
| 277 gtk_window_util::UpdateWindowPosition(this, &bounds_, &restored_bounds_); | |
| 278 shell_window_->SaveWindowPosition(); | |
| 279 } | |
| 280 | |
| 281 gboolean ShellWindowGtk::OnWindowState(GtkWidget* sender, | |
| 282 GdkEventWindowState* event) { | |
| 283 state_ = event->new_window_state; | |
| 284 | |
| 285 if (content_thinks_its_fullscreen_ && | |
| 286 !(state_ & GDK_WINDOW_STATE_FULLSCREEN)) { | |
| 287 content_thinks_its_fullscreen_ = false; | |
| 288 content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); | |
| 289 if (rvh) | |
| 290 rvh->ExitFullscreen(); | |
| 291 } | |
| 292 | |
| 293 return FALSE; | |
| 294 } | |
| 295 | |
| 296 gboolean ShellWindowGtk::OnButtonPress(GtkWidget* widget, | |
| 297 GdkEventButton* event) { | |
| 298 if (draggable_region_ && draggable_region_->contains(event->x, event->y)) { | |
| 299 if (event->button == 1) { | |
| 300 if (GDK_BUTTON_PRESS == event->type) { | |
| 301 if (!suppress_window_raise_) | |
| 302 gdk_window_raise(GTK_WIDGET(widget)->window); | |
| 303 | |
| 304 return gtk_window_util::HandleTitleBarLeftMousePress( | |
| 305 GTK_WINDOW(widget), bounds_, event); | |
| 306 } else if (GDK_2BUTTON_PRESS == event->type) { | |
| 307 bool is_maximized = gdk_window_get_state(GTK_WIDGET(widget)->window) & | |
| 308 GDK_WINDOW_STATE_MAXIMIZED; | |
| 309 if (is_maximized) { | |
| 310 gtk_window_util::UnMaximize(GTK_WINDOW(widget), | |
| 311 bounds_, restored_bounds_); | |
| 312 } else { | |
| 313 gtk_window_maximize(GTK_WINDOW(widget)); | |
| 314 } | |
| 315 return TRUE; | |
| 316 } | |
| 317 } else if (event->button == 2) { | |
| 318 gdk_window_lower(GTK_WIDGET(widget)->window); | |
| 319 return TRUE; | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 return FALSE; | |
| 324 } | |
| 325 | |
| 326 void ShellWindowGtk::SetFullscreen(bool fullscreen) { | |
| 327 content_thinks_its_fullscreen_ = fullscreen; | |
| 328 if (fullscreen) | |
| 329 gtk_window_fullscreen(window_); | |
| 330 else | |
| 331 gtk_window_unfullscreen(window_); | |
| 332 } | |
| 333 | |
| 334 bool ShellWindowGtk::IsFullscreenOrPending() const { | |
| 335 return content_thinks_its_fullscreen_; | |
| 336 } | |
| 337 | |
| 338 void ShellWindowGtk::UpdateWindowIcon() { | |
| 339 Profile* profile = shell_window_->profile(); | |
| 340 gfx::Image app_icon = shell_window_->app_icon(); | |
| 341 if (!app_icon.IsEmpty()) | |
| 342 gtk_util::SetWindowIcon(window_, profile, app_icon.ToGdkPixbuf()); | |
| 343 else | |
| 344 gtk_util::SetWindowIcon(window_, profile); | |
| 345 } | |
| 346 | |
| 347 void ShellWindowGtk::UpdateWindowTitle() { | |
| 348 string16 title = shell_window_->GetTitle(); | |
| 349 gtk_window_set_title(window_, UTF16ToUTF8(title).c_str()); | |
| 350 } | |
| 351 | |
| 352 void ShellWindowGtk::HandleKeyboardEvent( | |
| 353 const content::NativeWebKeyboardEvent& event) { | |
| 354 // No-op. | |
| 355 } | |
| 356 | |
| 357 void ShellWindowGtk::UpdateDraggableRegions( | |
| 358 const std::vector<extensions::DraggableRegion>& regions) { | |
| 359 // Draggable region is not supported for non-frameless window. | |
| 360 if (!frameless_) | |
| 361 return; | |
| 362 | |
| 363 draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions)); | |
| 364 } | |
| 365 | |
| 366 // static | |
| 367 NativeShellWindow* NativeShellWindow::Create( | |
| 368 ShellWindow* shell_window, const ShellWindow::CreateParams& params) { | |
| 369 return new ShellWindowGtk(shell_window, params); | |
| 370 } | |
| OLD | NEW |