• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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