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 |