[email protected] | 534b163 | 2012-08-14 18:54:48 | [diff] [blame] | 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/gtk_window_util.h" |
| 6 | |
| 7 | #include <dlfcn.h> |
| 8 | #include "content/public/browser/render_view_host.h" |
| 9 | #include "content/public/browser/web_contents.h" |
[email protected] | f3615f0 | 2013-02-26 06:09:06 | [diff] [blame] | 10 | #include "content/public/browser/web_contents_view.h" |
[email protected] | 5f39adc | 2013-05-23 11:50:46 | [diff] [blame] | 11 | #include "ui/base/base_window.h" |
[email protected] | 534b163 | 2012-08-14 18:54:48 | [diff] [blame] | 12 | |
| 13 | using content::RenderWidgetHost; |
| 14 | using content::WebContents; |
| 15 | |
| 16 | namespace gtk_window_util { |
| 17 | |
[email protected] | 736e9e9 | 2013-02-26 04:43:21 | [diff] [blame] | 18 | const int kFrameBorderThickness = 4; |
| 19 | const int kResizeAreaCornerSize = 16; |
| 20 | |
[email protected] | e3fcf7e | 2012-08-21 07:04:44 | [diff] [blame] | 21 | // Keep track of the last click time and the last click position so we can |
| 22 | // filter out extra GDK_BUTTON_PRESS events when a double click happens. |
| 23 | static guint32 last_click_time; |
| 24 | static int last_click_x; |
| 25 | static int last_click_y; |
| 26 | |
[email protected] | 534b163 | 2012-08-14 18:54:48 | [diff] [blame] | 27 | // Performs Cut/Copy/Paste operation on the |window|. |
| 28 | // If the current render view is focused, then just call the specified |method| |
| 29 | // against the current render view host, otherwise emit the specified |signal| |
| 30 | // against the focused widget. |
| 31 | // TODO(suzhe): This approach does not work for plugins. |
| 32 | void DoCutCopyPaste(GtkWindow* window, |
| 33 | WebContents* web_contents, |
| 34 | void (RenderWidgetHost::*method)(), |
| 35 | const char* signal) { |
| 36 | GtkWidget* widget = gtk_window_get_focus(window); |
| 37 | if (widget == NULL) |
| 38 | return; // Do nothing if no focused widget. |
| 39 | |
[email protected] | f3615f0 | 2013-02-26 06:09:06 | [diff] [blame] | 40 | if (web_contents && |
| 41 | widget == web_contents->GetView()->GetContentNativeView()) { |
[email protected] | 534b163 | 2012-08-14 18:54:48 | [diff] [blame] | 42 | (web_contents->GetRenderViewHost()->*method)(); |
| 43 | } else { |
| 44 | guint id; |
| 45 | if ((id = g_signal_lookup(signal, G_OBJECT_TYPE(widget))) != 0) |
| 46 | g_signal_emit(widget, id, 0); |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | void DoCut(GtkWindow* window, WebContents* web_contents) { |
| 51 | DoCutCopyPaste(window, web_contents, |
| 52 | &RenderWidgetHost::Cut, "cut-clipboard"); |
| 53 | } |
| 54 | |
| 55 | void DoCopy(GtkWindow* window, WebContents* web_contents) { |
| 56 | DoCutCopyPaste(window, web_contents, |
| 57 | &RenderWidgetHost::Copy, "copy-clipboard"); |
| 58 | } |
| 59 | |
| 60 | void DoPaste(GtkWindow* window, WebContents* web_contents) { |
| 61 | DoCutCopyPaste(window, web_contents, |
| 62 | &RenderWidgetHost::Paste, "paste-clipboard"); |
| 63 | } |
| 64 | |
| 65 | // Ubuntu patches their version of GTK+ so that there is always a |
| 66 | // gripper in the bottom right corner of the window. We dynamically |
| 67 | // look up this symbol because it's a non-standard Ubuntu extension to |
| 68 | // GTK+. We always need to disable this feature since we can't |
| 69 | // communicate this to WebKit easily. |
| 70 | typedef void (*gtk_window_set_has_resize_grip_func)(GtkWindow*, gboolean); |
| 71 | gtk_window_set_has_resize_grip_func gtk_window_set_has_resize_grip_sym; |
| 72 | |
| 73 | void DisableResizeGrip(GtkWindow* window) { |
| 74 | static bool resize_grip_looked_up = false; |
| 75 | if (!resize_grip_looked_up) { |
| 76 | resize_grip_looked_up = true; |
| 77 | gtk_window_set_has_resize_grip_sym = |
| 78 | reinterpret_cast<gtk_window_set_has_resize_grip_func>( |
| 79 | dlsym(NULL, "gtk_window_set_has_resize_grip")); |
| 80 | } |
| 81 | if (gtk_window_set_has_resize_grip_sym) |
| 82 | gtk_window_set_has_resize_grip_sym(window, FALSE); |
| 83 | } |
| 84 | |
| 85 | GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) { |
| 86 | switch (edge) { |
| 87 | case GDK_WINDOW_EDGE_NORTH_WEST: |
| 88 | return GDK_TOP_LEFT_CORNER; |
| 89 | case GDK_WINDOW_EDGE_NORTH: |
| 90 | return GDK_TOP_SIDE; |
| 91 | case GDK_WINDOW_EDGE_NORTH_EAST: |
| 92 | return GDK_TOP_RIGHT_CORNER; |
| 93 | case GDK_WINDOW_EDGE_WEST: |
| 94 | return GDK_LEFT_SIDE; |
| 95 | case GDK_WINDOW_EDGE_EAST: |
| 96 | return GDK_RIGHT_SIDE; |
| 97 | case GDK_WINDOW_EDGE_SOUTH_WEST: |
| 98 | return GDK_BOTTOM_LEFT_CORNER; |
| 99 | case GDK_WINDOW_EDGE_SOUTH: |
| 100 | return GDK_BOTTOM_SIDE; |
| 101 | case GDK_WINDOW_EDGE_SOUTH_EAST: |
| 102 | return GDK_BOTTOM_RIGHT_CORNER; |
| 103 | default: |
| 104 | NOTREACHED(); |
| 105 | } |
| 106 | return GDK_LAST_CURSOR; |
| 107 | } |
| 108 | |
[email protected] | e3fcf7e | 2012-08-21 07:04:44 | [diff] [blame] | 109 | bool BoundsMatchMonitorSize(GtkWindow* window, gfx::Rect bounds) { |
| 110 | // A screen can be composed of multiple monitors. |
| 111 | GdkScreen* screen = gtk_window_get_screen(window); |
[email protected] | e3fcf7e | 2012-08-21 07:04:44 | [diff] [blame] | 112 | GdkRectangle monitor_size; |
[email protected] | b18bbebd | 2012-08-29 03:27:18 | [diff] [blame] | 113 | |
| 114 | if (gtk_widget_get_realized(GTK_WIDGET(window))) { |
| 115 | // |window| has been realized. |
| 116 | gint monitor_num = gdk_screen_get_monitor_at_window(screen, |
| 117 | gtk_widget_get_window(GTK_WIDGET(window))); |
| 118 | gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor_size); |
| 119 | return bounds.size() == gfx::Size(monitor_size.width, monitor_size.height); |
| 120 | } |
| 121 | |
| 122 | // Make sure the window doesn't match any monitor size. We compare against |
| 123 | // all monitors because we don't know which monitor the window is going to |
| 124 | // open on before window realized. |
| 125 | gint num_monitors = gdk_screen_get_n_monitors(screen); |
| 126 | for (gint i = 0; i < num_monitors; ++i) { |
| 127 | GdkRectangle monitor_size; |
| 128 | gdk_screen_get_monitor_geometry(screen, i, &monitor_size); |
| 129 | if (bounds.size() == gfx::Size(monitor_size.width, monitor_size.height)) |
| 130 | return true; |
| 131 | } |
| 132 | return false; |
[email protected] | e3fcf7e | 2012-08-21 07:04:44 | [diff] [blame] | 133 | } |
| 134 | |
| 135 | bool HandleTitleBarLeftMousePress( |
| 136 | GtkWindow* window, |
| 137 | const gfx::Rect& bounds, |
| 138 | GdkEventButton* event) { |
| 139 | // We want to start a move when the user single clicks, but not start a |
| 140 | // move when the user double clicks. However, a double click sends the |
| 141 | // following GDK events: GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE, |
| 142 | // GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE. If we |
| 143 | // start a gtk_window_begin_move_drag on the second GDK_BUTTON_PRESS, |
| 144 | // the call to gtk_window_maximize fails. To work around this, we |
| 145 | // keep track of the last click and if it's going to be a double click, |
| 146 | // we don't call gtk_window_begin_move_drag. |
| 147 | DCHECK(event->type == GDK_BUTTON_PRESS); |
| 148 | DCHECK(event->button == 1); |
| 149 | |
| 150 | static GtkSettings* settings = gtk_settings_get_default(); |
| 151 | gint double_click_time = 250; |
| 152 | gint double_click_distance = 5; |
| 153 | g_object_get(G_OBJECT(settings), |
| 154 | "gtk-double-click-time", &double_click_time, |
| 155 | "gtk-double-click-distance", &double_click_distance, |
| 156 | NULL); |
| 157 | |
| 158 | guint32 click_time = event->time - last_click_time; |
| 159 | int click_move_x = abs(event->x - last_click_x); |
| 160 | int click_move_y = abs(event->y - last_click_y); |
| 161 | |
| 162 | last_click_time = event->time; |
| 163 | last_click_x = static_cast<int>(event->x); |
| 164 | last_click_y = static_cast<int>(event->y); |
| 165 | |
| 166 | if (click_time > static_cast<guint32>(double_click_time) || |
| 167 | click_move_x > double_click_distance || |
| 168 | click_move_y > double_click_distance) { |
| 169 | // Ignore drag requests if the window is the size of the screen. |
| 170 | // We do this to avoid triggering fullscreen mode in metacity |
| 171 | // (without the --no-force-fullscreen flag) and in compiz (with |
| 172 | // Legacy Fullscreen Mode enabled). |
| 173 | if (!BoundsMatchMonitorSize(window, bounds)) { |
| 174 | gtk_window_begin_move_drag(window, event->button, |
| 175 | static_cast<gint>(event->x_root), |
| 176 | static_cast<gint>(event->y_root), |
| 177 | event->time); |
| 178 | } |
| 179 | return TRUE; |
| 180 | } |
| 181 | return FALSE; |
| 182 | } |
| 183 | |
| 184 | void UnMaximize(GtkWindow* window, |
| 185 | const gfx::Rect& bounds, |
| 186 | const gfx::Rect& restored_bounds) { |
| 187 | gtk_window_unmaximize(window); |
| 188 | |
| 189 | // It can happen that you end up with a window whose restore size is the same |
| 190 | // as the size of the screen, so unmaximizing it merely remaximizes it due to |
| 191 | // the same WM feature that SetWindowSize() works around. We try to detect |
| 192 | // this and resize the window to work around the issue. |
| 193 | if (bounds.size() == restored_bounds.size()) |
| 194 | gtk_window_resize(window, bounds.width(), bounds.height() - 1); |
| 195 | } |
| 196 | |
[email protected] | ea5b458 | 2012-08-23 03:12:51 | [diff] [blame] | 197 | void SetWindowCustomClass(GtkWindow* window, const std::string& wmclass) { |
| 198 | gtk_window_set_wmclass(window, |
| 199 | wmclass.c_str(), |
| 200 | gdk_get_program_class()); |
| 201 | |
| 202 | // Set WM_WINDOW_ROLE for session management purposes. |
| 203 | // See http://tronche.com/gui/x/icccm/sec-5.html . |
| 204 | gtk_window_set_role(window, wmclass.c_str()); |
| 205 | } |
| 206 | |
[email protected] | b18bbebd | 2012-08-29 03:27:18 | [diff] [blame] | 207 | void SetWindowSize(GtkWindow* window, const gfx::Size& size) { |
| 208 | gfx::Size new_size = size; |
| 209 | gint current_width = 0; |
| 210 | gint current_height = 0; |
| 211 | gtk_window_get_size(window, ¤t_width, ¤t_height); |
| 212 | GdkRectangle size_with_decorations = {0}; |
| 213 | GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window)); |
| 214 | if (gdk_window) { |
| 215 | gdk_window_get_frame_extents(gdk_window, |
| 216 | &size_with_decorations); |
| 217 | } |
| 218 | |
| 219 | if (current_width == size_with_decorations.width && |
| 220 | current_height == size_with_decorations.height) { |
| 221 | // Make sure the window doesn't match any monitor size. We compare against |
| 222 | // all monitors because we don't know which monitor the window is going to |
| 223 | // open on (the WM decides that). |
| 224 | GdkScreen* screen = gtk_window_get_screen(window); |
| 225 | gint num_monitors = gdk_screen_get_n_monitors(screen); |
| 226 | for (gint i = 0; i < num_monitors; ++i) { |
| 227 | GdkRectangle monitor_size; |
| 228 | gdk_screen_get_monitor_geometry(screen, i, &monitor_size); |
| 229 | if (gfx::Size(monitor_size.width, monitor_size.height) == size) { |
| 230 | gtk_window_resize(window, size.width(), size.height() - 1); |
| 231 | return; |
| 232 | } |
| 233 | } |
| 234 | } else { |
| 235 | // gtk_window_resize is the size of the window not including decorations, |
| 236 | // but we are given the |size| including window decorations. |
| 237 | if (size_with_decorations.width > current_width) { |
| 238 | new_size.set_width(size.width() - size_with_decorations.width + |
| 239 | current_width); |
| 240 | } |
| 241 | if (size_with_decorations.height > current_height) { |
| 242 | new_size.set_height(size.height() - size_with_decorations.height + |
| 243 | current_height); |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | gtk_window_resize(window, new_size.width(), new_size.height()); |
| 248 | } |
| 249 | |
[email protected] | 5f39adc | 2013-05-23 11:50:46 | [diff] [blame] | 250 | void UpdateWindowPosition(ui::BaseWindow* window, |
[email protected] | 939532d0 | 2012-08-31 23:37:33 | [diff] [blame] | 251 | gfx::Rect* bounds, |
| 252 | gfx::Rect* restored_bounds) { |
| 253 | gint x, y; |
| 254 | gtk_window_get_position(window->GetNativeWindow(), &x, &y); |
| 255 | (*bounds).set_origin(gfx::Point(x, y)); |
| 256 | if (!window->IsFullscreen() && !window->IsMaximized()) |
| 257 | *restored_bounds = *bounds; |
| 258 | } |
| 259 | |
[email protected] | 736e9e9 | 2013-02-26 04:43:21 | [diff] [blame] | 260 | bool GetWindowEdge(const gfx::Size& window_size, |
| 261 | int top_edge_inset, |
| 262 | int x, |
| 263 | int y, |
| 264 | GdkWindowEdge* edge) { |
| 265 | gfx::Rect middle(window_size); |
| 266 | middle.Inset(kFrameBorderThickness, |
| 267 | kFrameBorderThickness - top_edge_inset, |
| 268 | kFrameBorderThickness, |
| 269 | kFrameBorderThickness); |
| 270 | if (middle.Contains(x, y)) |
| 271 | return false; |
| 272 | |
| 273 | gfx::Rect north(0, 0, window_size.width(), |
| 274 | kResizeAreaCornerSize - top_edge_inset); |
| 275 | gfx::Rect west(0, 0, kResizeAreaCornerSize, window_size.height()); |
| 276 | gfx::Rect south(0, window_size.height() - kResizeAreaCornerSize, |
| 277 | window_size.width(), kResizeAreaCornerSize); |
| 278 | gfx::Rect east(window_size.width() - kResizeAreaCornerSize, 0, |
| 279 | kResizeAreaCornerSize, window_size.height()); |
| 280 | |
| 281 | if (north.Contains(x, y)) { |
| 282 | if (west.Contains(x, y)) |
| 283 | *edge = GDK_WINDOW_EDGE_NORTH_WEST; |
| 284 | else if (east.Contains(x, y)) |
| 285 | *edge = GDK_WINDOW_EDGE_NORTH_EAST; |
| 286 | else |
| 287 | *edge = GDK_WINDOW_EDGE_NORTH; |
| 288 | } else if (south.Contains(x, y)) { |
| 289 | if (west.Contains(x, y)) |
| 290 | *edge = GDK_WINDOW_EDGE_SOUTH_WEST; |
| 291 | else if (east.Contains(x, y)) |
| 292 | *edge = GDK_WINDOW_EDGE_SOUTH_EAST; |
| 293 | else |
| 294 | *edge = GDK_WINDOW_EDGE_SOUTH; |
| 295 | } else { |
| 296 | if (west.Contains(x, y)) |
| 297 | *edge = GDK_WINDOW_EDGE_WEST; |
| 298 | else if (east.Contains(x, y)) |
| 299 | *edge = GDK_WINDOW_EDGE_EAST; |
| 300 | else |
| 301 | return false; // The cursor must be outside the window. |
| 302 | } |
| 303 | return true; |
| 304 | } |
| 305 | |
[email protected] | 534b163 | 2012-08-14 18:54:48 | [diff] [blame] | 306 | } // namespace gtk_window_util |