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
17namespace {
18bool g_ui_controls_enabled = false;
19
20// static
21guint32 XTimeNow() {
22  struct timespec ts;
23  clock_gettime(CLOCK_MONOTONIC, &ts);
24  return ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
25}
26
27class 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
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 = 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
96namespace ui_controls {
97
98void EnableUIControls() {
99  g_ui_controls_enabled = true;
100}
101
102bool 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
146bool 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
167bool 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
178bool 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
185bool 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
236bool 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
256bool SendMouseClick(MouseButton type) {
257  CHECK(g_ui_controls_enabled);
258  return SendMouseEvents(type, UP | DOWN);
259}
260
261}  // namespace ui_controls
262