Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/cpp/webdriver-interactions/interactions_linux.cpp
2867 views
1
/*
2
Copyright 2007-2009 WebDriver committers
3
Copyright 2007-2009 Google Inc.
4
5
Licensed under the Apache License, Version 2.0 (the "License");
6
you may not use this file except in compliance with the License.
7
You may obtain a copy of the License at
8
9
http://www.apache.org/licenses/LICENSE-2.0
10
11
Unless required by applicable law or agreed to in writing, software
12
distributed under the License is distributed on an "AS IS" BASIS,
13
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
See the License for the specific language governing permissions and
15
limitations under the License.
16
*/
17
18
#include <ctime>
19
#include <string>
20
#include <iostream>
21
#include <fstream>
22
23
#include "interactions.h"
24
#include "logging.h"
25
26
#include <gdk/gdk.h>
27
#include <gdk/gdkkeysyms.h>
28
#include <X11/Xlib.h>
29
#include <time.h>
30
#include <stdlib.h>
31
#include <assert.h>
32
#include <list>
33
#include <algorithm>
34
#include <functional>
35
36
#include "translate_keycode_linux.h"
37
#include "interactions_linux.h"
38
39
using namespace std;
40
41
// This class represents a single modifier key. A modifier key is Shift,
42
// Ctrl or Alt. A key has, besides a GDK symbol related to it, a Mask
43
// that must be appended to each keyboard event when this modifier is
44
// set.
45
class XModifierKey
46
{
47
public:
48
// Stores the key associated with this modifier and the bit-mask
49
// to set when this key was toggled.
50
XModifierKey(const guint& associated_gdk_key, const GdkModifierType& gdk_mod,
51
const guint32& stored_state);
52
// if a_key matches the associated gdk key, toggeles the modifier
53
// key state.
54
void ToggleIfKeyMatches(const guint a_key);
55
// Returns true if the key given matches the key associated with
56
// this modifier.
57
bool KeyMatches(const guint a_key) const;
58
// if the modifier key was pressed, return the mask to OR with.
59
// If not, return 0.
60
guint GetAppropriateMask() const;
61
// Set the modifier to false.
62
void ClearModifier();
63
// Returns the associated key
64
guint get_associated_key() const;
65
// Returns true if the modifier is set, false otherwise.
66
bool get_toggle() const;
67
// Store the current state of the modifier key into the provided int.
68
void StoreState(guint32* state_store) const;
69
private:
70
bool toggle_;
71
guint associated_key_;
72
GdkModifierType gdk_mod_mask_;
73
};
74
75
XModifierKey::XModifierKey(const guint& associated_gdk_key,
76
const GdkModifierType& gdk_mod,
77
const guint32& stored_state) :
78
toggle_(stored_state & gdk_mod), associated_key_(associated_gdk_key), gdk_mod_mask_(gdk_mod)
79
{
80
LOG(DEBUG) << "Restored state for " << gdk_mod_mask_ << " : " << toggle_;
81
}
82
83
bool XModifierKey::KeyMatches(const guint a_key) const
84
{
85
return (a_key == associated_key_);
86
}
87
88
void XModifierKey::ToggleIfKeyMatches(const guint a_key)
89
{
90
if (KeyMatches(a_key)) {
91
toggle_ = !toggle_;
92
}
93
}
94
95
guint XModifierKey::GetAppropriateMask() const
96
{
97
if (toggle_) {
98
return gdk_mod_mask_;
99
}
100
return 0;
101
}
102
103
guint XModifierKey::get_associated_key() const
104
{
105
return associated_key_;
106
}
107
108
void XModifierKey::ClearModifier()
109
{
110
toggle_ = false;
111
}
112
113
bool XModifierKey::get_toggle() const
114
{
115
return toggle_;
116
}
117
118
void XModifierKey::StoreState(guint32* state_store) const
119
{
120
guint32 non_mask_bits = ~gdk_mod_mask_;
121
guint32 toggle_bit = (toggle_ ? gdk_mod_mask_ : 0);
122
123
*state_store = (*state_store & non_mask_bits) | toggle_bit;
124
LOG(DEBUG) << "Storing state for " << gdk_mod_mask_ << " toggled? " << toggle_ <<
125
" state store: " << *state_store << " non-mask bits: " << std::hex << non_mask_bits;
126
}
127
128
// Definition of a key press, release events pair.
129
typedef std::pair<GdkEvent*, GdkEvent*> KeyEventsPair;
130
enum KeyEventType { kKeyPress, kKeyRelease };
131
// This class handles generation of key press / release events.
132
// Events will be generated according to the given key to emulate
133
// and state of modifier keys.
134
class KeypressEventsHandler
135
{
136
public:
137
KeypressEventsHandler(GdkDrawable* win_handle, guint32 modifiers_state);
138
virtual ~KeypressEventsHandler();
139
140
// Create a series of key release events that were left on at the end of
141
// a sendKeys call.
142
list<GdkEvent*> CreateModifierReleaseEvents();
143
144
// Creates a series of key events according to the key to emulate
145
// Cases:
146
// 1. Null key: Reset modifiers state and return no events.
147
// 2. lowercase letter: Create KeyPress, KeyRelease events.
148
// 3. Uppercase letter: Creates Shift Down, KeyPress, KeyRelease
149
// and Shift Up events.
150
// 4. Modifier: KeyPress event only, unless it was down
151
// already - in which case, a KeyRelease
152
list<GdkEvent*> CreateEventsForKey(wchar_t key_to_emulate);
153
// Returns the time of the latest event.
154
guint32 get_last_event_time();
155
// Returns the state of modifier keys, to be stored between calls.
156
guint32 getModifierKeysState();
157
158
159
private:
160
// Create a keyboard event for a character or a non-modifier key
161
// (arrow or tab keys, for example).
162
GdkEvent* CreateKeyEvent(wchar_t key_to_emulate, KeyEventType ev_type);
163
// Create a keyboard event for a modifier key - for example, when
164
// shift is pressed.
165
GdkEvent* CreateModifierKeyEvent(wchar_t key_to_emulate);
166
// Returns true if the given character represents any of the modifier keys
167
// the instance of this class knows about.
168
bool IsModifierKey(wchar_t key);
169
// Generates key down / up pair for a regular character.
170
KeyEventsPair CreateKeyDownUpEvents(wchar_t key_to_emulate);
171
172
// Creates a generic key event - used by the public methods
173
// that generate events. Not used for modifier keys.
174
GdkEvent* CreateGenericKeyEvent(wchar_t key_to_emulate, KeyEventType ev_type);
175
176
// Similar to CreateGenericKeyEvent, but for modifier keys.
177
GdkEvent* CreateGenericModifierKeyEvent(guint gdk_key, KeyEventType ev_type);
178
// Creates an empty event.
179
GdkEvent* CreateEmptyKeyEvent(KeyEventType ev_type);
180
181
// Modifiers related.
182
// Clears all of the modifiers
183
void ClearModifiers();
184
// Creates XModifierKey instances for a list of known, hard-coded
185
// modifier keys.
186
void InitModifiers();
187
// Stores the state of all modifier keys into the static field.
188
void StoreModifiersState();
189
// Given a mask, add bits representing all of the relevant set modifiers
190
// to it.
191
void AddModifiersToMask(guint& mask_to_modifiy);
192
// Returns true if a modifier, representing this gdk key, is set.
193
bool IsModifierSet(guint gdk_key);
194
// Called during handling of a modifier key, this method stores
195
// the change of the appropriate modifier key (toggles it).
196
void StoreModifierKeyState(guint gdk_mod_key);
197
// Returns true if the Shift modifier is set.
198
bool IsShiftSet();
199
200
// Members.
201
// Known modifiers and their states.
202
list<XModifierKey> modifiers_;
203
// The window handle to be used.
204
GdkDrawable* win_handle_;
205
// Time of the most recent event created.
206
guint32 last_event_time_;
207
// State of modifier keys - initialized from a global
208
guint32 modifiers_state_;
209
};
210
211
// Sets the is_modifier field of the GdkEvent according to the supplied
212
// boolean.
213
static void SetIsModifierEvent(GdkEvent* p_ev, bool is_modifier)
214
{
215
assert(p_ev->type == GDK_KEY_RELEASE || p_ev->type == GDK_KEY_PRESS);
216
p_ev->key.is_modifier = (int) is_modifier;
217
}
218
219
KeypressEventsHandler::KeypressEventsHandler(GdkDrawable* win_handle, guint32 modifiers_state) :
220
modifiers_(), win_handle_(win_handle), last_event_time_(TimeSinceBootMsec()),
221
modifiers_state_(modifiers_state)
222
{
223
InitModifiers();
224
}
225
226
// Will be called for the "Null" key.
227
void KeypressEventsHandler::ClearModifiers()
228
{
229
for_each(modifiers_.begin(), modifiers_.end(),
230
mem_fun_ref(&XModifierKey::ClearModifier));
231
}
232
233
void KeypressEventsHandler::InitModifiers()
234
{
235
if (modifiers_.empty() == false) {
236
modifiers_.clear();
237
}
238
239
modifiers_.push_back(XModifierKey(GDK_Shift_L, GDK_SHIFT_MASK, modifiers_state_));
240
modifiers_.push_back(XModifierKey(GDK_Control_L, GDK_CONTROL_MASK, modifiers_state_));
241
modifiers_.push_back(XModifierKey(GDK_Alt_L, GDK_MOD1_MASK, modifiers_state_));
242
}
243
244
void KeypressEventsHandler::StoreModifiersState()
245
{
246
for_each(modifiers_.begin(), modifiers_.end(),
247
bind2nd(mem_fun_ref(&XModifierKey::StoreState), &modifiers_state_));
248
LOG(DEBUG) << "Stored modifiers: " << modifiers_state_;
249
}
250
251
bool KeypressEventsHandler::IsModifierKey(wchar_t key)
252
{
253
bool is_modifier = false;
254
guint gdk_key_sym = translate_code_to_gdk_symbol(key);
255
for (list<XModifierKey>::iterator it = modifiers_.begin();
256
it != modifiers_.end(); ++it) {
257
is_modifier |= it->KeyMatches(gdk_key_sym);
258
}
259
260
return is_modifier;
261
}
262
263
bool KeypressEventsHandler::IsModifierSet(guint gdk_key)
264
{
265
list<XModifierKey>::iterator it =
266
find_if(modifiers_.begin(), modifiers_.end(),
267
bind2nd(mem_fun_ref(&XModifierKey::KeyMatches), gdk_key));
268
269
if (it == modifiers_.end()) {
270
return false;
271
}
272
273
return it->get_toggle();
274
}
275
276
void KeypressEventsHandler::StoreModifierKeyState(guint gdk_mod_key)
277
{
278
for_each(modifiers_.begin(), modifiers_.end(),
279
bind2nd(mem_fun_ref(&XModifierKey::ToggleIfKeyMatches),
280
gdk_mod_key));
281
StoreModifiersState();
282
}
283
284
void KeypressEventsHandler::AddModifiersToMask(guint& mask_to_modifiy)
285
{
286
for (list<XModifierKey>::iterator it = modifiers_.begin();
287
it != modifiers_.end(); ++it) {
288
mask_to_modifiy|= it->GetAppropriateMask();
289
}
290
}
291
292
bool modifier_is_shift(const XModifierKey& k)
293
{
294
return (k.get_associated_key() == GDK_Shift_L);
295
}
296
297
bool KeypressEventsHandler::IsShiftSet()
298
{
299
list<XModifierKey>::iterator it =
300
find_if(modifiers_.begin(), modifiers_.end(), modifier_is_shift);
301
assert(it != modifiers_.end());
302
return it->get_toggle();
303
}
304
305
guint32 KeypressEventsHandler::get_last_event_time()
306
{
307
return last_event_time_;
308
}
309
310
guint32 KeypressEventsHandler::getModifierKeysState()
311
{
312
return modifiers_state_;
313
}
314
315
316
GdkEvent* KeypressEventsHandler::CreateEmptyKeyEvent(KeyEventType ev_type)
317
{
318
GdkEventType gdk_ev = GDK_KEY_PRESS;
319
if (ev_type == kKeyRelease) {
320
gdk_ev = GDK_KEY_RELEASE;
321
}
322
GdkEvent* p_ev = gdk_event_new(gdk_ev);
323
p_ev->key.window = GDK_WINDOW(g_object_ref(win_handle_));
324
p_ev->key.send_event = 0; // NOT a synthesized event.
325
p_ev->key.time = TimeSinceBootMsec();
326
// Also update the latest event time
327
last_event_time_ = p_ev->key.time;
328
// Deprecated.
329
p_ev->key.length = 0;
330
p_ev->key.string = NULL;
331
// Put a default key code for space. This will be fixed later
332
// by callers, that will translate the given character to
333
// its appropriate keycode.
334
const guint16 kSpaceKeycode = 65;
335
p_ev->key.hardware_keycode = kSpaceKeycode;
336
// This flag will be set to true later, if we indeed create
337
// a modifier key event.
338
SetIsModifierEvent(p_ev, false);
339
340
// This applies to regular characters, keys and modifiers.
341
// This must be done before the special handling for modifier
342
// keys, as it will change the internal state of the modifiers.
343
AddModifiersToMask(p_ev->key.state);
344
345
return p_ev;
346
}
347
348
static guint16 get_keycode_for_key(guint for_key)
349
{
350
guint16 ret_kc;
351
const char* display_name = gdk_display_get_name(gdk_display_get_default());
352
Display* xdisplay = XOpenDisplay(display_name);
353
assert(xdisplay != NULL);
354
355
KeyCode kc = XKeysymToKeycode(xdisplay, for_key);
356
LOG(DEBUG) << "Got keycode: " << (int) kc;
357
XCloseDisplay(xdisplay);
358
ret_kc = (int) kc;
359
360
return ret_kc;
361
}
362
363
GdkEvent* KeypressEventsHandler::CreateGenericKeyEvent(wchar_t key_to_emulate,
364
KeyEventType ev_type)
365
{
366
GdkEvent* p_ev = CreateEmptyKeyEvent(ev_type);
367
368
guint translated_key = translate_code_to_gdk_symbol(key_to_emulate);
369
// Common case - key is not a modifier or a special key (arrow, tab, etc)
370
if (translated_key == GDK_VoidSymbol) {
371
// Ordinary character.
372
p_ev->key.keyval = gdk_unicode_to_keyval(key_to_emulate);
373
} else {
374
// Special key
375
p_ev->key.keyval = translated_key;
376
}
377
378
p_ev->key.hardware_keycode = get_keycode_for_key(p_ev->key.keyval);
379
380
if (IsShiftSet()) {
381
p_ev->key.keyval = gdk_keyval_to_upper(p_ev->key.keyval);
382
}
383
384
return p_ev;
385
}
386
387
GdkEvent* KeypressEventsHandler::CreateGenericModifierKeyEvent(
388
guint gdk_key, KeyEventType ev_type)
389
{
390
GdkEvent* p_ev = CreateEmptyKeyEvent(ev_type);
391
392
p_ev->key.keyval = gdk_key;
393
p_ev->key.hardware_keycode = get_keycode_for_key(p_ev->key.keyval);
394
395
SetIsModifierEvent(p_ev, true);
396
397
return p_ev;
398
}
399
GdkEvent* KeypressEventsHandler::CreateKeyEvent(wchar_t key_to_emulate,
400
KeyEventType ev_type)
401
{
402
// Should only be called with non-modifier keys.
403
assert(IsModifierKey(key_to_emulate) == false);
404
return CreateGenericKeyEvent(key_to_emulate, ev_type);
405
}
406
407
KeyEventsPair KeypressEventsHandler::CreateKeyDownUpEvents(
408
wchar_t key_to_emulate)
409
{
410
GdkEvent* down = CreateKeyEvent(key_to_emulate, kKeyPress);
411
GdkEvent* up = CreateKeyEvent(key_to_emulate, kKeyRelease);
412
return std::make_pair(down, up);
413
}
414
415
GdkEvent* KeypressEventsHandler::CreateModifierKeyEvent(
416
wchar_t key_to_emulate)
417
{
418
guint translated_key = translate_code_to_gdk_symbol(key_to_emulate);
419
assert(translated_key != GDK_VoidSymbol);
420
// If the modifier is set - this is a release event, otherwise -
421
// a key press.
422
KeyEventType ev_type = kKeyPress;
423
if (IsModifierSet(translated_key)) {
424
ev_type = kKeyRelease;
425
}
426
GdkEvent* ret_event =
427
CreateGenericModifierKeyEvent(translated_key, ev_type);
428
429
StoreModifierKeyState(translated_key);
430
return ret_event;
431
}
432
433
list<GdkEvent*> KeypressEventsHandler::CreateModifierReleaseEvents()
434
{
435
list<GdkEvent*> ret_list;
436
for (list<XModifierKey>::iterator it = modifiers_.begin();
437
it != modifiers_.end(); ++it) {
438
if (it->get_toggle()) {
439
GdkEvent* rel_event =
440
CreateGenericModifierKeyEvent(it->get_associated_key(), kKeyRelease);
441
ret_list.push_back(rel_event);
442
it->ClearModifier();
443
}
444
}
445
446
StoreModifiersState();
447
448
return ret_list;
449
}
450
451
bool is_lowercase_symbol(wchar_t key_to_emulate)
452
{
453
// Note that it is *only* allowed for keys that cannot be translated
454
// this bears the assumption that keys defined in Keys.java do not
455
// have a different "capitalized" representation.
456
// This makes sense as the keys in Keys.java are non-alphanumeric
457
// keys (arrows, tab, etc);
458
//
459
assert(translate_code_to_gdk_symbol(key_to_emulate) == GDK_VoidSymbol);
460
461
string chars_req_shift = "!$^*()+{}:?|~@#%&_\"<>";
462
463
bool shift_needed = (chars_req_shift.find(toascii(key_to_emulate)) !=
464
string::npos);
465
// If the representation is different than the lowercase
466
// representation, this is not a lowercase character.
467
if (shift_needed || (key_to_emulate != towlower(key_to_emulate))) {
468
return false;
469
}
470
return true;
471
}
472
473
list<GdkEvent*> KeypressEventsHandler::CreateEventsForKey(
474
wchar_t key_to_emulate)
475
{
476
list<GdkEvent*> ret_list;
477
// First case - is it the NULL symbol? If so, reset modifiers and exit.
478
if (key_to_emulate == gNullKey) {
479
LOG(DEBUG) << "Null key - clearing modifiers.";
480
return CreateModifierReleaseEvents();
481
}
482
483
// Now: The key is either a modifier key or character key.
484
// Common case - not a modifier key. Need two events - Key press and
485
// key release.
486
if (IsModifierKey(key_to_emulate) == false) {
487
LOG(DEBUG) << "Key: " << key_to_emulate << " is not a modifier.";
488
489
guint translated_key = translate_code_to_gdk_symbol(key_to_emulate);
490
// First - check to see if this is an lowercase letter or is a
491
// non-alphanumeric key (which cannot be capitalized)
492
if ((translated_key != GDK_VoidSymbol) ||
493
(is_lowercase_symbol(key_to_emulate))) {
494
LOG(DEBUG) << "Lowercase letter or non void gdk symbol.";
495
// More common case - lowercase letter.
496
// Create only two events.
497
// Note that if the Shift modifier is set, this character will
498
// be converted to uppercase by CreateKeyEvent method.
499
KeyEventsPair ev = CreateKeyDownUpEvents(key_to_emulate);
500
ret_list.push_back(ev.first);
501
ret_list.push_back(ev.second);
502
} else {
503
// Uppercase letter/symbol: Fire up shift down event, this key and
504
// shift up event (unless the Shift modifier is already set)
505
bool shift_was_set = IsShiftSet();
506
LOG(DEBUG) << "Uppercase letter. Was shift set? " << shift_was_set;
507
if (shift_was_set == false) {
508
// push shift down event
509
ret_list.push_front(
510
CreateGenericModifierKeyEvent(GDK_Shift_L, kKeyPress));
511
StoreModifierKeyState(GDK_Shift_L);
512
}
513
KeyEventsPair ev = CreateKeyDownUpEvents(key_to_emulate);
514
// Push the events themselves.
515
ret_list.push_back(ev.first);
516
ret_list.push_back(ev.second);
517
518
if (shift_was_set == false) {
519
// push shift up event
520
ret_list.push_back(
521
CreateGenericModifierKeyEvent(GDK_Shift_L, kKeyRelease));
522
// Turn OFF the shift modifier!
523
StoreModifierKeyState(GDK_Shift_L);
524
}
525
}
526
} else { // Modifier key.
527
// When a modifier key is pressed, the state does not yet change to reflect
528
// it (on the KeyPress event for the modifier key). When the modifier key is
529
// released, the state indeed reflects that it was pressed.
530
LOG(DEBUG) << "Key: " << key_to_emulate << " IS a modifier.";
531
// generate only one keypress event, either press or release.
532
GdkEvent* p_ev = CreateModifierKeyEvent(key_to_emulate);
533
ret_list.push_back(p_ev);
534
}
535
536
return ret_list;
537
}
538
539
KeypressEventsHandler::~KeypressEventsHandler()
540
{
541
modifiers_.clear();
542
}
543
544
static void submit_and_free_event(GdkEvent* p_key_event, int sleep_time_ms)
545
{
546
gdk_event_put(p_key_event);
547
gdk_event_free(p_key_event);
548
sleep_for_ms(sleep_time_ms);
549
}
550
551
static void submit_and_free_events_list(list<GdkEvent*>& events_list,
552
int sleep_time_ms)
553
{
554
for_each(events_list.begin(), events_list.end(), print_key_event);
555
556
for_each(events_list.begin(), events_list.end(),
557
bind2nd(ptr_fun(submit_and_free_event), sleep_time_ms));
558
559
events_list.clear();
560
}
561
562
// global variable declared here so it is not used beforehand.
563
guint32 gModifiersState = 0;
564
565
int getTimePerKey(int proposedTimePerKey)
566
{
567
const int minTimePerKey = 10 /* ms */;
568
if (proposedTimePerKey < minTimePerKey) {
569
return minTimePerKey;
570
}
571
572
return proposedTimePerKey;
573
}
574
575
void updateLastEventTime(const guint32 lastEventTime) {
576
if (gLatestEventTime < lastEventTime) {
577
gLatestEventTime = lastEventTime;
578
}
579
}
580
581
extern "C"
582
{
583
void sendKeys(WINDOW_HANDLE windowHandle, const wchar_t* value, int requestedTimePerKey)
584
{
585
init_logging();
586
int timePerKey = getTimePerKey(requestedTimePerKey);
587
588
LOG(DEBUG) << "---------- starting sendKeys: " << windowHandle << " tpk: " <<
589
timePerKey << "---------";
590
GdkDrawable* hwnd = (GdkDrawable*) windowHandle;
591
592
// The keyp_handler will remember the state of modifier keys and
593
// will be used to generate the events themselves.
594
KeypressEventsHandler keyp_handler(hwnd, gModifiersState);
595
596
struct timespec sleep_time;
597
sleep_time.tv_sec = timePerKey / 1000;
598
sleep_time.tv_nsec = (timePerKey % 1000) * 1000000;
599
LOG(DEBUG) << "Sleep time is " << sleep_time.tv_sec << " seconds and " <<
600
sleep_time.tv_nsec << " nanoseconds.";
601
602
int i = 0;
603
while (value[i] != '\0') {
604
list<GdkEvent*> events_for_key =
605
keyp_handler.CreateEventsForKey(value[i]);
606
607
submit_and_free_events_list(events_for_key, timePerKey);
608
609
i++;
610
}
611
612
updateLastEventTime(keyp_handler.get_last_event_time());
613
gModifiersState = keyp_handler.getModifierKeysState();
614
615
LOG(DEBUG) << "---------- Ending sendKeys. Total keys: " << i
616
<< " ----------";
617
}
618
619
void releaseModifierKeys(WINDOW_HANDLE windowHandle, int requestedTimePerKey)
620
{
621
init_logging();
622
int timePerKey = getTimePerKey(requestedTimePerKey);
623
624
LOG(DEBUG) << "---------- starting releaseModifierKeys: " << windowHandle << " tpk: " <<
625
timePerKey << "---------";
626
GdkDrawable* hwnd = (GdkDrawable*) windowHandle;
627
628
// The state of the modifier keys is stored - just calling release will work.
629
KeypressEventsHandler keyp_handler(hwnd, gModifiersState);
630
631
// Free the remaining modifiers that are still set.
632
list<GdkEvent*> modifier_release_events =
633
keyp_handler.CreateModifierReleaseEvents();
634
int num_released = modifier_release_events.size();
635
636
submit_and_free_events_list(modifier_release_events, timePerKey);
637
638
updateLastEventTime(keyp_handler.get_last_event_time());
639
gModifiersState = keyp_handler.getModifierKeysState();
640
641
LOG(DEBUG) << "---------- Ending releaseModifierKeys. Released: " << num_released
642
<< " ----------";
643
}
644
645
}
646
647