| 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_->OnNativeWindowChanged(); | |
| 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 shell_window_->OnNativeWindowChanged(); | |
| 208 } | |
| 209 | |
| 210 void ShellWindowGtk::Restore() { | |
| 211 if (IsMaximized()) | |
| 212 gtk_window_unmaximize(window_); | |
| 213 else if (IsMinimized()) | |
| 214 gtk_window_deiconify(window_); | |
| 215 shell_window_->OnNativeWindowChanged(); | |
| 216 } | |
| 217 | |
| 218 void ShellWindowGtk::SetBounds(const gfx::Rect& bounds) { | |
| 219 gtk_window_move(window_, bounds.x(), bounds.y()); | |
| 220 gtk_window_util::SetWindowSize(window_, | |
| 221 gfx::Size(bounds.width(), bounds.height())); | |
| 222 } | |
| 223 | |
| 224 void ShellWindowGtk::FlashFrame(bool flash) { | |
| 225 gtk_window_set_urgency_hint(window_, flash); | |
| 226 } | |
| 227 | |
| 228 bool ShellWindowGtk::IsAlwaysOnTop() const { | |
| 229 return false; | |
| 230 } | |
| 231 | |
| 232 void ShellWindowGtk::ActiveWindowChanged(GdkWindow* active_window) { | |
| 233 // Do nothing if we're in the process of closing the browser window. | |
| 234 if (!window_) | |
| 235 return; | |
| 236 | |
| 237 is_active_ = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; | |
| 238 } | |
| 239 | |
| 240 // Callback for the delete event. This event is fired when the user tries to | |
| 241 // close the window (e.g., clicking on the X in the window manager title bar). | |
| 242 gboolean ShellWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget, | |
| 243 GdkEvent* event) { | |
| 244 Close(); | |
| 245 | |
| 246 // Return true to prevent the GTK window from being destroyed. Close will | |
| 247 // destroy it for us. | |
| 248 return TRUE; | |
| 249 } | |
| 250 | |
| 251 gboolean ShellWindowGtk::OnConfigure(GtkWidget* widget, | |
| 252 GdkEventConfigure* event) { | |
| 253 // We update |bounds_| but not |restored_bounds_| here. The latter needs | |
| 254 // to be updated conditionally when the window is non-maximized and non- | |
| 255 // fullscreen, but whether those state updates have been processed yet is | |
| 256 // window-manager specific. We update |restored_bounds_| in the debounced | |
| 257 // handler below, after the window state has been updated. | |
| 258 bounds_.SetRect(event->x, event->y, event->width, event->height); | |
| 259 | |
| 260 // The GdkEventConfigure* we get here doesn't have quite the right | |
| 261 // coordinates though (they're relative to the drawable window area, rather | |
| 262 // than any window manager decorations, if enabled), so we need to call | |
| 263 // gtk_window_get_position() to get the right values. (Otherwise session | |
| 264 // restore, if enabled, will restore windows to incorrect positions.) That's | |
| 265 // a round trip to the X server though, so we set a debounce timer and only | |
| 266 // call it (in OnDebouncedBoundsChanged() below) after we haven't seen a | |
| 267 // reconfigure event in a short while. | |
| 268 // We don't use Reset() because the timer may not yet be running. | |
| 269 // (In that case Stop() is a no-op.) | |
| 270 window_configure_debounce_timer_.Stop(); | |
| 271 window_configure_debounce_timer_.Start(FROM_HERE, | |
| 272 base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), this, | |
| 273 &ShellWindowGtk::OnDebouncedBoundsChanged); | |
| 274 | |
| 275 return FALSE; | |
| 276 } | |
| 277 | |
| 278 void ShellWindowGtk::OnDebouncedBoundsChanged() { | |
| 279 gtk_window_util::UpdateWindowPosition(this, &bounds_, &restored_bounds_); | |
| 280 shell_window_->OnNativeWindowChanged(); | |
| 281 } | |
| 282 | |
| 283 gboolean ShellWindowGtk::OnWindowState(GtkWidget* sender, | |
| 284 GdkEventWindowState* event) { | |
| 285 state_ = event->new_window_state; | |
| 286 | |
| 287 if (content_thinks_its_fullscreen_ && | |
| 288 !(state_ & GDK_WINDOW_STATE_FULLSCREEN)) { | |
| 289 content_thinks_its_fullscreen_ = false; | |
| 290 content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); | |
| 291 if (rvh) | |
| 292 rvh->ExitFullscreen(); | |
| 293 } | |
| 294 | |
| 295 shell_window_->OnNativeWindowChanged(); | |
| 296 return FALSE; | |
| 297 } | |
| 298 | |
| 299 gboolean ShellWindowGtk::OnButtonPress(GtkWidget* widget, | |
| 300 GdkEventButton* event) { | |
| 301 if (draggable_region_ && draggable_region_->contains(event->x, event->y)) { | |
| 302 if (event->button == 1) { | |
| 303 if (GDK_BUTTON_PRESS == event->type) { | |
| 304 if (!suppress_window_raise_) | |
| 305 gdk_window_raise(GTK_WIDGET(widget)->window); | |
| 306 | |
| 307 return gtk_window_util::HandleTitleBarLeftMousePress( | |
| 308 GTK_WINDOW(widget), bounds_, event); | |
| 309 } else if (GDK_2BUTTON_PRESS == event->type) { | |
| 310 bool is_maximized = gdk_window_get_state(GTK_WIDGET(widget)->window) & | |
| 311 GDK_WINDOW_STATE_MAXIMIZED; | |
| 312 if (is_maximized) { | |
| 313 gtk_window_util::UnMaximize(GTK_WINDOW(widget), | |
| 314 bounds_, restored_bounds_); | |
| 315 } else { | |
| 316 gtk_window_maximize(GTK_WINDOW(widget)); | |
| 317 } | |
| 318 return TRUE; | |
| 319 } | |
| 320 } else if (event->button == 2) { | |
| 321 gdk_window_lower(GTK_WIDGET(widget)->window); | |
| 322 return TRUE; | |
| 323 } | |
| 324 } | |
| 325 | |
| 326 return FALSE; | |
| 327 } | |
| 328 | |
| 329 void ShellWindowGtk::SetFullscreen(bool fullscreen) { | |
| 330 content_thinks_its_fullscreen_ = fullscreen; | |
| 331 if (fullscreen) | |
| 332 gtk_window_fullscreen(window_); | |
| 333 else | |
| 334 gtk_window_unfullscreen(window_); | |
| 335 } | |
| 336 | |
| 337 bool ShellWindowGtk::IsFullscreenOrPending() const { | |
| 338 return content_thinks_its_fullscreen_; | |
| 339 } | |
| 340 | |
| 341 void ShellWindowGtk::UpdateWindowIcon() { | |
| 342 Profile* profile = shell_window_->profile(); | |
| 343 gfx::Image app_icon = shell_window_->app_icon(); | |
| 344 if (!app_icon.IsEmpty()) | |
| 345 gtk_util::SetWindowIcon(window_, profile, app_icon.ToGdkPixbuf()); | |
| 346 else | |
| 347 gtk_util::SetWindowIcon(window_, profile); | |
| 348 } | |
| 349 | |
| 350 void ShellWindowGtk::UpdateWindowTitle() { | |
| 351 string16 title = shell_window_->GetTitle(); | |
| 352 gtk_window_set_title(window_, UTF16ToUTF8(title).c_str()); | |
| 353 } | |
| 354 | |
| 355 void ShellWindowGtk::HandleKeyboardEvent( | |
| 356 const content::NativeWebKeyboardEvent& event) { | |
| 357 // No-op. | |
| 358 } | |
| 359 | |
| 360 void ShellWindowGtk::UpdateDraggableRegions( | |
| 361 const std::vector<extensions::DraggableRegion>& regions) { | |
| 362 // Draggable region is not supported for non-frameless window. | |
| 363 if (!frameless_) | |
| 364 return; | |
| 365 | |
| 366 draggable_region_.reset(ShellWindow::RawDraggableRegionsToSkRegion(regions)); | |
| 367 } | |
| 368 | |
| 369 // static | |
| 370 NativeShellWindow* NativeShellWindow::Create( | |
| 371 ShellWindow* shell_window, const ShellWindow::CreateParams& params) { | |
| 372 return new ShellWindowGtk(shell_window, params); | |
| 373 } | |
| OLD | NEW |