Home | History | Annotate | Download | only in automation
      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:
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    248 bool SendMouseClick(MouseButton type) {
    249   return SendMouseEvents(type, UP | DOWN);
    250 }
    251 
    252 #if defined(TOOLKIT_VIEWS)
    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
    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