ui_controls_linux.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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, bool shift, bool alt, bool command) { 101 DCHECK(command == false); // No command key on Linux 102 GdkWindow* event_window = NULL; 103 GtkWidget* grab_widget = gtk_grab_get_current(); 104 if (grab_widget) { 105 // If there is a grab, send all events to the grabbed widget. 106 event_window = grab_widget->window; 107 } else if (window) { 108 event_window = GTK_WIDGET(window)->window; 109 } else { 110 // No target was specified. Send the events to the active toplevel. 111 GList* windows = gtk_window_list_toplevels(); 112 for (GList* element = windows; element; element = g_list_next(element)) { 113 GtkWindow* this_window = GTK_WINDOW(element->data); 114 if (gtk_window_is_active(this_window)) { 115 event_window = GTK_WIDGET(this_window)->window; 116 break; 117 } 118 } 119 g_list_free(windows); 120 } 121 if (!event_window) { 122 NOTREACHED() << "Window not specified and none is active"; 123 return false; 124 } 125 126 std::vector<GdkEvent*> events; 127 ui::SynthesizeKeyPressEvents(event_window, key, control, shift, alt, &events); 128 for (std::vector<GdkEvent*>::iterator iter = events.begin(); 129 iter != events.end(); ++iter) { 130 gdk_event_put(*iter); 131 // gdk_event_put appends a copy of the event. 132 gdk_event_free(*iter); 133 } 134 135 return true; 136} 137 138bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, 139 ui::KeyboardCode key, 140 bool control, bool shift, 141 bool alt, bool command, 142 Task* task) { 143 DCHECK(command == false); // No command key on Linux 144 int release_count = 1; 145 if (control) 146 release_count++; 147 if (shift) 148 release_count++; 149 if (alt) 150 release_count++; 151 // This object will delete itself after running |task|. 152 new EventWaiter(task, GDK_KEY_RELEASE, release_count); 153 return SendKeyPress(window, key, control, shift, alt, command); 154} 155 156bool SendMouseMove(long x, long y) { 157 gdk_display_warp_pointer(gdk_display_get_default(), gdk_screen_get_default(), 158 x, y); 159 // Sometimes gdk_display_warp_pointer fails to send back any indication of 160 // the move, even though it succesfully moves the server cursor. We fake it in 161 // order to get drags to work. 162 FakeAMouseMotionEvent(x, y); 163 164 return true; 165} 166 167bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { 168 bool rv = SendMouseMove(x, y); 169 // We can't rely on any particular event signalling the completion of the 170 // mouse move. Posting the task to the message loop hopefully guarantees 171 // the pointer has moved before task is run (although it may not run it as 172 // soon as it could). 173 MessageLoop::current()->PostTask(FROM_HERE, task); 174 return rv; 175} 176 177bool SendMouseEvents(MouseButton type, int state) { 178 GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS); 179 180 event->button.send_event = false; 181 event->button.time = gtk_util::XTimeNow(); 182 183 gint x, y; 184 GtkWidget* grab_widget = gtk_grab_get_current(); 185 if (grab_widget) { 186 // If there is a grab, we need to target all events at it regardless of 187 // what widget the mouse is over. 188 event->button.window = grab_widget->window; 189 gdk_window_get_pointer(event->button.window, &x, &y, NULL); 190 } else { 191 event->button.window = gdk_window_at_pointer(&x, &y); 192 } 193 194 g_object_ref(event->button.window); 195 event->button.x = x; 196 event->button.y = y; 197 gint origin_x, origin_y; 198 gdk_window_get_origin(event->button.window, &origin_x, &origin_y); 199 event->button.x_root = x + origin_x; 200 event->button.y_root = y + origin_y; 201 202 event->button.axes = NULL; 203 GdkModifierType modifier; 204 gdk_window_get_pointer(event->button.window, NULL, NULL, &modifier); 205 event->button.state = modifier; 206 event->button.button = type == LEFT ? 1 : (type == MIDDLE ? 2 : 3); 207 event->button.device = gdk_device_get_core_pointer(); 208 209 event->button.type = GDK_BUTTON_PRESS; 210 if (state & DOWN) 211 gdk_event_put(event); 212 213 // Also send a release event. 214 GdkEvent* release_event = gdk_event_copy(event); 215 release_event->button.type = GDK_BUTTON_RELEASE; 216 release_event->button.time++; 217 if (state & UP) 218 gdk_event_put(release_event); 219 220 gdk_event_free(event); 221 gdk_event_free(release_event); 222 223 return false; 224} 225 226bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { 227 bool rv = SendMouseEvents(type, state); 228 GdkEventType wait_type; 229 if (state & UP) { 230 wait_type = GDK_BUTTON_RELEASE; 231 } else { 232 if (type == LEFT) 233 wait_type = GDK_BUTTON_PRESS; 234 else if (type == MIDDLE) 235 wait_type = GDK_2BUTTON_PRESS; 236 else 237 wait_type = GDK_3BUTTON_PRESS; 238 } 239 new EventWaiter(task, wait_type, 1); 240 return rv; 241} 242 243bool SendMouseClick(MouseButton type) { 244 return SendMouseEvents(type, UP | DOWN); 245} 246 247#if defined(TOOLKIT_VIEWS) 248void MoveMouseToCenterAndPress(views::View* view, MouseButton button, 249 int state, Task* task) { 250 gfx::Point view_center(view->width() / 2, view->height() / 2); 251 views::View::ConvertPointToScreen(view, &view_center); 252 SendMouseMoveNotifyWhenDone(view_center.x(), view_center.y(), 253 new ClickTask(button, state, task)); 254} 255#else 256void MoveMouseToCenterAndPress(GtkWidget* widget, 257 MouseButton button, 258 int state, 259 Task* task) { 260 gfx::Rect bounds = gtk_util::GetWidgetScreenBounds(widget); 261 SendMouseMoveNotifyWhenDone(bounds.x() + bounds.width() / 2, 262 bounds.y() + bounds.height() / 2, 263 new ClickTask(button, state, task)); 264} 265#endif 266 267} // namespace ui_controls 268