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