1 // Copyright 2013 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 "ui/base/test/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/message_loop.h" 13 #include "ui/base/gtk/event_synthesis_gtk.h" 14 #include "ui/base/gtk/gtk_screen_util.h" 15 #include "ui/gfx/rect.h" 16 17 namespace { 18 bool g_ui_controls_enabled = false; 19 20 // static 21 guint32 XTimeNow() { 22 struct timespec ts; 23 clock_gettime(CLOCK_MONOTONIC, &ts); 24 return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; 25 } 26 27 class EventWaiter : public base::MessageLoopForUI::Observer { 28 public: 29 EventWaiter(const base::Closure& task, GdkEventType type, int count) 30 : task_(task), 31 type_(type), 32 count_(count) { 33 base::MessageLoopForUI::current()->AddObserver(this); 34 } 35 36 virtual ~EventWaiter() { 37 base::MessageLoopForUI::current()->RemoveObserver(this); 38 } 39 40 // MessageLoop::Observer implementation: 41 virtual void WillProcessEvent(GdkEvent* event) OVERRIDE { 42 if ((event->type == type_) && (--count_ == 0)) { 43 // At the time we're invoked the event has not actually been processed. 44 // Use PostTask to make sure the event has been processed before 45 // notifying. 46 // NOTE: if processing a message results in running a nested message 47 // loop, then DidProcessEvent isn't immediately sent. As such, we do 48 // the processing in WillProcessEvent rather than DidProcessEvent. 49 base::MessageLoop::current()->PostTask(FROM_HERE, task_); 50 delete this; 51 } 52 } 53 54 virtual void DidProcessEvent(GdkEvent* event) OVERRIDE { 55 // No-op. 56 } 57 58 private: 59 base::Closure task_; 60 GdkEventType type_; 61 // The number of events of this type to wait for. 62 int count_; 63 }; 64 65 void 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 = 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 = gtk_widget_get_window(grab_widget); 76 } else { 77 event->motion.window = gdk_window_at_pointer(&x, &y); 78 } 79 g_object_ref(event->motion.window); 80 gint window_x, window_y; 81 gdk_window_get_origin(event->motion.window, &window_x, &window_y); 82 event->motion.x = x - window_x; 83 event->motion.y = y - window_y; 84 event->motion.x_root = x; 85 event->motion.y_root = 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 96 namespace ui_controls { 97 98 void EnableUIControls() { 99 g_ui_controls_enabled = true; 100 } 101 102 bool SendKeyPress(gfx::NativeWindow window, 103 ui::KeyboardCode key, 104 bool control, 105 bool shift, 106 bool alt, 107 bool command) { 108 CHECK(g_ui_controls_enabled); 109 DCHECK(!command); // No command key on Linux 110 GdkWindow* event_window = NULL; 111 GtkWidget* grab_widget = gtk_grab_get_current(); 112 if (grab_widget) { 113 // If there is a grab, send all events to the grabbed widget. 114 event_window = gtk_widget_get_window(grab_widget); 115 } else if (window) { 116 event_window = gtk_widget_get_window(GTK_WIDGET(window)); 117 } else { 118 // No target was specified. Send the events to the active toplevel. 119 GList* windows = gtk_window_list_toplevels(); 120 for (GList* element = windows; element; element = g_list_next(element)) { 121 GtkWindow* this_window = GTK_WINDOW(element->data); 122 if (gtk_window_is_active(this_window)) { 123 event_window = gtk_widget_get_window(GTK_WIDGET(this_window)); 124 break; 125 } 126 } 127 g_list_free(windows); 128 } 129 if (!event_window) { 130 NOTREACHED() << "Window not specified and none is active"; 131 return false; 132 } 133 134 std::vector<GdkEvent*> events; 135 ui::SynthesizeKeyPressEvents(event_window, key, control, shift, alt, &events); 136 for (std::vector<GdkEvent*>::iterator iter = events.begin(); 137 iter != events.end(); ++iter) { 138 gdk_event_put(*iter); 139 // gdk_event_put appends a copy of the event. 140 gdk_event_free(*iter); 141 } 142 143 return true; 144 } 145 146 bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, 147 ui::KeyboardCode key, 148 bool control, 149 bool shift, 150 bool alt, 151 bool command, 152 const base::Closure& task) { 153 CHECK(g_ui_controls_enabled); 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 CHECK(g_ui_controls_enabled); 169 gdk_display_warp_pointer(gdk_display_get_default(), gdk_screen_get_default(), 170 x, y); 171 // Sometimes gdk_display_warp_pointer fails to send back any indication of 172 // the move, even though it succesfully moves the server cursor. We fake it in 173 // order to get drags to work. 174 FakeAMouseMotionEvent(x, y); 175 return true; 176 } 177 178 bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { 179 CHECK(g_ui_controls_enabled); 180 bool rv = SendMouseMove(x, y); 181 new EventWaiter(task, GDK_MOTION_NOTIFY, 1); 182 return rv; 183 } 184 185 bool SendMouseEvents(MouseButton type, int state) { 186 CHECK(g_ui_controls_enabled); 187 GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS); 188 189 event->button.send_event = false; 190 event->button.time = XTimeNow(); 191 192 gint x, y; 193 GtkWidget* grab_widget = gtk_grab_get_current(); 194 if (grab_widget) { 195 // If there is a grab, we need to target all events at it regardless of 196 // what widget the mouse is over. 197 event->button.window = gtk_widget_get_window(grab_widget); 198 gdk_window_get_pointer(event->button.window, &x, &y, NULL); 199 } else { 200 event->button.window = gdk_window_at_pointer(&x, &y); 201 CHECK(event->button.window); 202 } 203 204 g_object_ref(event->button.window); 205 event->button.x = x; 206 event->button.y = y; 207 gint origin_x, origin_y; 208 gdk_window_get_origin(event->button.window, &origin_x, &origin_y); 209 event->button.x_root = x + origin_x; 210 event->button.y_root = y + origin_y; 211 212 event->button.axes = NULL; 213 GdkModifierType modifier; 214 gdk_window_get_pointer(event->button.window, NULL, NULL, &modifier); 215 event->button.state = modifier; 216 event->button.button = type == LEFT ? 1 : (type == MIDDLE ? 2 : 3); 217 event->button.device = gdk_device_get_core_pointer(); 218 219 event->button.type = GDK_BUTTON_PRESS; 220 if (state & DOWN) 221 gdk_event_put(event); 222 223 // Also send a release event. 224 GdkEvent* release_event = gdk_event_copy(event); 225 release_event->button.type = GDK_BUTTON_RELEASE; 226 release_event->button.time++; 227 if (state & UP) 228 gdk_event_put(release_event); 229 230 gdk_event_free(event); 231 gdk_event_free(release_event); 232 233 return false; 234 } 235 236 bool SendMouseEventsNotifyWhenDone(MouseButton type, 237 int state, 238 const base::Closure& task) { 239 CHECK(g_ui_controls_enabled); 240 bool rv = SendMouseEvents(type, state); 241 GdkEventType wait_type; 242 if (state & UP) { 243 wait_type = GDK_BUTTON_RELEASE; 244 } else { 245 if (type == LEFT) 246 wait_type = GDK_BUTTON_PRESS; 247 else if (type == MIDDLE) 248 wait_type = GDK_2BUTTON_PRESS; 249 else 250 wait_type = GDK_3BUTTON_PRESS; 251 } 252 new EventWaiter(task, wait_type, 1); 253 return rv; 254 } 255 256 bool SendMouseClick(MouseButton type) { 257 CHECK(g_ui_controls_enabled); 258 return SendMouseEvents(type, UP | DOWN); 259 } 260 261 } // namespace ui_controls 262