ui_controls_linux.cc revision 72a454cd3513ac24fbdd0e0cb9ad70b86a99b801
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, bool shift, bool alt, bool command) {
101  DCHECK(command == false);  // No command key on Linux
102  GdkWindow* event_window = NULL;
103  GtkWidget* grab_widget = gtk_grab_get_current();
104  if (grab_widget) {
105    // If there is a grab, send all events to the grabbed widget.
106    event_window = grab_widget->window;
107  } else if (window) {
108    event_window = GTK_WIDGET(window)->window;
109  } else {
110    // No target was specified. Send the events to the active toplevel.
111    GList* windows = gtk_window_list_toplevels();
112    for (GList* element = windows; element; element = g_list_next(element)) {
113      GtkWindow* this_window = GTK_WINDOW(element->data);
114      if (gtk_window_is_active(this_window)) {
115        event_window = GTK_WIDGET(this_window)->window;
116        break;
117      }
118    }
119    g_list_free(windows);
120  }
121  if (!event_window) {
122    NOTREACHED() << "Window not specified and none is active";
123    return false;
124  }
125
126  std::vector<GdkEvent*> events;
127  ui::SynthesizeKeyPressEvents(event_window, key, control, shift, alt, &events);
128  for (std::vector<GdkEvent*>::iterator iter = events.begin();
129       iter != events.end(); ++iter) {
130    gdk_event_put(*iter);
131    // gdk_event_put appends a copy of the event.
132    gdk_event_free(*iter);
133  }
134
135  return true;
136}
137
138bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
139                                ui::KeyboardCode key,
140                                bool control, bool shift,
141                                bool alt, bool command,
142                                Task* task) {
143  DCHECK(command == false);  // No command key on Linux
144  int release_count = 1;
145  if (control)
146    release_count++;
147  if (shift)
148    release_count++;
149  if (alt)
150    release_count++;
151  // This object will delete itself after running |task|.
152  new EventWaiter(task, GDK_KEY_RELEASE, release_count);
153  return SendKeyPress(window, key, control, shift, alt, command);
154}
155
156bool SendMouseMove(long x, long y) {
157  gdk_display_warp_pointer(gdk_display_get_default(), gdk_screen_get_default(),
158                           x, y);
159  // Sometimes gdk_display_warp_pointer fails to send back any indication of
160  // the move, even though it succesfully moves the server cursor. We fake it in
161  // order to get drags to work.
162  FakeAMouseMotionEvent(x, y);
163
164  return true;
165}
166
167bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) {
168  bool rv = SendMouseMove(x, y);
169  // We can't rely on any particular event signalling the completion of the
170  // mouse move. Posting the task to the message loop hopefully guarantees
171  // the pointer has moved before task is run (although it may not run it as
172  // soon as it could).
173  MessageLoop::current()->PostTask(FROM_HERE, task);
174  return rv;
175}
176
177bool SendMouseEvents(MouseButton type, int state) {
178  GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS);
179
180  event->button.send_event = false;
181  event->button.time = gtk_util::XTimeNow();
182
183  gint x, y;
184  GtkWidget* grab_widget = gtk_grab_get_current();
185  if (grab_widget) {
186    // If there is a grab, we need to target all events at it regardless of
187    // what widget the mouse is over.
188    event->button.window = grab_widget->window;
189    gdk_window_get_pointer(event->button.window, &x, &y, NULL);
190  } else {
191    event->button.window = gdk_window_at_pointer(&x, &y);
192  }
193
194  g_object_ref(event->button.window);
195  event->button.x = x;
196  event->button.y = y;
197  gint origin_x, origin_y;
198  gdk_window_get_origin(event->button.window, &origin_x, &origin_y);
199  event->button.x_root = x + origin_x;
200  event->button.y_root = y + origin_y;
201
202  event->button.axes = NULL;
203  GdkModifierType modifier;
204  gdk_window_get_pointer(event->button.window, NULL, NULL, &modifier);
205  event->button.state = modifier;
206  event->button.button = type == LEFT ? 1 : (type == MIDDLE ? 2 : 3);
207  event->button.device = gdk_device_get_core_pointer();
208
209  event->button.type = GDK_BUTTON_PRESS;
210  if (state & DOWN)
211    gdk_event_put(event);
212
213  // Also send a release event.
214  GdkEvent* release_event = gdk_event_copy(event);
215  release_event->button.type = GDK_BUTTON_RELEASE;
216  release_event->button.time++;
217  if (state & UP)
218    gdk_event_put(release_event);
219
220  gdk_event_free(event);
221  gdk_event_free(release_event);
222
223  return false;
224}
225
226bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) {
227  bool rv = SendMouseEvents(type, state);
228  GdkEventType wait_type;
229  if (state & UP) {
230    wait_type = GDK_BUTTON_RELEASE;
231  } else {
232    if (type == LEFT)
233      wait_type = GDK_BUTTON_PRESS;
234    else if (type == MIDDLE)
235      wait_type = GDK_2BUTTON_PRESS;
236    else
237      wait_type = GDK_3BUTTON_PRESS;
238  }
239  new EventWaiter(task, wait_type, 1);
240  return rv;
241}
242
243bool SendMouseClick(MouseButton type) {
244  return SendMouseEvents(type, UP | DOWN);
245}
246
247#if defined(TOOLKIT_VIEWS)
248void MoveMouseToCenterAndPress(views::View* view, MouseButton button,
249                               int state, Task* task) {
250  gfx::Point view_center(view->width() / 2, view->height() / 2);
251  views::View::ConvertPointToScreen(view, &view_center);
252  SendMouseMoveNotifyWhenDone(view_center.x(), view_center.y(),
253                              new ClickTask(button, state, task));
254}
255#else
256void MoveMouseToCenterAndPress(GtkWidget* widget,
257                               MouseButton button,
258                               int state,
259                               Task* task) {
260  gfx::Rect bounds = gtk_util::GetWidgetScreenBounds(widget);
261  SendMouseMoveNotifyWhenDone(bounds.x() + bounds.width() / 2,
262                              bounds.y() + bounds.height() / 2,
263                              new ClickTask(button, state, task));
264}
265#endif
266
267}  // namespace ui_controls
268