summaryrefslogtreecommitdiffstats
path: root/chrome/browser/automation/ui_controls_linux.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/automation/ui_controls_linux.cc')
-rw-r--r--chrome/browser/automation/ui_controls_linux.cc289
1 files changed, 289 insertions, 0 deletions
diff --git a/chrome/browser/automation/ui_controls_linux.cc b/chrome/browser/automation/ui_controls_linux.cc
new file mode 100644
index 0000000..c9dca78
--- /dev/null
+++ b/chrome/browser/automation/ui_controls_linux.cc
@@ -0,0 +1,289 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/automation/ui_controls.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "gfx/rect.h"
+#include "base/event_synthesis_gtk.h"
+#include "base/keyboard_code_conversion_gtk.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "chrome/browser/gtk/gtk_util.h"
+#include "chrome/test/automation/automation_constants.h"
+
+#if defined(TOOLKIT_VIEWS)
+#include "views/view.h"
+#include "views/widget/widget.h"
+#endif
+
+namespace {
+
+class EventWaiter : public MessageLoopForUI::Observer {
+ public:
+ EventWaiter(Task* task, GdkEventType type, int count)
+ : task_(task),
+ type_(type),
+ count_(count) {
+ MessageLoopForUI::current()->AddObserver(this);
+ }
+
+ virtual ~EventWaiter() {
+ MessageLoopForUI::current()->RemoveObserver(this);
+ }
+
+ // MessageLoop::Observer implementation:
+ virtual void WillProcessEvent(GdkEvent* event) {
+ if ((event->type == type_) && (--count_ == 0)) {
+ // At the time we're invoked the event has not actually been processed.
+ // Use PostTask to make sure the event has been processed before
+ // notifying.
+ // NOTE: if processing a message results in running a nested message
+ // loop, then DidProcessEvent isn't immediately sent. As such, we do
+ // the processing in WillProcessEvent rather than DidProcessEvent.
+ MessageLoop::current()->PostTask(FROM_HERE, task_);
+ delete this;
+ }
+ }
+
+ virtual void DidProcessEvent(GdkEvent* event) {
+ // No-op.
+ }
+
+ private:
+ // We pass ownership of task_ to MessageLoop when the current event is
+ // received.
+ Task* task_;
+ GdkEventType type_;
+ // The number of events of this type to wait for.
+ int count_;
+};
+
+class ClickTask : public Task {
+ public:
+ ClickTask(ui_controls::MouseButton button, int state, Task* followup)
+ : button_(button), state_(state), followup_(followup) {
+ }
+
+ virtual ~ClickTask() {}
+
+ virtual void Run() {
+ if (followup_)
+ ui_controls::SendMouseEventsNotifyWhenDone(button_, state_, followup_);
+ else
+ ui_controls::SendMouseEvents(button_, state_);
+ }
+
+ private:
+ ui_controls::MouseButton button_;
+ int state_;
+ Task* followup_;
+};
+
+void FakeAMouseMotionEvent(gint x, gint y) {
+ GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
+
+ event->motion.send_event = false;
+ event->motion.time = gtk_util::XTimeNow();
+
+ GtkWidget* grab_widget = gtk_grab_get_current();
+ if (grab_widget) {
+ // If there is a grab, we need to target all events at it regardless of
+ // what widget the mouse is over.
+ event->motion.window = grab_widget->window;
+ } else {
+ event->motion.window = gdk_window_at_pointer(&x, &y);
+ }
+ g_object_ref(event->motion.window);
+ event->motion.x = x;
+ event->motion.y = y;
+ gint origin_x, origin_y;
+ gdk_window_get_origin(event->motion.window, &origin_x, &origin_y);
+ event->motion.x_root = x + origin_x;
+ event->motion.y_root = y + origin_y;
+
+ event->motion.device = gdk_device_get_core_pointer();
+ event->type = GDK_MOTION_NOTIFY;
+
+ gdk_event_put(event);
+ gdk_event_free(event);
+}
+
+} // namespace
+
+namespace ui_controls {
+
+bool SendKeyPress(gfx::NativeWindow window,
+ base::KeyboardCode key,
+ bool control, bool shift, bool alt, bool command) {
+ DCHECK(command == false); // No command key on Linux
+ GdkWindow* event_window = NULL;
+ GtkWidget* grab_widget = gtk_grab_get_current();
+ if (grab_widget) {
+ // If there is a grab, send all events to the grabbed widget.
+ event_window = grab_widget->window;
+ } else if (window) {
+ event_window = GTK_WIDGET(window)->window;
+ } else {
+ // No target was specified. Send the events to the active toplevel.
+ GList* windows = gtk_window_list_toplevels();
+ for (GList* element = windows; element; element = g_list_next(element)) {
+ GtkWindow* this_window = GTK_WINDOW(element->data);
+ if (gtk_window_is_active(this_window)) {
+ event_window = GTK_WIDGET(this_window)->window;
+ break;
+ }
+ }
+ g_list_free(windows);
+ }
+ if (!event_window) {
+ NOTREACHED() << "Window not specified and none is active";
+ return false;
+ }
+
+ std::vector<GdkEvent*> events;
+ base::SynthesizeKeyPressEvents(event_window, key, control, shift, alt,
+ &events);
+ for (std::vector<GdkEvent*>::iterator iter = events.begin();
+ iter != events.end(); ++iter) {
+ gdk_event_put(*iter);
+ // gdk_event_put appends a copy of the event.
+ gdk_event_free(*iter);
+ }
+
+ return true;
+}
+
+bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window,
+ base::KeyboardCode key,
+ bool control, bool shift,
+ bool alt, bool command,
+ Task* task) {
+ DCHECK(command == false); // No command key on Linux
+ int release_count = 1;
+ if (control)
+ release_count++;
+ if (shift)
+ release_count++;
+ if (alt)
+ release_count++;
+ // This object will delete itself after running |task|.
+ new EventWaiter(task, GDK_KEY_RELEASE, release_count);
+ return SendKeyPress(window, key, control, shift, alt, command);
+}
+
+bool SendMouseMove(long x, long y) {
+ gdk_display_warp_pointer(gdk_display_get_default(), gdk_screen_get_default(),
+ x, y);
+ // Sometimes gdk_display_warp_pointer fails to send back any indication of
+ // the move, even though it succesfully moves the server cursor. We fake it in
+ // order to get drags to work.
+ FakeAMouseMotionEvent(x, y);
+
+ return true;
+}
+
+bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) {
+ bool rv = SendMouseMove(x, y);
+ // We can't rely on any particular event signalling the completion of the
+ // mouse move. Posting the task to the message loop hopefully guarantees
+ // the pointer has moved before task is run (although it may not run it as
+ // soon as it could).
+ MessageLoop::current()->PostTask(FROM_HERE, task);
+ return rv;
+}
+
+bool SendMouseEvents(MouseButton type, int state) {
+ GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS);
+
+ event->button.send_event = false;
+ event->button.time = gtk_util::XTimeNow();
+
+ gint x, y;
+ GtkWidget* grab_widget = gtk_grab_get_current();
+ if (grab_widget) {
+ // If there is a grab, we need to target all events at it regardless of
+ // what widget the mouse is over.
+ event->button.window = grab_widget->window;
+ gdk_window_get_pointer(event->button.window, &x, &y, NULL);
+ } else {
+ event->button.window = gdk_window_at_pointer(&x, &y);
+ }
+
+ g_object_ref(event->button.window);
+ event->button.x = x;
+ event->button.y = y;
+ gint origin_x, origin_y;
+ gdk_window_get_origin(event->button.window, &origin_x, &origin_y);
+ event->button.x_root = x + origin_x;
+ event->button.y_root = y + origin_y;
+
+ event->button.axes = NULL;
+ GdkModifierType modifier;
+ gdk_window_get_pointer(event->button.window, NULL, NULL, &modifier);
+ event->button.state = modifier;
+ event->button.button = type == LEFT ? 1 : (type == MIDDLE ? 2 : 3);
+ event->button.device = gdk_device_get_core_pointer();
+
+ event->button.type = GDK_BUTTON_PRESS;
+ if (state & DOWN)
+ gdk_event_put(event);
+
+ // Also send a release event.
+ GdkEvent* release_event = gdk_event_copy(event);
+ release_event->button.type = GDK_BUTTON_RELEASE;
+ release_event->button.time++;
+ if (state & UP)
+ gdk_event_put(release_event);
+
+ gdk_event_free(event);
+ gdk_event_free(release_event);
+
+ return false;
+}
+
+bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) {
+ bool rv = SendMouseEvents(type, state);
+ GdkEventType wait_type;
+ if (state & UP) {
+ wait_type = GDK_BUTTON_RELEASE;
+ } else {
+ if (type == LEFT)
+ wait_type = GDK_BUTTON_PRESS;
+ else if (type == MIDDLE)
+ wait_type = GDK_2BUTTON_PRESS;
+ else
+ wait_type = GDK_3BUTTON_PRESS;
+ }
+ new EventWaiter(task, wait_type, 1);
+ return rv;
+}
+
+bool SendMouseClick(MouseButton type) {
+ return SendMouseEvents(type, UP | DOWN);
+}
+
+#if defined(TOOLKIT_VIEWS)
+void MoveMouseToCenterAndPress(views::View* view, MouseButton button,
+ int state, Task* task) {
+ gfx::Point view_center(view->width() / 2, view->height() / 2);
+ views::View::ConvertPointToScreen(view, &view_center);
+ SendMouseMoveNotifyWhenDone(view_center.x(), view_center.y(),
+ new ClickTask(button, state, task));
+}
+#else
+void MoveMouseToCenterAndPress(GtkWidget* widget,
+ MouseButton button,
+ int state,
+ Task* task) {
+ gfx::Rect bounds = gtk_util::GetWidgetScreenBounds(widget);
+ SendMouseMoveNotifyWhenDone(bounds.x() + bounds.width() / 2,
+ bounds.y() + bounds.height() / 2,
+ new ClickTask(button, state, task));
+}
+#endif
+
+} // namespace ui_controls