| /* | |
| Copyright 2007-2010 WebDriver committers | |
| Copyright 2007-2010 Google Inc. | |
| Licensed under the Apache License, Version 2.0 (the "License"); | |
| you may not use this file except in compliance with the License. | |
| You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| #include <ctime> | |
| #include <string> | |
| #include <iostream> | |
| #include <fstream> | |
| #include "interactions.h" | |
| #include "logging.h" | |
| #include <gdk/gdk.h> | |
| #include <gdk/gdkkeysyms.h> | |
| #include <X11/Xlib.h> | |
| #include <time.h> | |
| #include <stdlib.h> | |
| #include <assert.h> | |
| #include <list> | |
| #include <algorithm> | |
| #include <functional> | |
| #include "translate_keycode_linux.h" | |
| #include "interactions_linux.h" | |
| #include "interactions_common.h" | |
| using namespace std; | |
| enum MouseEventType { bMousePress, bMouseRelease, bMouse2ButtonPress }; | |
| // This class handles generation of mouse press / release events. | |
| class MouseEventsHandler | |
| { | |
| public: | |
| MouseEventsHandler(GdkDrawable* win_handle); | |
| virtual ~MouseEventsHandler(); | |
| // Creates a series of mouse events (i.e mouse up/down) | |
| list<GdkEvent*> CreateEventsForMouseMove(long x, long y); | |
| list<GdkEvent*> CreateEventsForMouseClick(long x, long y, long button); | |
| list<GdkEvent*> CreateEventsForMouseDoubleClick(long x, long y); | |
| list<GdkEvent*> CreateEventsForMouseDown(long x, long y, long button); | |
| list<GdkEvent*> CreateEventsForMouseUp(long x, long y, long button); | |
| // Returns the time of the latest event. | |
| guint32 get_last_event_time(); | |
| private: | |
| // Create mouse move event | |
| GdkEvent* CreateMouseMotionEvent(long x, long y); | |
| // Create mouse button event (up/down) | |
| GdkEvent* CreateMouseButtonEvent(MouseEventType ev_type, long x, long y, long button); | |
| // The window handle to be used. | |
| GdkDrawable* win_handle_; | |
| // Time of the most recent event created. | |
| guint32 last_event_time_; | |
| }; | |
| MouseEventsHandler::MouseEventsHandler(GdkDrawable* win_handle) : | |
| win_handle_(win_handle), last_event_time_(TimeSinceBootMsec()) | |
| { | |
| } | |
| guint32 MouseEventsHandler::get_last_event_time() | |
| { | |
| return last_event_time_; | |
| } | |
| GdkDevice* getSomeDevice() | |
| { | |
| GList *pList = gdk_devices_list(); | |
| GList *currNode = pList; | |
| GdkDevice *currDevice = NULL; | |
| while ((currNode != NULL) && (currDevice == NULL)) { | |
| currDevice = (GdkDevice*) currNode->data; | |
| currNode = currNode->next; | |
| } | |
| return (GdkDevice*) g_object_ref(currDevice); | |
| } | |
| GdkEvent* MouseEventsHandler::CreateMouseMotionEvent(long x, long y) | |
| { | |
| GdkEvent* p_ev = gdk_event_new(GDK_MOTION_NOTIFY); | |
| p_ev->motion.window = GDK_WINDOW(g_object_ref(win_handle_)); | |
| p_ev->motion.send_event = 0; // NOT a synthesized event. | |
| p_ev->motion.time = TimeSinceBootMsec(); | |
| p_ev->motion.x = x; | |
| p_ev->motion.y = y; | |
| p_ev->motion.axes = NULL; | |
| p_ev->motion.is_hint = 0; | |
| // It is necessary to provide a device. any device. | |
| p_ev->motion.device = getSomeDevice(); | |
| p_ev->motion.state = gModifiersState; | |
| // Also update the latest event time | |
| last_event_time_ = p_ev->motion.time; | |
| return p_ev; | |
| } | |
| GdkEvent* MouseEventsHandler::CreateMouseButtonEvent(MouseEventType ev_type, long x, long y, long button) | |
| { | |
| GdkEventType gdk_ev = GDK_BUTTON_PRESS; | |
| if (ev_type == bMouseRelease) { | |
| gdk_ev = GDK_BUTTON_RELEASE; | |
| } else if (ev_type == bMouse2ButtonPress) { | |
| gdk_ev = GDK_2BUTTON_PRESS; | |
| } | |
| GdkEvent* p_ev = gdk_event_new(gdk_ev); | |
| p_ev->button.window = GDK_WINDOW(g_object_ref(win_handle_)); | |
| p_ev->button.send_event = 0; // NOT a synthesized event. | |
| p_ev->button.time = TimeSinceBootMsec(); | |
| p_ev->button.x = x; | |
| p_ev->button.y = y; | |
| p_ev->button.button = button; | |
| p_ev->button.device = getSomeDevice(); | |
| p_ev->button.state = gModifiersState; | |
| // Also update the latest event time | |
| last_event_time_ = p_ev->motion.time; | |
| return p_ev; | |
| } | |
| list<GdkEvent*> MouseEventsHandler::CreateEventsForMouseDown(long x, long y, long button) | |
| { | |
| GdkEvent* down = CreateMouseButtonEvent(bMousePress, x, y, button); | |
| list<GdkEvent*> ret_list; | |
| ret_list.push_back(down); | |
| return ret_list; | |
| } | |
| list<GdkEvent*> MouseEventsHandler::CreateEventsForMouseUp(long x, long y, long button) | |
| { | |
| GdkEvent* up = CreateMouseButtonEvent(bMouseRelease, x, y, button); | |
| list<GdkEvent*> ret_list; | |
| ret_list.push_back(up); | |
| return ret_list; | |
| } | |
| list<GdkEvent*> MouseEventsHandler::CreateEventsForMouseDoubleClick(long x, long y) | |
| { | |
| // double click is only possible with the left mouse button | |
| const int leftMouseButton = 1; | |
| list<GdkEvent*> ret_list; | |
| ret_list.push_back(CreateMouseButtonEvent(bMousePress, x, y, leftMouseButton)); | |
| ret_list.push_back(CreateMouseButtonEvent(bMouseRelease, x, y, leftMouseButton)); | |
| ret_list.push_back(CreateMouseButtonEvent(bMousePress, x, y, leftMouseButton)); | |
| ret_list.push_back(CreateMouseButtonEvent(bMouse2ButtonPress, x, y, leftMouseButton)); | |
| ret_list.push_back(CreateMouseButtonEvent(bMouseRelease, x, y, leftMouseButton)); | |
| return ret_list; | |
| } | |
| list<GdkEvent*> MouseEventsHandler::CreateEventsForMouseClick(long x, long y, long button) | |
| { | |
| GdkEvent* down = CreateMouseButtonEvent(bMousePress, x, y, button); | |
| GdkEvent* up = CreateMouseButtonEvent(bMouseRelease, x, y, button); | |
| list<GdkEvent*> ret_list; | |
| ret_list.push_back(down); | |
| ret_list.push_back(up); | |
| return ret_list; | |
| } | |
| list<GdkEvent*> MouseEventsHandler::CreateEventsForMouseMove(long x, long y) | |
| { | |
| GdkEvent* move = CreateMouseMotionEvent(x, y); | |
| list<GdkEvent*> ret_list; | |
| ret_list.push_back(move); | |
| return ret_list; | |
| } | |
| MouseEventsHandler::~MouseEventsHandler() | |
| { | |
| } | |
| static void submit_and_free_event(GdkEvent* p_mouse_event, int sleep_time_ms) | |
| { | |
| gdk_event_put(p_mouse_event); | |
| GdkDevice* usedDevice = NULL; | |
| if (p_mouse_event->type == GDK_MOTION_NOTIFY) { | |
| usedDevice = p_mouse_event->motion.device; | |
| } else { | |
| usedDevice = p_mouse_event->button.device; | |
| } | |
| g_object_unref(usedDevice); | |
| gdk_event_free(p_mouse_event); | |
| sleep_for_ms(sleep_time_ms); | |
| } | |
| static void print_mouse_event(GdkEvent* p_ev) | |
| { | |
| if (!((p_ev->type == GDK_BUTTON_PRESS) || (p_ev->type == GDK_BUTTON_RELEASE) | |
| || (p_ev->type == GDK_MOTION_NOTIFY) || p_ev->type == GDK_2BUTTON_PRESS)) { | |
| LOG(DEBUG) << "Not a mouse event."; | |
| return; | |
| } | |
| std::string ev_type; | |
| if (p_ev->type == GDK_BUTTON_PRESS) { | |
| ev_type = "press"; | |
| }; | |
| if (p_ev->type == GDK_BUTTON_RELEASE) { | |
| ev_type = "release"; | |
| }; | |
| if (p_ev->type == GDK_MOTION_NOTIFY) { | |
| ev_type = "motion"; | |
| }; | |
| if (p_ev->type == GDK_2BUTTON_PRESS) { | |
| ev_type = "2press"; | |
| }; | |
| LOG(DEBUG) << "Type: " << ev_type << " time: " << | |
| p_ev->key.time; | |
| } | |
| static void submit_and_free_events_list(list<GdkEvent*>& events_list, | |
| int sleep_time_ms) | |
| { | |
| for_each(events_list.begin(), events_list.end(), print_mouse_event); | |
| for_each(events_list.begin(), events_list.end(), | |
| bind2nd(ptr_fun(submit_and_free_event), sleep_time_ms)); | |
| events_list.clear(); | |
| } | |
| extern "C" | |
| { | |
| WD_RESULT clickAt(WINDOW_HANDLE windowHandle, long x, long y, long button) | |
| { | |
| init_logging(); | |
| LOG(DEBUG) << "---------- starting clickAt: " << windowHandle << "---------"; | |
| GdkDrawable* hwnd = (GdkDrawable*) windowHandle; | |
| if (button == 2) { | |
| // the right mouse button has the value 3 in GDK | |
| button = 3; | |
| } else { | |
| // the left mouse button is default | |
| button = 1; | |
| } | |
| MouseEventsHandler mousep_handler(hwnd); | |
| list<GdkEvent*> events_for_mouse = mousep_handler.CreateEventsForMouseClick(x, y, button); | |
| const int timePerEvent = 10 /* ms */; | |
| submit_and_free_events_list(events_for_mouse, timePerEvent); | |
| if (gLatestEventTime < mousep_handler.get_last_event_time()) { | |
| gLatestEventTime = mousep_handler.get_last_event_time(); | |
| } | |
| LOG(DEBUG) << "---------- Ending clickAt ----------"; | |
| return 0; | |
| } | |
| WD_RESULT doubleClickAt(WINDOW_HANDLE windowHandle, long x, long y) | |
| { | |
| init_logging(); | |
| LOG(DEBUG) << "---------- starting doubleClickAt: " << windowHandle << "---------"; | |
| GdkDrawable* hwnd = (GdkDrawable*) windowHandle; | |
| MouseEventsHandler mousep_handler(hwnd); | |
| const int timePerEvent = 10 /* ms */; | |
| list<GdkEvent*> events_for_mouse = mousep_handler.CreateEventsForMouseDoubleClick(x, y); | |
| submit_and_free_events_list(events_for_mouse, timePerEvent); | |
| if (gLatestEventTime < mousep_handler.get_last_event_time()) { | |
| gLatestEventTime = mousep_handler.get_last_event_time(); | |
| } | |
| LOG(DEBUG) << "---------- Ending doubleClickAt ----------"; | |
| return 0; | |
| } | |
| /** | |
| * mouseMoveTo | |
| */ | |
| WD_RESULT mouseMoveTo(WINDOW_HANDLE windowHandle, long duration, long fromX, long fromY, long toX, long toY) | |
| { | |
| init_logging(); | |
| const int timePerEvent = 10 /* ms */; | |
| LOG(DEBUG) << "---------- starting mouseMoveTo: " << windowHandle << "---------"; | |
| GdkDrawable* hwnd = (GdkDrawable*) windowHandle; | |
| MouseEventsHandler mousep_handler(hwnd); | |
| long pointsDistance = distanceBetweenPoints(fromX, fromY, toX, toY); | |
| const int stepSizeInPixels = 5; | |
| int steps = pointsDistance / stepSizeInPixels; | |
| // If the distance between the points is less than stepSizeInPixels, | |
| // make sure enough move events aregenerated. | |
| // * If the start and finish points are the same, one step is needed. | |
| // Otherwise, generate at least 2 move events: one one the start | |
| // point and the other on the end point. | |
| if ((fromX == toX) && (fromY == toY)) { | |
| steps = 1; | |
| } else { | |
| steps = max(steps, 2); | |
| } | |
| assert(steps > 0); | |
| LOG(DEBUG) << "From: (" << fromX << ", " << fromY << ") to: (" << toX << ", " << toY << ")"; | |
| LOG(DEBUG) << "Distance: " << pointsDistance << " steps: " << steps; | |
| for (int i = 0; i < steps; ++i) { | |
| // To avoid integer division rounding and cumulative floating point errors, | |
| // calculate from scratch each time. We adjust the divider to steps - 1 | |
| // to get a move event generated on the exact starting point as well | |
| // as the end point. | |
| int div_by = max(steps - 1, 1); | |
| int currentX = fromX + ((toX - fromX) * ((double)i) / div_by); | |
| int currentY = fromY + ((toY - fromY) * ((double)i) / div_by); | |
| LOG(DEBUG) << "Moving to: (" << currentX << ", " << currentY << ")"; | |
| list<GdkEvent*> events_for_mouse = mousep_handler.CreateEventsForMouseMove(currentX, currentY); | |
| submit_and_free_events_list(events_for_mouse, timePerEvent); | |
| } | |
| if (gLatestEventTime < mousep_handler.get_last_event_time()) { | |
| gLatestEventTime = mousep_handler.get_last_event_time(); | |
| } | |
| LOG(DEBUG) << "---------- Ending mouseMoveTo ----------"; | |
| return 0; | |
| } | |
| /** | |
| * mouseDownAt | |
| */ | |
| WD_RESULT mouseDownAt(WINDOW_HANDLE windowHandle, long x, long y, long button) | |
| { | |
| init_logging(); | |
| const int timePerEvent = 10 /* ms */; | |
| LOG(DEBUG) << "---------- starting mouseDownAt: " << windowHandle << "---------"; | |
| GdkDrawable* hwnd = (GdkDrawable*) windowHandle; | |
| MouseEventsHandler mousep_handler(hwnd); | |
| struct timespec sleep_time; | |
| sleep_time.tv_sec = timePerEvent / 1000; | |
| sleep_time.tv_nsec = (timePerEvent % 1000) * 1000000; | |
| LOG(DEBUG) << "Sleep time is " << sleep_time.tv_sec << " seconds and " << | |
| sleep_time.tv_nsec << " nanoseconds."; | |
| list<GdkEvent*> events_for_mouse = mousep_handler.CreateEventsForMouseDown(x, y, button); | |
| submit_and_free_events_list(events_for_mouse, timePerEvent); | |
| if (gLatestEventTime < mousep_handler.get_last_event_time()) { | |
| gLatestEventTime = mousep_handler.get_last_event_time(); | |
| } | |
| LOG(DEBUG) << "---------- Ending mouseDownAt ----------"; | |
| return 0; | |
| } | |
| /** | |
| * mouseUpAt | |
| */ | |
| WD_RESULT mouseUpAt(WINDOW_HANDLE windowHandle, long x, long y, long button) | |
| { | |
| init_logging(); | |
| const int timePerEvent = 10 /* ms */; | |
| LOG(DEBUG) << "---------- starting mouseUpAt: " << windowHandle << "---------"; | |
| GdkDrawable* hwnd = (GdkDrawable*) windowHandle; | |
| MouseEventsHandler mousep_handler(hwnd); | |
| struct timespec sleep_time; | |
| sleep_time.tv_sec = timePerEvent / 1000; | |
| sleep_time.tv_nsec = (timePerEvent % 1000) * 1000000; | |
| LOG(DEBUG) << "Sleep time is " << sleep_time.tv_sec << " seconds and " << | |
| sleep_time.tv_nsec << " nanoseconds."; | |
| list<GdkEvent*> events_for_mouse = mousep_handler.CreateEventsForMouseUp(x, y, button); | |
| submit_and_free_events_list(events_for_mouse, timePerEvent); | |
| if (gLatestEventTime < mousep_handler.get_last_event_time()) { | |
| gLatestEventTime = mousep_handler.get_last_event_time(); | |
| } | |
| LOG(DEBUG) << "---------- Ending mouseUpAt ----------"; | |
| return 0; | |
| } | |
| bool pending_mouse_events() | |
| { | |
| init_logging(); | |
| LOG(DEBUG) << "Waiting for all events to be processed"; | |
| GdkEvent* lastEvent = gdk_event_peek(); | |
| LOG(DEBUG) << "Got event: " << | |
| (lastEvent != NULL ? lastEvent->type : 0); | |
| bool ret_val = false; | |
| if (lastEvent != NULL && is_gdk_mouse_event(lastEvent) && | |
| event_earlier_than(lastEvent, gLatestEventTime)) { | |
| ret_val = true; | |
| } | |
| if (lastEvent != NULL) { | |
| gdk_event_free(lastEvent); | |
| } | |
| LOG(DEBUG) << "Returning: " << ret_val; | |
| return ret_val; | |
| } | |
| } // extern C |