| /* | |
| Copyright 2007-2009 WebDriver committers | |
| Copyright 2007-2009 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" | |
| using namespace std; | |
| // This class represents a single modifier key. A modifier key is Shift, | |
| // Ctrl or Alt. A key has, besides a GDK symbol related to it, a Mask | |
| // that must be appended to each keyboard event when this modifier is | |
| // set. | |
| class XModifierKey | |
| { | |
| public: | |
| // Stores the key associated with this modifier and the bit-mask | |
| // to set when this key was toggled. | |
| XModifierKey(const guint& associated_gdk_key, const GdkModifierType& gdk_mod, | |
| const guint32& stored_state); | |
| // if a_key matches the associated gdk key, toggeles the modifier | |
| // key state. | |
| void ToggleIfKeyMatches(const guint a_key); | |
| // Returns true if the key given matches the key associated with | |
| // this modifier. | |
| bool KeyMatches(const guint a_key) const; | |
| // if the modifier key was pressed, return the mask to OR with. | |
| // If not, return 0. | |
| guint GetAppropriateMask() const; | |
| // Set the modifier to false. | |
| void ClearModifier(); | |
| // Returns the associated key | |
| guint get_associated_key() const; | |
| // Returns true if the modifier is set, false otherwise. | |
| bool get_toggle() const; | |
| // Store the current state of the modifier key into the provided int. | |
| void StoreState(guint32* state_store) const; | |
| private: | |
| bool toggle_; | |
| guint associated_key_; | |
| GdkModifierType gdk_mod_mask_; | |
| }; | |
| XModifierKey::XModifierKey(const guint& associated_gdk_key, | |
| const GdkModifierType& gdk_mod, | |
| const guint32& stored_state) : | |
| toggle_(stored_state & gdk_mod), associated_key_(associated_gdk_key), gdk_mod_mask_(gdk_mod) | |
| { | |
| LOG(DEBUG) << "Restored state for " << gdk_mod_mask_ << " : " << toggle_; | |
| } | |
| bool XModifierKey::KeyMatches(const guint a_key) const | |
| { | |
| return (a_key == associated_key_); | |
| } | |
| void XModifierKey::ToggleIfKeyMatches(const guint a_key) | |
| { | |
| if (KeyMatches(a_key)) { | |
| toggle_ = !toggle_; | |
| } | |
| } | |
| guint XModifierKey::GetAppropriateMask() const | |
| { | |
| if (toggle_) { | |
| return gdk_mod_mask_; | |
| } | |
| return 0; | |
| } | |
| guint XModifierKey::get_associated_key() const | |
| { | |
| return associated_key_; | |
| } | |
| void XModifierKey::ClearModifier() | |
| { | |
| toggle_ = false; | |
| } | |
| bool XModifierKey::get_toggle() const | |
| { | |
| return toggle_; | |
| } | |
| void XModifierKey::StoreState(guint32* state_store) const | |
| { | |
| guint32 non_mask_bits = ~gdk_mod_mask_; | |
| guint32 toggle_bit = (toggle_ ? gdk_mod_mask_ : 0); | |
| *state_store = (*state_store & non_mask_bits) | toggle_bit; | |
| LOG(DEBUG) << "Storing state for " << gdk_mod_mask_ << " toggled? " << toggle_ << | |
| " state store: " << *state_store << " non-mask bits: " << std::hex << non_mask_bits; | |
| } | |
| // Definition of a key press, release events pair. | |
| typedef std::pair<GdkEvent*, GdkEvent*> KeyEventsPair; | |
| enum KeyEventType { kKeyPress, kKeyRelease }; | |
| // This class handles generation of key press / release events. | |
| // Events will be generated according to the given key to emulate | |
| // and state of modifier keys. | |
| class KeypressEventsHandler | |
| { | |
| public: | |
| KeypressEventsHandler(GdkDrawable* win_handle, guint32 modifiers_state); | |
| virtual ~KeypressEventsHandler(); | |
| // Create a series of key release events that were left on at the end of | |
| // a sendKeys call. | |
| list<GdkEvent*> CreateModifierReleaseEvents(); | |
| // Creates a series of key events according to the key to emulate | |
| // Cases: | |
| // 1. Null key: Reset modifiers state and return no events. | |
| // 2. lowercase letter: Create KeyPress, KeyRelease events. | |
| // 3. Uppercase letter: Creates Shift Down, KeyPress, KeyRelease | |
| // and Shift Up events. | |
| // 4. Modifier: KeyPress event only, unless it was down | |
| // already - in which case, a KeyRelease | |
| list<GdkEvent*> CreateEventsForKey(wchar_t key_to_emulate); | |
| // Returns the time of the latest event. | |
| guint32 get_last_event_time(); | |
| // Returns the state of modifier keys, to be stored between calls. | |
| guint32 getModifierKeysState(); | |
| private: | |
| // Create a keyboard event for a character or a non-modifier key | |
| // (arrow or tab keys, for example). | |
| GdkEvent* CreateKeyEvent(wchar_t key_to_emulate, KeyEventType ev_type); | |
| // Create a keyboard event for a modifier key - for example, when | |
| // shift is pressed. | |
| GdkEvent* CreateModifierKeyEvent(wchar_t key_to_emulate); | |
| // Returns true if the given character represents any of the modifier keys | |
| // the instance of this class knows about. | |
| bool IsModifierKey(wchar_t key); | |
| // Generates key down / up pair for a regular character. | |
| KeyEventsPair CreateKeyDownUpEvents(wchar_t key_to_emulate); | |
| // Creates a generic key event - used by the public methods | |
| // that generate events. Not used for modifier keys. | |
| GdkEvent* CreateGenericKeyEvent(wchar_t key_to_emulate, KeyEventType ev_type); | |
| // Similar to CreateGenericKeyEvent, but for modifier keys. | |
| GdkEvent* CreateGenericModifierKeyEvent(guint gdk_key, KeyEventType ev_type); | |
| // Creates an empty event. | |
| GdkEvent* CreateEmptyKeyEvent(KeyEventType ev_type); | |
| // Modifiers related. | |
| // Clears all of the modifiers | |
| void ClearModifiers(); | |
| // Creates XModifierKey instances for a list of known, hard-coded | |
| // modifier keys. | |
| void InitModifiers(); | |
| // Stores the state of all modifier keys into the static field. | |
| void StoreModifiersState(); | |
| // Given a mask, add bits representing all of the relevant set modifiers | |
| // to it. | |
| void AddModifiersToMask(guint& mask_to_modifiy); | |
| // Returns true if a modifier, representing this gdk key, is set. | |
| bool IsModifierSet(guint gdk_key); | |
| // Called during handling of a modifier key, this method stores | |
| // the change of the appropriate modifier key (toggles it). | |
| void StoreModifierKeyState(guint gdk_mod_key); | |
| // Returns true if the Shift modifier is set. | |
| bool IsShiftSet(); | |
| // Members. | |
| // Known modifiers and their states. | |
| list<XModifierKey> modifiers_; | |
| // The window handle to be used. | |
| GdkDrawable* win_handle_; | |
| // Time of the most recent event created. | |
| guint32 last_event_time_; | |
| // State of modifier keys - initialized from a global | |
| guint32 modifiers_state_; | |
| }; | |
| // Sets the is_modifier field of the GdkEvent according to the supplied | |
| // boolean. | |
| static void SetIsModifierEvent(GdkEvent* p_ev, bool is_modifier) | |
| { | |
| assert(p_ev->type == GDK_KEY_RELEASE || p_ev->type == GDK_KEY_PRESS); | |
| p_ev->key.is_modifier = (int) is_modifier; | |
| } | |
| KeypressEventsHandler::KeypressEventsHandler(GdkDrawable* win_handle, guint32 modifiers_state) : | |
| modifiers_(), win_handle_(win_handle), last_event_time_(TimeSinceBootMsec()), | |
| modifiers_state_(modifiers_state) | |
| { | |
| InitModifiers(); | |
| } | |
| // Will be called for the "Null" key. | |
| void KeypressEventsHandler::ClearModifiers() | |
| { | |
| for_each(modifiers_.begin(), modifiers_.end(), | |
| mem_fun_ref(&XModifierKey::ClearModifier)); | |
| } | |
| void KeypressEventsHandler::InitModifiers() | |
| { | |
| if (modifiers_.empty() == false) { | |
| modifiers_.clear(); | |
| } | |
| modifiers_.push_back(XModifierKey(GDK_Shift_L, GDK_SHIFT_MASK, modifiers_state_)); | |
| modifiers_.push_back(XModifierKey(GDK_Control_L, GDK_CONTROL_MASK, modifiers_state_)); | |
| modifiers_.push_back(XModifierKey(GDK_Alt_L, GDK_MOD1_MASK, modifiers_state_)); | |
| } | |
| void KeypressEventsHandler::StoreModifiersState() | |
| { | |
| for_each(modifiers_.begin(), modifiers_.end(), | |
| bind2nd(mem_fun_ref(&XModifierKey::StoreState), &modifiers_state_)); | |
| LOG(DEBUG) << "Stored modifiers: " << modifiers_state_; | |
| } | |
| bool KeypressEventsHandler::IsModifierKey(wchar_t key) | |
| { | |
| bool is_modifier = false; | |
| guint gdk_key_sym = translate_code_to_gdk_symbol(key); | |
| for (list<XModifierKey>::iterator it = modifiers_.begin(); | |
| it != modifiers_.end(); ++it) { | |
| is_modifier |= it->KeyMatches(gdk_key_sym); | |
| } | |
| return is_modifier; | |
| } | |
| bool KeypressEventsHandler::IsModifierSet(guint gdk_key) | |
| { | |
| list<XModifierKey>::iterator it = | |
| find_if(modifiers_.begin(), modifiers_.end(), | |
| bind2nd(mem_fun_ref(&XModifierKey::KeyMatches), gdk_key)); | |
| if (it == modifiers_.end()) { | |
| return false; | |
| } | |
| return it->get_toggle(); | |
| } | |
| void KeypressEventsHandler::StoreModifierKeyState(guint gdk_mod_key) | |
| { | |
| for_each(modifiers_.begin(), modifiers_.end(), | |
| bind2nd(mem_fun_ref(&XModifierKey::ToggleIfKeyMatches), | |
| gdk_mod_key)); | |
| StoreModifiersState(); | |
| } | |
| void KeypressEventsHandler::AddModifiersToMask(guint& mask_to_modifiy) | |
| { | |
| for (list<XModifierKey>::iterator it = modifiers_.begin(); | |
| it != modifiers_.end(); ++it) { | |
| mask_to_modifiy|= it->GetAppropriateMask(); | |
| } | |
| } | |
| bool modifier_is_shift(const XModifierKey& k) | |
| { | |
| return (k.get_associated_key() == GDK_Shift_L); | |
| } | |
| bool KeypressEventsHandler::IsShiftSet() | |
| { | |
| list<XModifierKey>::iterator it = | |
| find_if(modifiers_.begin(), modifiers_.end(), modifier_is_shift); | |
| assert(it != modifiers_.end()); | |
| return it->get_toggle(); | |
| } | |
| guint32 KeypressEventsHandler::get_last_event_time() | |
| { | |
| return last_event_time_; | |
| } | |
| guint32 KeypressEventsHandler::getModifierKeysState() | |
| { | |
| return modifiers_state_; | |
| } | |
| GdkEvent* KeypressEventsHandler::CreateEmptyKeyEvent(KeyEventType ev_type) | |
| { | |
| GdkEventType gdk_ev = GDK_KEY_PRESS; | |
| if (ev_type == kKeyRelease) { | |
| gdk_ev = GDK_KEY_RELEASE; | |
| } | |
| GdkEvent* p_ev = gdk_event_new(gdk_ev); | |
| p_ev->key.window = GDK_WINDOW(g_object_ref(win_handle_)); | |
| p_ev->key.send_event = 0; // NOT a synthesized event. | |
| p_ev->key.time = TimeSinceBootMsec(); | |
| // Also update the latest event time | |
| last_event_time_ = p_ev->key.time; | |
| // Deprecated. | |
| p_ev->key.length = 0; | |
| p_ev->key.string = NULL; | |
| // Put a default key code for space. This will be fixed later | |
| // by callers, that will translate the given character to | |
| // its appropriate keycode. | |
| const guint16 kSpaceKeycode = 65; | |
| p_ev->key.hardware_keycode = kSpaceKeycode; | |
| // This flag will be set to true later, if we indeed create | |
| // a modifier key event. | |
| SetIsModifierEvent(p_ev, false); | |
| // This applies to regular characters, keys and modifiers. | |
| // This must be done before the special handling for modifier | |
| // keys, as it will change the internal state of the modifiers. | |
| AddModifiersToMask(p_ev->key.state); | |
| return p_ev; | |
| } | |
| static guint16 get_keycode_for_key(guint for_key) | |
| { | |
| guint16 ret_kc; | |
| const char* display_name = gdk_display_get_name(gdk_display_get_default()); | |
| Display* xdisplay = XOpenDisplay(display_name); | |
| assert(xdisplay != NULL); | |
| KeyCode kc = XKeysymToKeycode(xdisplay, for_key); | |
| LOG(DEBUG) << "Got keycode: " << (int) kc; | |
| XCloseDisplay(xdisplay); | |
| ret_kc = (int) kc; | |
| return ret_kc; | |
| } | |
| GdkEvent* KeypressEventsHandler::CreateGenericKeyEvent(wchar_t key_to_emulate, | |
| KeyEventType ev_type) | |
| { | |
| GdkEvent* p_ev = CreateEmptyKeyEvent(ev_type); | |
| guint translated_key = translate_code_to_gdk_symbol(key_to_emulate); | |
| // Common case - key is not a modifier or a special key (arrow, tab, etc) | |
| if (translated_key == GDK_VoidSymbol) { | |
| // Ordinary character. | |
| p_ev->key.keyval = gdk_unicode_to_keyval(key_to_emulate); | |
| } else { | |
| // Special key | |
| p_ev->key.keyval = translated_key; | |
| } | |
| p_ev->key.hardware_keycode = get_keycode_for_key(p_ev->key.keyval); | |
| if (IsShiftSet()) { | |
| p_ev->key.keyval = gdk_keyval_to_upper(p_ev->key.keyval); | |
| } | |
| return p_ev; | |
| } | |
| GdkEvent* KeypressEventsHandler::CreateGenericModifierKeyEvent( | |
| guint gdk_key, KeyEventType ev_type) | |
| { | |
| GdkEvent* p_ev = CreateEmptyKeyEvent(ev_type); | |
| p_ev->key.keyval = gdk_key; | |
| p_ev->key.hardware_keycode = get_keycode_for_key(p_ev->key.keyval); | |
| SetIsModifierEvent(p_ev, true); | |
| return p_ev; | |
| } | |
| GdkEvent* KeypressEventsHandler::CreateKeyEvent(wchar_t key_to_emulate, | |
| KeyEventType ev_type) | |
| { | |
| // Should only be called with non-modifier keys. | |
| assert(IsModifierKey(key_to_emulate) == false); | |
| return CreateGenericKeyEvent(key_to_emulate, ev_type); | |
| } | |
| KeyEventsPair KeypressEventsHandler::CreateKeyDownUpEvents( | |
| wchar_t key_to_emulate) | |
| { | |
| GdkEvent* down = CreateKeyEvent(key_to_emulate, kKeyPress); | |
| GdkEvent* up = CreateKeyEvent(key_to_emulate, kKeyRelease); | |
| return std::make_pair(down, up); | |
| } | |
| GdkEvent* KeypressEventsHandler::CreateModifierKeyEvent( | |
| wchar_t key_to_emulate) | |
| { | |
| guint translated_key = translate_code_to_gdk_symbol(key_to_emulate); | |
| assert(translated_key != GDK_VoidSymbol); | |
| // If the modifier is set - this is a release event, otherwise - | |
| // a key press. | |
| KeyEventType ev_type = kKeyPress; | |
| if (IsModifierSet(translated_key)) { | |
| ev_type = kKeyRelease; | |
| } | |
| GdkEvent* ret_event = | |
| CreateGenericModifierKeyEvent(translated_key, ev_type); | |
| StoreModifierKeyState(translated_key); | |
| return ret_event; | |
| } | |
| list<GdkEvent*> KeypressEventsHandler::CreateModifierReleaseEvents() | |
| { | |
| list<GdkEvent*> ret_list; | |
| for (list<XModifierKey>::iterator it = modifiers_.begin(); | |
| it != modifiers_.end(); ++it) { | |
| if (it->get_toggle()) { | |
| GdkEvent* rel_event = | |
| CreateGenericModifierKeyEvent(it->get_associated_key(), kKeyRelease); | |
| ret_list.push_back(rel_event); | |
| it->ClearModifier(); | |
| } | |
| } | |
| StoreModifiersState(); | |
| return ret_list; | |
| } | |
| bool is_lowercase_symbol(wchar_t key_to_emulate) | |
| { | |
| // Note that it is *only* allowed for keys that cannot be translated | |
| // this bears the assumption that keys defined in Keys.java do not | |
| // have a different "capitalized" representation. | |
| // This makes sense as the keys in Keys.java are non-alphanumeric | |
| // keys (arrows, tab, etc); | |
| // | |
| assert(translate_code_to_gdk_symbol(key_to_emulate) == GDK_VoidSymbol); | |
| string chars_req_shift = "!$^*()+{}:?|~@#%&_\"<>"; | |
| bool shift_needed = (chars_req_shift.find(toascii(key_to_emulate)) != | |
| string::npos); | |
| // If the representation is different than the lowercase | |
| // representation, this is not a lowercase character. | |
| if (shift_needed || (key_to_emulate != towlower(key_to_emulate))) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| list<GdkEvent*> KeypressEventsHandler::CreateEventsForKey( | |
| wchar_t key_to_emulate) | |
| { | |
| list<GdkEvent*> ret_list; | |
| // First case - is it the NULL symbol? If so, reset modifiers and exit. | |
| if (key_to_emulate == gNullKey) { | |
| LOG(DEBUG) << "Null key - clearing modifiers."; | |
| return CreateModifierReleaseEvents(); | |
| } | |
| // Now: The key is either a modifier key or character key. | |
| // Common case - not a modifier key. Need two events - Key press and | |
| // key release. | |
| if (IsModifierKey(key_to_emulate) == false) { | |
| LOG(DEBUG) << "Key: " << key_to_emulate << " is not a modifier."; | |
| guint translated_key = translate_code_to_gdk_symbol(key_to_emulate); | |
| // First - check to see if this is an lowercase letter or is a | |
| // non-alphanumeric key (which cannot be capitalized) | |
| if ((translated_key != GDK_VoidSymbol) || | |
| (is_lowercase_symbol(key_to_emulate))) { | |
| LOG(DEBUG) << "Lowercase letter or non void gdk symbol."; | |
| // More common case - lowercase letter. | |
| // Create only two events. | |
| // Note that if the Shift modifier is set, this character will | |
| // be converted to uppercase by CreateKeyEvent method. | |
| KeyEventsPair ev = CreateKeyDownUpEvents(key_to_emulate); | |
| ret_list.push_back(ev.first); | |
| ret_list.push_back(ev.second); | |
| } else { | |
| // Uppercase letter/symbol: Fire up shift down event, this key and | |
| // shift up event (unless the Shift modifier is already set) | |
| bool shift_was_set = IsShiftSet(); | |
| LOG(DEBUG) << "Uppercase letter. Was shift set? " << shift_was_set; | |
| if (shift_was_set == false) { | |
| // push shift down event | |
| ret_list.push_front( | |
| CreateGenericModifierKeyEvent(GDK_Shift_L, kKeyPress)); | |
| StoreModifierKeyState(GDK_Shift_L); | |
| } | |
| KeyEventsPair ev = CreateKeyDownUpEvents(key_to_emulate); | |
| // Push the events themselves. | |
| ret_list.push_back(ev.first); | |
| ret_list.push_back(ev.second); | |
| if (shift_was_set == false) { | |
| // push shift up event | |
| ret_list.push_back( | |
| CreateGenericModifierKeyEvent(GDK_Shift_L, kKeyRelease)); | |
| // Turn OFF the shift modifier! | |
| StoreModifierKeyState(GDK_Shift_L); | |
| } | |
| } | |
| } else { // Modifier key. | |
| // When a modifier key is pressed, the state does not yet change to reflect | |
| // it (on the KeyPress event for the modifier key). When the modifier key is | |
| // released, the state indeed reflects that it was pressed. | |
| LOG(DEBUG) << "Key: " << key_to_emulate << " IS a modifier."; | |
| // generate only one keypress event, either press or release. | |
| GdkEvent* p_ev = CreateModifierKeyEvent(key_to_emulate); | |
| ret_list.push_back(p_ev); | |
| } | |
| return ret_list; | |
| } | |
| KeypressEventsHandler::~KeypressEventsHandler() | |
| { | |
| modifiers_.clear(); | |
| } | |
| static void submit_and_free_event(GdkEvent* p_key_event, int sleep_time_ms) | |
| { | |
| gdk_event_put(p_key_event); | |
| gdk_event_free(p_key_event); | |
| sleep_for_ms(sleep_time_ms); | |
| } | |
| static void submit_and_free_events_list(list<GdkEvent*>& events_list, | |
| int sleep_time_ms) | |
| { | |
| for_each(events_list.begin(), events_list.end(), print_key_event); | |
| for_each(events_list.begin(), events_list.end(), | |
| bind2nd(ptr_fun(submit_and_free_event), sleep_time_ms)); | |
| events_list.clear(); | |
| } | |
| // global variable declared here so it is not used beforehand. | |
| guint32 gModifiersState = 0; | |
| int getTimePerKey(int proposedTimePerKey) | |
| { | |
| const int minTimePerKey = 10 /* ms */; | |
| if (proposedTimePerKey < minTimePerKey) { | |
| return minTimePerKey; | |
| } | |
| return proposedTimePerKey; | |
| } | |
| void updateLastEventTime(const guint32 lastEventTime) { | |
| if (gLatestEventTime < lastEventTime) { | |
| gLatestEventTime = lastEventTime; | |
| } | |
| } | |
| extern "C" | |
| { | |
| void sendKeys(WINDOW_HANDLE windowHandle, const wchar_t* value, int requestedTimePerKey) | |
| { | |
| init_logging(); | |
| int timePerKey = getTimePerKey(requestedTimePerKey); | |
| LOG(DEBUG) << "---------- starting sendKeys: " << windowHandle << " tpk: " << | |
| timePerKey << "---------"; | |
| GdkDrawable* hwnd = (GdkDrawable*) windowHandle; | |
| // The keyp_handler will remember the state of modifier keys and | |
| // will be used to generate the events themselves. | |
| KeypressEventsHandler keyp_handler(hwnd, gModifiersState); | |
| struct timespec sleep_time; | |
| sleep_time.tv_sec = timePerKey / 1000; | |
| sleep_time.tv_nsec = (timePerKey % 1000) * 1000000; | |
| LOG(DEBUG) << "Sleep time is " << sleep_time.tv_sec << " seconds and " << | |
| sleep_time.tv_nsec << " nanoseconds."; | |
| int i = 0; | |
| while (value[i] != '\0') { | |
| list<GdkEvent*> events_for_key = | |
| keyp_handler.CreateEventsForKey(value[i]); | |
| submit_and_free_events_list(events_for_key, timePerKey); | |
| i++; | |
| } | |
| updateLastEventTime(keyp_handler.get_last_event_time()); | |
| gModifiersState = keyp_handler.getModifierKeysState(); | |
| LOG(DEBUG) << "---------- Ending sendKeys. Total keys: " << i | |
| << " ----------"; | |
| } | |
| void releaseModifierKeys(WINDOW_HANDLE windowHandle, int requestedTimePerKey) | |
| { | |
| init_logging(); | |
| int timePerKey = getTimePerKey(requestedTimePerKey); | |
| LOG(DEBUG) << "---------- starting releaseModifierKeys: " << windowHandle << " tpk: " << | |
| timePerKey << "---------"; | |
| GdkDrawable* hwnd = (GdkDrawable*) windowHandle; | |
| // The state of the modifier keys is stored - just calling release will work. | |
| KeypressEventsHandler keyp_handler(hwnd, gModifiersState); | |
| // Free the remaining modifiers that are still set. | |
| list<GdkEvent*> modifier_release_events = | |
| keyp_handler.CreateModifierReleaseEvents(); | |
| int num_released = modifier_release_events.size(); | |
| submit_and_free_events_list(modifier_release_events, timePerKey); | |
| updateLastEventTime(keyp_handler.get_last_event_time()); | |
| gModifiersState = keyp_handler.getModifierKeysState(); | |
| LOG(DEBUG) << "---------- Ending releaseModifierKeys. Released: " << num_released | |
| << " ----------"; | |
| } | |
| } |