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