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
23 namespace {
24
25 class EventWaiter : public MessageLoopForUI::Observer {
26 public:
EventWaiter(Task * task,GdkEventType type,int count)27 EventWaiter(Task* task, GdkEventType type, int count)
28 : task_(task),
29 type_(type),
30 count_(count) {
31 MessageLoopForUI::current()->AddObserver(this);
32 }
33
~EventWaiter()34 virtual ~EventWaiter() {
35 MessageLoopForUI::current()->RemoveObserver(this);
36 }
37
38 // MessageLoop::Observer implementation:
WillProcessEvent(GdkEvent * event)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
DidProcessEvent(GdkEvent * event)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
FakeAMouseMotionEvent(gint x,gint y)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 = 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
96 namespace ui_controls {
97
SendKeyPress(gfx::NativeWindow window,ui::KeyboardCode key,bool control,bool shift,bool alt,bool command)98 bool 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
SendKeyPressNotifyWhenDone(gfx::NativeWindow window,ui::KeyboardCode key,bool control,bool shift,bool alt,bool command,Task * task)141 bool 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
SendMouseMove(long x,long y)161 bool 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
SendMouseMoveNotifyWhenDone(long x,long y,Task * task)172 bool 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
SendMouseEvents(MouseButton type,int state)182 bool 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
SendMouseEventsNotifyWhenDone(MouseButton type,int state,Task * task)231 bool 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
SendMouseClick(MouseButton type)248 bool SendMouseClick(MouseButton type) {
249 return SendMouseEvents(type, UP | DOWN);
250 }
251
252 #if defined(TOOLKIT_VIEWS)
MoveMouseToCenterAndPress(views::View * view,MouseButton button,int state,Task * task)253 void 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
MoveMouseToCenterAndPress(GtkWidget * widget,MouseButton button,int state,Task * task)261 void 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