| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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/automation/ui_controls.h" | |
| 6 | |
| 7 #include <gdk/gdkkeysyms.h> | |
| 8 #include <gtk/gtk.h> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/logging.h" | |
| 12 #include "base/message_loop.h" | |
| 13 #include "chrome/browser/automation/ui_controls_internal.h" | |
| 14 #include "chrome/browser/ui/gtk/gtk_util.h" | |
| 15 #include "chrome/common/automation_constants.h" | |
| 16 #include "ui/base/gtk/event_synthesis_gtk.h" | |
| 17 #include "ui/gfx/rect.h" | |
| 18 | |
| 19 #if defined(TOOLKIT_VIEWS) | |
| 20 #include "ui/views/view.h" | |
| 21 #include "ui/views/widget/widget.h" | |
| 22 #endif | |
| 23 | |
| 24 namespace { | |
| 25 | |
| 26 // static | |
| 27 guint32 XTimeNow() { | |
| 28 struct timespec ts; | |
| 29 clock_gettime(CLOCK_MONOTONIC, &ts); | |
| 30 return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; | |
| 31 } | |
| 32 | |
| 33 class EventWaiter : public MessageLoopForUI::Observer { | |
| 34 public: | |
| 35 EventWaiter(const base::Closure& task, GdkEventType type, int count) | |
| 36 : task_(task), | |
| 37 type_(type), | |
| 38 count_(count) { | |
| 39 MessageLoopForUI::current()->AddObserver(this); | |
| 40 } | |
| 41 | |
| 42 virtual ~EventWaiter() { | |
| 43 MessageLoopForUI::current()->RemoveObserver(this); | |
| 44 } | |
| 45 | |
| 46 // MessageLoop::Observer implementation: | |
| 47 virtual void WillProcessEvent(GdkEvent* event) OVERRIDE { | |
| 48 if ((event->type == type_) && (--count_ == 0)) { | |
| 49 // At the time we're invoked the event has not actually been processed. | |
| 50 // Use PostTask to make sure the event has been processed before | |
| 51 // notifying. | |
| 52 // NOTE: if processing a message results in running a nested message | |
| 53 // loop, then DidProcessEvent isn't immediately sent. As such, we do | |
| 54 // the processing in WillProcessEvent rather than DidProcessEvent. | |
| 55 MessageLoop::current()->PostTask(FROM_HERE, task_); | |
| 56 delete this; | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 virtual void DidProcessEvent(GdkEvent* event) OVERRIDE { | |
| 61 // No-op. | |
| 62 } | |
| 63 | |
| 64 private: | |
| 65 base::Closure task_; | |
| 66 GdkEventType type_; | |
| 67 // The number of events of this type to wait for. | |
| 68 int count_; | |
| 69 }; | |
| 70 | |
| 71 void FakeAMouseMotionEvent(gint x, gint y) { | |
| 72 GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY); | |
| 73 | |
| 74 event->motion.send_event = false; | |
| 75 event->motion.time = XTimeNow(); | |
| 76 | |
| 77 GtkWidget* grab_widget = gtk_grab_get_current(); | |
| 78 if (grab_widget) { | |
| 79 // If there is a grab, we need to target all events at it regardless of | |
| 80 // what widget the mouse is over. | |
| 81 event->motion.window = gtk_widget_get_window(grab_widget); | |
| 82 } else { | |
| 83 event->motion.window = gdk_window_at_pointer(&x, &y); | |
| 84 } | |
| 85 g_object_ref(event->motion.window); | |
| 86 gint window_x, window_y; | |
| 87 gdk_window_get_origin(event->motion.window, &window_x, &window_y); | |
| 88 event->motion.x = x - window_x; | |
| 89 event->motion.y = y - window_y; | |
| 90 event->motion.x_root = x; | |
| 91 event->motion.y_root = y; | |
| 92 | |
| 93 event->motion.device = gdk_device_get_core_pointer(); | |
| 94 event->type = GDK_MOTION_NOTIFY; | |
| 95 | |
| 96 gdk_event_put(event); | |
| 97 gdk_event_free(event); | |
| 98 } | |
| 99 | |
| 100 } // namespace | |
| 101 | |
| 102 namespace ui_controls { | |
| 103 | |
| 104 bool SendKeyPress(gfx::NativeWindow window, | |
| 105 ui::KeyboardCode key, | |
| 106 bool control, | |
| 107 bool shift, | |
| 108 bool alt, | |
| 109 bool command) { | |
| 110 DCHECK(!command); // No command key on Linux | |
| 111 GdkWindow* event_window = NULL; | |
| 112 GtkWidget* grab_widget = gtk_grab_get_current(); | |
| 113 if (grab_widget) { | |
| 114 // If there is a grab, send all events to the grabbed widget. | |
| 115 event_window = gtk_widget_get_window(grab_widget); | |
| 116 } else if (window) { | |
| 117 event_window = gtk_widget_get_window(GTK_WIDGET(window)); | |
| 118 } else { | |
| 119 // No target was specified. Send the events to the active toplevel. | |
| 120 GList* windows = gtk_window_list_toplevels(); | |
| 121 for (GList* element = windows; element; element = g_list_next(element)) { | |
| 122 GtkWindow* this_window = GTK_WINDOW(element->data); | |
| 123 if (gtk_window_is_active(this_window)) { | |
| 124 event_window = gtk_widget_get_window(GTK_WIDGET(this_window)); | |
| 125 break; | |
| 126 } | |
| 127 } | |
| 128 g_list_free(windows); | |
| 129 } | |
| 130 if (!event_window) { | |
| 131 NOTREACHED() << "Window not specified and none is active"; | |
| 132 return false; | |
| 133 } | |
| 134 | |
| 135 std::vector<GdkEvent*> events; | |
| 136 ui::SynthesizeKeyPressEvents(event_window, key, control, shift, alt, &events); | |
| 137 for (std::vector<GdkEvent*>::iterator iter = events.begin(); | |
| 138 iter != events.end(); ++iter) { | |
| 139 gdk_event_put(*iter); | |
| 140 // gdk_event_put appends a copy of the event. | |
| 141 gdk_event_free(*iter); | |
| 142 } | |
| 143 | |
| 144 return true; | |
| 145 } | |
| 146 | |
| 147 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, | |
| 148 ui::KeyboardCode key, | |
| 149 bool control, | |
| 150 bool shift, | |
| 151 bool alt, | |
| 152 bool command, | |
| 153 const base::Closure& task) { | |
| 154 DCHECK(!command); // No command key on Linux | |
| 155 int release_count = 1; | |
| 156 if (control) | |
| 157 release_count++; | |
| 158 if (shift) | |
| 159 release_count++; | |
| 160 if (alt) | |
| 161 release_count++; | |
| 162 // This object will delete itself after running |task|. | |
| 163 new EventWaiter(task, GDK_KEY_RELEASE, release_count); | |
| 164 return SendKeyPress(window, key, control, shift, alt, command); | |
| 165 } | |
| 166 | |
| 167 bool SendMouseMove(long x, long y) { | |
| 168 gdk_display_warp_pointer(gdk_display_get_default(), gdk_screen_get_default(), | |
| 169 x, y); | |
| 170 // Sometimes gdk_display_warp_pointer fails to send back any indication of | |
| 171 // the move, even though it succesfully moves the server cursor. We fake it in | |
| 172 // order to get drags to work. | |
| 173 FakeAMouseMotionEvent(x, y); | |
| 174 return true; | |
| 175 } | |
| 176 | |
| 177 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { | |
| 178 bool rv = SendMouseMove(x, y); | |
| 179 new EventWaiter(task, GDK_MOTION_NOTIFY, 1); | |
| 180 return rv; | |
| 181 } | |
| 182 | |
| 183 bool SendMouseEvents(MouseButton type, int state) { | |
| 184 GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS); | |
| 185 | |
| 186 event->button.send_event = false; | |
| 187 event->button.time = XTimeNow(); | |
| 188 | |
| 189 gint x, y; | |
| 190 GtkWidget* grab_widget = gtk_grab_get_current(); | |
| 191 if (grab_widget) { | |
| 192 // If there is a grab, we need to target all events at it regardless of | |
| 193 // what widget the mouse is over. | |
| 194 event->button.window = gtk_widget_get_window(grab_widget); | |
| 195 gdk_window_get_pointer(event->button.window, &x, &y, NULL); | |
| 196 } else { | |
| 197 event->button.window = gdk_window_at_pointer(&x, &y); | |
| 198 CHECK(event->button.window); | |
| 199 } | |
| 200 | |
| 201 g_object_ref(event->button.window); | |
| 202 event->button.x = x; | |
| 203 event->button.y = y; | |
| 204 gint origin_x, origin_y; | |
| 205 gdk_window_get_origin(event->button.window, &origin_x, &origin_y); | |
| 206 event->button.x_root = x + origin_x; | |
| 207 event->button.y_root = y + origin_y; | |
| 208 | |
| 209 event->button.axes = NULL; | |
| 210 GdkModifierType modifier; | |
| 211 gdk_window_get_pointer(event->button.window, NULL, NULL, &modifier); | |
| 212 event->button.state = modifier; | |
| 213 event->button.button = type == LEFT ? 1 : (type == MIDDLE ? 2 : 3); | |
| 214 event->button.device = gdk_device_get_core_pointer(); | |
| 215 | |
| 216 event->button.type = GDK_BUTTON_PRESS; | |
| 217 if (state & DOWN) | |
| 218 gdk_event_put(event); | |
| 219 | |
| 220 // Also send a release event. | |
| 221 GdkEvent* release_event = gdk_event_copy(event); | |
| 222 release_event->button.type = GDK_BUTTON_RELEASE; | |
| 223 release_event->button.time++; | |
| 224 if (state & UP) | |
| 225 gdk_event_put(release_event); | |
| 226 | |
| 227 gdk_event_free(event); | |
| 228 gdk_event_free(release_event); | |
| 229 | |
| 230 return false; | |
| 231 } | |
| 232 | |
| 233 bool SendMouseEventsNotifyWhenDone(MouseButton type, | |
| 234 int state, | |
| 235 const base::Closure& task) { | |
| 236 bool rv = SendMouseEvents(type, state); | |
| 237 GdkEventType wait_type; | |
| 238 if (state & UP) { | |
| 239 wait_type = GDK_BUTTON_RELEASE; | |
| 240 } else { | |
| 241 if (type == LEFT) | |
| 242 wait_type = GDK_BUTTON_PRESS; | |
| 243 else if (type == MIDDLE) | |
| 244 wait_type = GDK_2BUTTON_PRESS; | |
| 245 else | |
| 246 wait_type = GDK_3BUTTON_PRESS; | |
| 247 } | |
| 248 new EventWaiter(task, wait_type, 1); | |
| 249 return rv; | |
| 250 } | |
| 251 | |
| 252 bool SendMouseClick(MouseButton type) { | |
| 253 return SendMouseEvents(type, UP | DOWN); | |
| 254 } | |
| 255 | |
| 256 #if defined(TOOLKIT_VIEWS) | |
| 257 | |
| 258 #if defined(OS_LINUX) && !defined(USE_AURA) | |
| 259 void OnConfigure(GtkWidget* gtk_widget, GdkEvent* event, gpointer data) { | |
| 260 views::Widget* widget = static_cast<views::Widget*>(data); | |
| 261 gfx::Rect actual = widget->GetWindowScreenBounds(); | |
| 262 gfx::Rect desired = widget->GetRootView()->bounds(); | |
| 263 if (actual.size() == desired.size()) | |
| 264 MessageLoop::current()->Quit(); | |
| 265 } | |
| 266 | |
| 267 void SynchronizeWidgetSize(views::Widget* widget) { | |
| 268 // If the actual window size and desired window size | |
| 269 // are different, wait until the window is resized | |
| 270 // to desired size. | |
| 271 gfx::Rect actual = widget->GetWindowScreenBounds(); | |
| 272 gfx::Rect desired = widget->GetRootView()->bounds(); | |
| 273 if (actual.size() != desired.size()) { | |
| 274 // Listen to configure-event that is emitted when an window gets | |
| 275 // resized. | |
| 276 GtkWidget* gtk_widget = widget->GetNativeView(); | |
| 277 g_signal_connect(gtk_widget, "configure-event", | |
| 278 G_CALLBACK(&OnConfigure), widget); | |
| 279 MessageLoop::current()->Run(); | |
| 280 } | |
| 281 } | |
| 282 #endif | |
| 283 | |
| 284 void MoveMouseToCenterAndPress(views::View* view, | |
| 285 MouseButton button, | |
| 286 int state, | |
| 287 const base::Closure& task) { | |
| 288 #if defined(OS_LINUX) | |
| 289 // X is asynchronous and we need to wait until the window gets | |
| 290 // resized to desired size. | |
| 291 SynchronizeWidgetSize(view->GetWidget()); | |
| 292 #endif | |
| 293 | |
| 294 gfx::Point view_center(view->width() / 2, view->height() / 2); | |
| 295 views::View::ConvertPointToScreen(view, &view_center); | |
| 296 SendMouseMoveNotifyWhenDone( | |
| 297 view_center.x(), view_center.y(), | |
| 298 base::Bind(&ui_controls::internal::ClickTask, button, state, task)); | |
| 299 } | |
| 300 #else | |
| 301 void MoveMouseToCenterAndPress(GtkWidget* widget, | |
| 302 MouseButton button, | |
| 303 int state, | |
| 304 const base::Closure& task) { | |
| 305 gfx::Rect bounds = gtk_util::GetWidgetScreenBounds(widget); | |
| 306 SendMouseMoveNotifyWhenDone( | |
| 307 bounds.x() + bounds.width() / 2, | |
| 308 bounds.y() + bounds.height() / 2, | |
| 309 base::Bind(&ui_controls::internal::ClickTask, button, state, task)); | |
| 310 } | |
| 311 #endif | |
| 312 | |
| 313 #if defined(TOOLKIT_VIEWS) | |
| 314 void RunClosureAfterAllPendingUIEvents(const base::Closure& task) { | |
| 315 // Send noop event and run task. | |
| 316 int x, y; | |
| 317 gdk_window_at_pointer(&x, &y); | |
| 318 SendMouseMoveNotifyWhenDone(x, y, task); | |
| 319 } | |
| 320 #endif | |
| 321 | |
| 322 } // namespace ui_controls | |
| OLD | NEW |