VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/runtime/UIKeyboardHandler.cpp@ 35744

Last change on this file since 35744 was 35744, checked in by vboxsync, 13 years ago

FE/Qt4-OSX: fix todo for host key combos

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 58.9 KB
Line 
1/* $Id: UIKeyboardHandler.cpp 35744 2011-01-27 16:31:07Z vboxsync $ */
2/** @file
3 *
4 * VBox frontends: Qt GUI ("VirtualBox"):
5 * UIKeyboardHandler class implementation
6 */
7
8/*
9 * Copyright (C) 2010 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20/* Global includes */
21#include <QKeyEvent>
22
23/* Local includes */
24#include "VBoxGlobal.h"
25#include "VBoxProblemReporter.h"
26#include "UIActionsPool.h"
27#include "UIKeyboardHandlerNormal.h"
28#include "UIKeyboardHandlerFullscreen.h"
29#include "UIKeyboardHandlerSeamless.h"
30#include "UIKeyboardHandlerScale.h"
31#include "UIMouseHandler.h"
32#include "UISession.h"
33#include "UIMachineLogic.h"
34#include "UIMachineWindow.h"
35#include "UIMachineView.h"
36#include "UIHotKeyEditor.h"
37
38#ifdef Q_WS_X11
39# include <QX11Info>
40# include <X11/XKBlib.h>
41# include <X11/keysym.h>
42# ifdef KeyPress
43const int XFocusOut = FocusOut;
44const int XFocusIn = FocusIn;
45const int XKeyPress = KeyPress;
46const int XKeyRelease = KeyRelease;
47# undef KeyRelease
48# undef KeyPress
49# undef FocusOut
50# undef FocusIn
51# endif /* KeyPress */
52# include "XKeyboard.h"
53#endif /* Q_WS_X11 */
54
55#ifdef Q_WS_MAC
56# include "VBoxUtils-darwin.h"
57# include "DarwinKeyboard.h"
58# include "UICocoaApplication.h"
59# include <Carbon/Carbon.h>
60#endif /* Q_WS_MAC */
61
62/* Enums representing different keyboard-states: */
63enum { KeyExtended = 0x01, KeyPressed = 0x02, KeyPause = 0x04, KeyPrint = 0x08 };
64enum { IsKeyPressed = 0x01, IsExtKeyPressed = 0x02, IsKbdCaptured = 0x80 };
65
66#ifdef Q_WS_WIN
67UIKeyboardHandler* UIKeyboardHandler::m_spKeyboardHandler = 0;
68#endif /* Q_WS_WIN */
69
70/* Factory function to create keyboard-handler: */
71UIKeyboardHandler* UIKeyboardHandler::create(UIMachineLogic *pMachineLogic,
72 UIVisualStateType visualStateType)
73{
74 /* Prepare keyboard-handler: */
75 UIKeyboardHandler *pKeyboardHandler = 0;
76 /* Depending on visual-state type: */
77 switch (visualStateType)
78 {
79 case UIVisualStateType_Normal:
80 pKeyboardHandler = new UIKeyboardHandlerNormal(pMachineLogic);
81 break;
82 case UIVisualStateType_Fullscreen:
83 pKeyboardHandler = new UIKeyboardHandlerFullscreen(pMachineLogic);
84 break;
85 case UIVisualStateType_Seamless:
86 pKeyboardHandler = new UIKeyboardHandlerSeamless(pMachineLogic);
87 break;
88 case UIVisualStateType_Scale:
89 pKeyboardHandler = new UIKeyboardHandlerScale(pMachineLogic);
90 break;
91 default:
92 break;
93 }
94#ifdef Q_WS_WIN
95 /* Its required to have static pointer to created handler
96 * because windows keyboard-hook works only with static members: */
97 m_spKeyboardHandler = pKeyboardHandler;
98#endif /* Q_WS_WIN */
99 /* Return prepared keyboard-handler: */
100 return pKeyboardHandler;
101}
102
103/* Factory function to destroy keyboard-handler: */
104void UIKeyboardHandler::destroy(UIKeyboardHandler *pKeyboardHandler)
105{
106 /* Delete keyboard-handler: */
107#ifdef Q_WS_WIN
108 m_spKeyboardHandler = 0;
109#endif /* Q_WS_WIN */
110 delete pKeyboardHandler;
111}
112
113/* Prepare listened objects: */
114void UIKeyboardHandler::prepareListener(ulong uIndex, UIMachineWindow *pMachineWindow)
115{
116 /* If that window is NOT registered yet: */
117 if (!m_windows.contains(uIndex))
118 {
119 /* Add window: */
120 m_windows.insert(uIndex, pMachineWindow);
121 /* Install event-filter for window: */
122 m_windows[uIndex]->machineWindow()->installEventFilter(this);
123 }
124
125 /* If that view is NOT registered yet: */
126 if (!m_views.contains(uIndex))
127 {
128 /* Add view: */
129 m_views.insert(uIndex, pMachineWindow->machineView());
130 /* Install event-filter for view: */
131 m_views[uIndex]->installEventFilter(this);
132 }
133}
134
135/* Cleanup listened objects: */
136void UIKeyboardHandler::cleanupListener(ulong uIndex)
137{
138 /* Check if we should release keyboard first: */
139 if ((int)uIndex == m_iKeyboardCaptureViewIndex)
140 releaseKeyboard();
141
142 /* If window still registered: */
143 if (m_windows.contains(uIndex))
144 {
145 /* Remove window: */
146 m_windows.remove(uIndex);
147 }
148
149 /* If view still registered: */
150 if (m_views.contains(uIndex))
151 {
152 /* Remove view: */
153 m_views.remove(uIndex);
154 }
155}
156
157void UIKeyboardHandler::captureKeyboard(ulong uScreenId)
158{
159 /* Do NOT capture keyboard if its captured already: */
160 if (m_fIsKeyboardCaptured)
161 return;
162
163 /* If such view exists: */
164 if (m_views.contains(uScreenId))
165 {
166 /* Store new keyboard-captured state value: */
167 m_fIsKeyboardCaptured = true;
168
169 /* Remember which screen had captured keyboard: */
170 m_iKeyboardCaptureViewIndex = uScreenId;
171
172#if defined(Q_WS_WIN)
173 /* On Win, keyboard grabbing is ineffective, a low-level keyboard hook is used instead. */
174#elif defined(Q_WS_X11)
175 /* On X11, we are using passive XGrabKey for normal (windowed) mode
176 * instead of XGrabKeyboard (called by QWidget::grabKeyboard())
177 * because XGrabKeyboard causes a problem under metacity - a window cannot be moved
178 * using the mouse if it is currently actively grabbing the keyboard;
179 * For static modes we are using usual (active) keyboard grabbing. */
180 switch (machineLogic()->visualStateType())
181 {
182 /* If window is moveable we are making passive keyboard grab: */
183 case UIVisualStateType_Normal:
184 case UIVisualStateType_Scale:
185 {
186 XGrabKey(QX11Info::display(), AnyKey, AnyModifier, m_windows[m_iKeyboardCaptureViewIndex]->machineWindow()->winId(), False, GrabModeAsync, GrabModeAsync);
187 break;
188 }
189 /* If window is NOT moveable we are making active keyboard grab: */
190 case UIVisualStateType_Fullscreen:
191 case UIVisualStateType_Seamless:
192 {
193 /* Keyboard grabbing can fail because of some keyboard shortcut is still grabbed by window manager.
194 * We can't be sure this shortcut will be released at all, so we will retry to grab keyboard for 50 times,
195 * and after we will just ignore that issue: */
196 int cTriesLeft = 50;
197 while (cTriesLeft && XGrabKeyboard(QX11Info::display(), m_windows[m_iKeyboardCaptureViewIndex]->machineWindow()->winId(), False, GrabModeAsync, GrabModeAsync, CurrentTime)) { --cTriesLeft; }
198 break;
199 }
200 /* Should we try to grab keyboard in default case? I think - NO. */
201 default:
202 break;
203 }
204#elif defined(Q_WS_MAC)
205 /* On Mac, we use the Qt methods + disabling global hot keys + watching modifiers (for right/left separation). */
206 ::DarwinDisableGlobalHotKeys(true);
207 m_views[m_iKeyboardCaptureViewIndex]->grabKeyboard();
208#else
209 /* On other platforms we are just praying Qt method will work. */
210 m_views[m_iKeyboardCaptureViewIndex]->grabKeyboard();
211#endif
212
213 /* Notify all the listeners: */
214 emit keyboardStateChanged(keyboardState());
215 }
216}
217
218void UIKeyboardHandler::releaseKeyboard()
219{
220 /* Do NOT capture keyboard if its captured already: */
221 if (!m_fIsKeyboardCaptured)
222 return;
223
224 /* If such view exists: */
225 if (m_views.contains(m_iKeyboardCaptureViewIndex))
226 {
227 /* Store new keyboard-captured state value: */
228 m_fIsKeyboardCaptured = false;
229
230#if defined(Q_WS_WIN)
231 /* On Win, keyboard grabbing is ineffective, a low-level keyboard hook is used instead. */
232#elif defined(Q_WS_X11)
233 /* On X11, we are using passive XGrabKey for normal (windowed) mode
234 * instead of XGrabKeyboard (called by QWidget::grabKeyboard())
235 * because XGrabKeyboard causes a problem under metacity - a window cannot be moved
236 * using the mouse if it is currently actively grabbing the keyboard;
237 * For static modes we are using usual (active) keyboard grabbing. */
238 switch (machineLogic()->visualStateType())
239 {
240 /* If window is moveable we are making passive keyboard ungrab: */
241 case UIVisualStateType_Normal:
242 case UIVisualStateType_Scale:
243 {
244 XUngrabKey(QX11Info::display(), AnyKey, AnyModifier, m_windows[m_iKeyboardCaptureViewIndex]->machineWindow()->winId());
245 break;
246 }
247 /* If window is NOT moveable we are making active keyboard ungrab: */
248 case UIVisualStateType_Fullscreen:
249 case UIVisualStateType_Seamless:
250 {
251 XUngrabKeyboard(QX11Info::display(), CurrentTime);
252 break;
253 }
254 /* Should we try to release keyboard in default case? I think - NO. */
255 default:
256 break;
257 }
258#elif defined(Q_WS_MAC)
259 ::DarwinDisableGlobalHotKeys(false);
260 m_views[m_iKeyboardCaptureViewIndex]->releaseKeyboard();
261#else
262 m_views[m_iKeyboardCaptureViewIndex]->releaseKeyboard();
263#endif
264
265 /* Reset keyboard-capture index: */
266 m_iKeyboardCaptureViewIndex = -1;
267
268 /* Notify all the listeners: */
269 emit keyboardStateChanged(keyboardState());
270 }
271}
272
273void UIKeyboardHandler::releaseAllPressedKeys(bool aReleaseHostKey /* = true */)
274{
275 CKeyboard keyboard = session().GetConsole().GetKeyboard();
276 bool fSentRESEND = false;
277
278 /* Send a dummy scan code (RESEND) to prevent the guest OS from recognizing
279 * a single key click (for ex., Alt) and performing an unwanted action
280 * (for ex., activating the menu) when we release all pressed keys below.
281 * Note, that it's just a guess that sending RESEND will give the desired
282 * effect :), but at least it works with NT and W2k guests. */
283 for (uint i = 0; i < SIZEOF_ARRAY (m_pressedKeys); i++)
284 {
285 if (m_pressedKeys[i] & IsKeyPressed)
286 {
287 if (!fSentRESEND)
288 {
289 keyboard.PutScancode (0xFE);
290 fSentRESEND = true;
291 }
292 keyboard.PutScancode(i | 0x80);
293 }
294 else if (m_pressedKeys[i] & IsExtKeyPressed)
295 {
296 if (!fSentRESEND)
297 {
298 keyboard.PutScancode(0xFE);
299 fSentRESEND = true;
300 }
301 QVector <LONG> codes(2);
302 codes[0] = 0xE0;
303 codes[1] = i | 0x80;
304 keyboard.PutScancodes(codes);
305 }
306 m_pressedKeys[i] = 0;
307 }
308
309 if (aReleaseHostKey)
310 m_bIsHostComboPressed = false;
311
312#ifdef Q_WS_MAC
313 unsigned int hostComboModifierMask = 0;
314 QList<int> hostCombo = UIHotKeyCombination::toKeyCodeList(m_globalSettings.hostCombo());
315 for (int i = 0; i < hostCombo.size(); ++i)
316 hostComboModifierMask |= ::DarwinKeyCodeToDarwinModifierMask(hostCombo.at(i));
317 /* Clear most of the modifiers: */
318 m_darwinKeyModifiers &=
319 alphaLock | kEventKeyModifierNumLockMask |
320 (aReleaseHostKey ? 0 : hostComboModifierMask);
321#endif
322
323 emit keyboardStateChanged(keyboardState());
324}
325
326/* Current keyboard state: */
327int UIKeyboardHandler::keyboardState() const
328{
329 return (m_fIsKeyboardCaptured ? UIViewStateType_KeyboardCaptured : 0) |
330 (m_bIsHostComboPressed ? UIViewStateType_HostKeyPressed : 0);
331}
332
333#if defined(Q_WS_WIN)
334
335bool UIKeyboardHandler::winEventFilter(MSG *pMsg, ulong uScreenId)
336{
337 /* Check if some system event should be filtered-out.
338 * Returning 'true' means filtering-out,
339 * Returning 'false' means passing event to Qt. */
340 bool fResult = false; /* Pass to Qt by default: */
341 switch (pMsg->message)
342 {
343 case WM_KEYDOWN:
344 case WM_KEYUP:
345 case WM_SYSKEYDOWN:
346 case WM_SYSKEYUP:
347 {
348 /* Check for the special flag possibly set at the end of this function: */
349 if (pMsg->lParam & (0x1 << 25))
350 {
351 pMsg->lParam &= ~(0x1 << 25);
352 fResult = false;
353 break;
354 }
355
356 /* Scancodes 0x80 and 0x00 are ignored: */
357 unsigned scan = (pMsg->lParam >> 16) & 0x7F;
358 if (!scan)
359 {
360 fResult = true;
361 break;
362 }
363
364 int vkey = pMsg->wParam;
365
366 int flags = 0;
367 if (pMsg->lParam & 0x1000000)
368 flags |= KeyExtended;
369 if (!(pMsg->lParam & 0x80000000))
370 flags |= KeyPressed;
371
372 /* Check for special Korean keys. Based on the keyboard layout selected
373 * on the host, the scancode in lParam might be 0x71/0x72 or 0xF1/0xF2.
374 * In either case, we must deliver 0xF1/0xF2 scancode to the guest when
375 * the key is pressed and nothing when it's released. */
376 if (scan == 0x71 || scan == 0x72)
377 {
378 scan |= 0x80;
379 flags = KeyPressed; /* Because a release would be ignored. */
380 vkey = VK_PROCESSKEY; /* In case it was 0xFF */
381 }
382
383 /* When one of the SHIFT keys is held and one of the cursor movement
384 * keys is pressed, Windows duplicates SHIFT press/release messages,
385 * but with the virtual key code set to 0xFF. These virtual keys are also
386 * sent in some other situations (Pause, PrtScn, etc.). Ignore suc messages. */
387 if (vkey == 0xFF)
388 {
389 fResult = true;
390 break;
391 }
392
393 switch (vkey)
394 {
395 case VK_SHIFT:
396 case VK_CONTROL:
397 case VK_MENU:
398 {
399 /* Overcome Win32 modifier key generalization: */
400 int keyscan = scan;
401 if (flags & KeyExtended)
402 keyscan |= 0xE000;
403 switch (keyscan)
404 {
405 case 0x002A: vkey = VK_LSHIFT; break;
406 case 0x0036: vkey = VK_RSHIFT; break;
407 case 0x001D: vkey = VK_LCONTROL; break;
408 case 0xE01D: vkey = VK_RCONTROL; break;
409 case 0x0038: vkey = VK_LMENU; break;
410 case 0xE038: vkey = VK_RMENU; break;
411 }
412 break;
413 }
414 case VK_NUMLOCK:
415 /* Win32 sets the extended bit for the NumLock key. Reset it: */
416 flags &= ~KeyExtended;
417 break;
418 case VK_SNAPSHOT:
419 flags |= KeyPrint;
420 break;
421 case VK_PAUSE:
422 flags |= KeyPause;
423 break;
424 }
425
426 bool result = keyEvent(vkey, scan, flags, uScreenId);
427 if (!result && m_fIsKeyboardCaptured)
428 {
429 /* keyEvent() returned that it didn't process the message, but since the
430 * keyboard is captured, we don't want to pass it to Windows. We just want
431 * to let Qt process the message (to handle non-alphanumeric <HOST>+key
432 * shortcuts for example). So send it directly to the window with the
433 * special flag in the reserved area of lParam (to avoid recursion). */
434 ::SendMessage(pMsg->hwnd, pMsg->message,
435 pMsg->wParam, pMsg->lParam | (0x1 << 25));
436 fResult = true;
437 break;
438 }
439
440 /* These special keys have to be handled by Windows as well to update the
441 * internal modifier state and to enable/disable the keyboard LED */
442 if (vkey == VK_NUMLOCK || vkey == VK_CAPITAL || vkey == VK_LSHIFT || vkey == VK_RSHIFT)
443 {
444 fResult = false;
445 break;
446 }
447
448 fResult = result;
449 break;
450 }
451 default:
452 break;
453 }
454 /* Return result: */
455 return fResult;
456}
457
458#elif defined(Q_WS_X11)
459
460static Bool UIKeyboardHandlerCompEvent(Display*, XEvent *pEvent, XPointer pvArg)
461{
462 XEvent *pKeyEvent = (XEvent*)pvArg;
463 if ((pEvent->type == XKeyPress) && (pEvent->xkey.keycode == pKeyEvent->xkey.keycode))
464 return True;
465 else
466 return False;
467}
468
469bool UIKeyboardHandler::x11EventFilter(XEvent *pEvent, ulong uScreenId)
470{
471 /* Check if some system event should be filtered-out.
472 * Returning 'true' means filtering-out,
473 * Returning 'false' means passing event to Qt. */
474 bool fResult = false; /* Pass to Qt by default: */
475 switch (pEvent->type)
476 {
477 /* We have to handle XFocusOut right here as this event is not passed to UIMachineView::event().
478 * Handling this event is important for releasing the keyboard before the screen saver gets active.
479 * See public ticket #3894: Apparently this makes problems with newer versions of Qt
480 * and this hack is probably not necessary anymore. So disable it for Qt >= 4.5.0. */
481 case XFocusOut:
482 case XFocusIn:
483 {
484 if (uisession()->isRunning())
485 {
486 if (VBoxGlobal::qtRTVersion() < ((4 << 16) | (5 << 8) | 0))
487 {
488 if (pEvent->type == XFocusIn)
489 {
490 /* Capture keyboard by chosen view number: */
491 captureKeyboard(uScreenId);
492 /* Reset the single-time disable capture flag: */
493 if (uisession()->isAutoCaptureDisabled())
494 uisession()->setAutoCaptureDisabled(false);
495 }
496 else
497 {
498 /* Release keyboard: */
499 releaseKeyboard();
500 /* And all pressed keys including host-one: */
501 releaseAllPressedKeys(true);
502 }
503 }
504 }
505 fResult = false;
506 }
507 case XKeyPress:
508 case XKeyRelease:
509 {
510 /* Translate the keycode to a PC scan code. */
511 unsigned scan = handleXKeyEvent(pEvent);
512
513 /* Scancodes 0x00 (no valid translation) and 0x80 are ignored: */
514 if (!scan & 0x7F)
515 {
516 fResult = true;
517 break;
518 }
519
520 /* Fix for http://www.virtualbox.org/ticket/1296:
521 * when X11 sends events for repeated keys, it always inserts an XKeyRelease before the XKeyPress. */
522 XEvent returnEvent;
523 if ((pEvent->type == XKeyRelease) && (XCheckIfEvent(pEvent->xkey.display, &returnEvent,
524 UIKeyboardHandlerCompEvent, (XPointer)pEvent) == True))
525 {
526 XPutBackEvent(pEvent->xkey.display, &returnEvent);
527 fResult = true;
528 break;
529 }
530
531 KeySym ks = ::XKeycodeToKeysym(pEvent->xkey.display, pEvent->xkey.keycode, 0);
532
533 int flags = 0;
534 if (scan >> 8)
535 flags |= KeyExtended;
536 if (pEvent->type == XKeyPress)
537 flags |= KeyPressed;
538
539 /* Remove the extended flag: */
540 scan &= 0x7F;
541
542 /* Special Korean keys must send scancode 0xF1/0xF2 when pressed and nothing
543 * when released.
544 */
545 if (scan == 0x71 || scan == 0x72)
546 {
547 if (pEvent->type == XKeyRelease) /* Ignore. */
548 {
549 fResult = true;
550 break;
551 }
552 scan |= 0x80; /* Re-create the bizarre scancode. */
553 }
554
555 switch (ks)
556 {
557 case XK_Print:
558 flags |= KeyPrint;
559 break;
560 case XK_Pause:
561 if (pEvent->xkey.state & ControlMask) /* Break */
562 {
563 ks = XK_Break;
564 flags |= KeyExtended;
565 scan = 0x46;
566 }
567 else
568 flags |= KeyPause;
569 break;
570 }
571
572 fResult = keyEvent(ks, scan, flags, uScreenId);
573 }
574 default:
575 break;
576 }
577 /* Return result: */
578 return fResult;
579}
580
581#endif
582
583/* Machine state-change handler: */
584void UIKeyboardHandler::sltMachineStateChanged()
585{
586 /* Get machine state: */
587 KMachineState state = uisession()->machineState();
588 /* Handle particular machine states: */
589 switch (state)
590 {
591 case KMachineState_Paused:
592 case KMachineState_TeleportingPausedVM:
593 case KMachineState_Stuck:
594 {
595 /* Release the keyboard: */
596 releaseKeyboard();
597 /* And all pressed keys except the host-one : */
598 releaseAllPressedKeys(false /* release host-key? */);
599 break;
600 }
601 case KMachineState_Running:
602 {
603 /* Capture the keyboard by the first focused view: */
604 QList<ulong> theListOfViewIds = m_views.keys();
605 for (int i = 0; i < theListOfViewIds.size(); ++i)
606 {
607 if (m_views[theListOfViewIds[i]]->hasFocus())
608 {
609 /* Capture keyboard: */
610#ifdef Q_WS_WIN
611 if (!uisession()->isAutoCaptureDisabled() && m_globalSettings.autoCapture() &&
612 GetAncestor(m_views[theListOfViewIds[i]]->winId(), GA_ROOT) == GetForegroundWindow())
613#else /* Q_WS_WIN */
614 if (!uisession()->isAutoCaptureDisabled() && m_globalSettings.autoCapture())
615#endif /* !Q_WS_WIN */
616 captureKeyboard(theListOfViewIds[i]);
617 /* Reset the single-time disable capture flag: */
618 if (uisession()->isAutoCaptureDisabled())
619 uisession()->setAutoCaptureDisabled(false);
620 break;
621 }
622 }
623 break;
624 }
625 default:
626 break;
627 }
628}
629
630/* Keyboard-handler constructor: */
631UIKeyboardHandler::UIKeyboardHandler(UIMachineLogic *pMachineLogic)
632 : QObject(pMachineLogic)
633 , m_pMachineLogic(pMachineLogic)
634 , m_iKeyboardCaptureViewIndex(-1)
635 , m_globalSettings(vboxGlobal().settings())
636 , m_fIsKeyboardCaptured(false)
637 , m_bIsHostComboPressed(false)
638 , m_bIsHostComboAlone (false)
639 , m_fPassCAD(false)
640#if defined(Q_WS_WIN)
641 , m_bIsHostkeyInCapture(false)
642 , m_iKeyboardHookViewIndex(-1)
643#elif defined(Q_WS_MAC)
644 , m_darwinKeyModifiers(0)
645 , m_fKeyboardGrabbed(false)
646 , m_iKeyboardGrabViewIndex(-1)
647#endif
648{
649 /* Prepare: */
650 prepareCommon();
651
652 /* Load settings: */
653 loadSettings();
654
655 /* Initialize: */
656 sltMachineStateChanged();
657}
658
659/* Keyboard-handler destructor: */
660UIKeyboardHandler::~UIKeyboardHandler()
661{
662 /* Cleanup: */
663 cleanupCommon();
664}
665
666void UIKeyboardHandler::prepareCommon()
667{
668 /* Machine state-change updater: */
669 connect(uisession(), SIGNAL(sigMachineStateChange()), this, SLOT(sltMachineStateChanged()));
670
671 /* Pressed keys: */
672 ::memset(m_pressedKeys, 0, sizeof(m_pressedKeys));
673}
674
675void UIKeyboardHandler::loadSettings()
676{
677 /* Global settings: */
678#ifdef Q_WS_X11
679 /* Initialize the X keyboard subsystem: */
680 initMappedX11Keyboard(QX11Info::display(), vboxGlobal().settings().publicProperty("GUI/RemapScancodes"));
681#endif
682
683 /* Extra data settings: */
684 {
685 /* CAD settings: */
686 QString passCAD = session().GetConsole().GetMachine().GetExtraData(VBoxDefs::GUI_PassCAD);
687 if (!passCAD.isEmpty() && passCAD != "false" && passCAD != "no")
688 m_fPassCAD = true;
689 }
690}
691
692void UIKeyboardHandler::cleanupCommon()
693{
694#if defined(Q_WS_WIN)
695 /* Cleaning keyboard-hook: */
696 if (m_keyboardHook)
697 {
698 UnhookWindowsHookEx(m_keyboardHook);
699 m_keyboardHook = NULL;
700 }
701#elif defined(Q_WS_MAC)
702 /* We have to make sure the callback for the keyboard events
703 * is released when closing this view. */
704 if (m_fKeyboardGrabbed)
705 darwinGrabKeyboardEvents(false);
706#endif
707}
708
709/* Machine-logic getter: */
710UIMachineLogic* UIKeyboardHandler::machineLogic() const
711{
712 return m_pMachineLogic;
713}
714
715/* UI Session getter: */
716UISession* UIKeyboardHandler::uisession() const
717{
718 return machineLogic()->uisession();
719}
720
721/* Main Session getter: */
722CSession& UIKeyboardHandler::session() const
723{
724 return uisession()->session();
725}
726
727/* Event handler for prepared listener(s): */
728bool UIKeyboardHandler::eventFilter(QObject *pWatchedObject, QEvent *pEvent)
729{
730 /* Check if pWatchedObject object is window: */
731 if (UIMachineWindow *pWatchedWindow = isItListenedWindow(pWatchedObject))
732 {
733 /* Get corresponding screen index: */
734 ulong uScreenId = m_windows.key(pWatchedWindow);
735 NOREF(uScreenId);
736 /* Handle window events: */
737 switch (pEvent->type())
738 {
739#if defined(Q_WS_WIN)
740 /* Install/uninstall low-level keyboard-hook on every activation/deactivation to:
741 * a) avoid excess hook calls when we're not active and;
742 * b) be always in front of any other possible hooks. */
743 case QEvent::WindowActivate:
744 {
745 /* If keyboard hook is NOT currently created;
746 * Or created but NOT for that window: */
747 if (!m_keyboardHook || m_iKeyboardHookViewIndex != uScreenId)
748 {
749 /* If keyboard-hook present: */
750 if (m_keyboardHook)
751 {
752 /* We should remove existing keyboard-hook first: */
753 UnhookWindowsHookEx(m_keyboardHook);
754 m_keyboardHook = NULL;
755 }
756 /* Register new keyboard-hook: */
757 m_keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, lowLevelKeyboardProc, GetModuleHandle(NULL), 0);
758 AssertMsg(m_keyboardHook, ("SetWindowsHookEx(): err=%d", GetLastError()));
759 /* Remember which view had captured keyboard: */
760 m_iKeyboardHookViewIndex = uScreenId;
761 }
762 break;
763 }
764 case QEvent::WindowDeactivate:
765 {
766 /* If keyboard is currently captured: */
767 if (m_keyboardHook && m_iKeyboardHookViewIndex == uScreenId)
768 {
769 /* We should remove existing keyboard-hook: */
770 UnhookWindowsHookEx(m_keyboardHook);
771 m_keyboardHook = NULL;
772 /* Remember what there is no window captured keyboard: */
773 m_iKeyboardHookViewIndex = -1;
774 }
775 break;
776 }
777#elif defined(Q_WS_MAC)
778 case QEvent::WindowActivate:
779 {
780 /* If keyboard event handler is NOT currently installed;
781 * Or installed but NOT for that window: */
782 if (m_iKeyboardGrabViewIndex != (int)uScreenId)
783 {
784 /* If keyboard event handler is NOT currently installed: */
785 if (m_iKeyboardGrabViewIndex == -1)
786 {
787 /* Install the keyboard event handler: */
788 darwinGrabKeyboardEvents(true);
789 }
790 /* Update the id: */
791 m_iKeyboardGrabViewIndex = uScreenId;
792 }
793 break;
794 }
795 case QEvent::WindowDeactivate:
796 {
797 /* If keyboard event handler is installed exactly for that window: */
798 if (m_iKeyboardGrabViewIndex == (int)uScreenId)
799 {
800 /* Remove the keyboard event handler: */
801 darwinGrabKeyboardEvents(false);
802 /* Update the id: */
803 m_iKeyboardGrabViewIndex = -1;
804 }
805 break;
806 }
807#endif
808 default:
809 break;
810 }
811 }
812
813 else
814
815 /* Check if pWatchedObject object is view: */
816 if (UIMachineView *pWatchedView = isItListenedView(pWatchedObject))
817 {
818 /* Get corresponding screen index: */
819 ulong uScreenId = m_views.key(pWatchedView);
820 NOREF(uScreenId);
821 /* Handle view events: */
822 switch (pEvent->type())
823 {
824 case QEvent::FocusIn:
825 if (uisession()->isRunning())
826 {
827 /* Capture keyboard: */
828#ifdef Q_WS_WIN
829 if (!uisession()->isAutoCaptureDisabled() && m_globalSettings.autoCapture() &&
830 GetAncestor(pWatchedView->winId(), GA_ROOT) == GetForegroundWindow())
831#else /* Q_WS_WIN */
832 if (!uisession()->isAutoCaptureDisabled() && m_globalSettings.autoCapture())
833#endif /* !Q_WS_WIN */
834 captureKeyboard(uScreenId);
835 /* Reset the single-time disable capture flag: */
836 if (uisession()->isAutoCaptureDisabled())
837 uisession()->setAutoCaptureDisabled(false);
838 }
839 break;
840 case QEvent::FocusOut:
841 /* Release keyboard: */
842 if (uisession()->isRunning())
843 releaseKeyboard();
844 /* And all pressed keys: */
845 releaseAllPressedKeys(true);
846 break;
847 case QEvent::KeyPress:
848 case QEvent::KeyRelease:
849 {
850 QKeyEvent *pKeyEvent = static_cast<QKeyEvent*>(pEvent);
851
852 if (m_bIsHostComboPressed && pEvent->type() == QEvent::KeyPress)
853 {
854 /* Passing F1-F12 keys to the guest: */
855 if (pKeyEvent->key() >= Qt::Key_F1 && pKeyEvent->key() <= Qt::Key_F12)
856 {
857 QVector <LONG> combo(6);
858 combo[0] = 0x1d; /* Ctrl down */
859 combo[1] = 0x38; /* Alt down */
860 combo[4] = 0xb8; /* Alt up */
861 combo[5] = 0x9d; /* Ctrl up */
862 if (pKeyEvent->key() >= Qt::Key_F1 && pKeyEvent->key() <= Qt::Key_F10)
863 {
864 combo[2] = 0x3b + (pKeyEvent->key() - Qt::Key_F1); /* F1-F10 down */
865 combo[3] = 0xbb + (pKeyEvent->key() - Qt::Key_F1); /* F1-F10 up */
866 }
867 /* There is some scan slice between F10 and F11 keys, so its separated: */
868 else if (pKeyEvent->key() >= Qt::Key_F11 && pKeyEvent->key() <= Qt::Key_F12)
869 {
870 combo[2] = 0x57 + (pKeyEvent->key() - Qt::Key_F11); /* F11-F12 down */
871 combo[3] = 0xd7 + (pKeyEvent->key() - Qt::Key_F11); /* F11-F12 up */
872 }
873 CKeyboard keyboard = session().GetConsole().GetKeyboard();
874 keyboard.PutScancodes(combo);
875 }
876 /* Process hot keys not processed in keyEvent() (as in case of non-alphanumeric keys): */
877 machineLogic()->actionsPool()->processHotKey(QKeySequence(pKeyEvent->key()));
878 }
879 else if (!m_bIsHostComboPressed && pEvent->type() == QEvent::KeyRelease)
880 {
881 /* Show a possible warning on key release which seems to be more expected by the end user: */
882 if (uisession()->isPaused())
883 {
884 /* If the reminder is disabled we pass the event to Qt to enable normal
885 * keyboard functionality (for example, menu access with Alt+Letter): */
886 if (!vboxProblem().remindAboutPausedVMInput())
887 break;
888 }
889 }
890 break;
891 }
892 default:
893 break;
894 }
895 }
896
897 /* Else just propagate to base-class: */
898 return QObject::eventFilter(pWatchedObject, pEvent);
899}
900
901#if defined(Q_WS_WIN)
902
903LRESULT CALLBACK UIKeyboardHandler::lowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
904{
905 if (nCode == HC_ACTION && m_spKeyboardHandler && m_spKeyboardHandler->winLowKeyboardEvent(wParam, *(KBDLLHOOKSTRUCT*)lParam))
906 return 1;
907
908 return CallNextHookEx(NULL, nCode, wParam, lParam);
909}
910
911bool UIKeyboardHandler::winLowKeyboardEvent(UINT msg, const KBDLLHOOKSTRUCT &event)
912{
913 /* Check what related machine-view was NOT unregistered yet: */
914 if (!m_views.contains(m_iKeyboardHookViewIndex))
915 return false;
916
917 if (!m_fIsKeyboardCaptured)
918 return false;
919
920 /* Sometimes it happens that Win inserts additional events on some key
921 * press/release. For example, it prepends ALT_GR in German layout with
922 * the VK_LCONTROL vkey with curious 0x21D scan code (seems to be necessary
923 * to specially treat ALT_GR to enter additional chars to regular apps).
924 * These events are definitely unwanted in VM, so filter them out. */
925 /* Note (michael): it also sometimes sends the VK_CAPITAL vkey with scan
926 * code 0x23a. If this is not passed through then it is impossible to
927 * cancel CapsLock on a French keyboard. I didn't find any other examples
928 * of these strange events. Let's hope we are not missing anything else
929 * of importance! */
930 if (m_views[m_iKeyboardHookViewIndex]->hasFocus() && (event.scanCode & ~0xFF))
931 {
932 if (event.vkCode == VK_CAPITAL)
933 return false;
934 else
935 return true;
936 }
937
938 /* It's possible that a key has been pressed while the keyboard was not
939 * captured, but is being released under the capture. Detect this situation
940 * and return false to let Windows process the message normally and update
941 * its key state table (to avoid the stuck key effect). */
942 uint8_t what_pressed = (event.flags & 0x01) && (event.vkCode != VK_RSHIFT) ? IsExtKeyPressed : IsKeyPressed;
943 if ((event.flags & 0x80) /* released */ &&
944 ((UIHotKeyCombination::toKeyCodeList(m_globalSettings.hostCombo()).contains(event.vkCode) && !m_bIsHostkeyInCapture) ||
945 (m_pressedKeys[event.scanCode] & (IsKbdCaptured | what_pressed)) == what_pressed))
946 return false;
947
948 MSG message;
949 message.hwnd = m_views[m_iKeyboardHookViewIndex]->winId();
950 message.message = msg;
951 message.wParam = event.vkCode;
952 message.lParam = 1 | (event.scanCode & 0xFF) << 16 | (event.flags & 0xFF) << 24;
953
954 /* Windows sets here the extended bit when the Right Shift key is pressed,
955 * which is totally wrong. Undo it. */
956 if (event.vkCode == VK_RSHIFT)
957 message.lParam &= ~0x1000000;
958
959 /* We suppose here that this hook is always called on the main GUI thread */
960 long dummyResult;
961 return m_views[m_iKeyboardHookViewIndex]->winEvent(&message, &dummyResult);
962}
963
964#elif defined(Q_WS_MAC)
965
966void UIKeyboardHandler::darwinGrabKeyboardEvents(bool fGrab)
967{
968 m_fKeyboardGrabbed = fGrab;
969 if (fGrab)
970 {
971 /* Disable mouse and keyboard event compression/delaying to make sure we *really* get all of the events. */
972 ::CGSetLocalEventsSuppressionInterval(0.0);
973 ::darwinSetMouseCoalescingEnabled(false);
974
975 /* Register the event callback/hook and grab the keyboard. */
976 UICocoaApplication::instance()->registerForNativeEvents(RT_BIT_32(10) | RT_BIT_32(11) | RT_BIT_32(12) /* NSKeyDown | NSKeyUp | | NSFlagsChanged */,
977 UIKeyboardHandler::darwinEventHandlerProc, this);
978
979 ::DarwinGrabKeyboard (false);
980 }
981 else
982 {
983 ::DarwinReleaseKeyboard();
984 UICocoaApplication::instance()->unregisterForNativeEvents(RT_BIT_32(10) | RT_BIT_32(11) | RT_BIT_32(12) /* NSKeyDown | NSKeyUp | | NSFlagsChanged */,
985 UIKeyboardHandler::darwinEventHandlerProc, this);
986 }
987}
988
989bool UIKeyboardHandler::darwinEventHandlerProc(const void *pvCocoaEvent, const void *pvCarbonEvent, void *pvUser)
990{
991 UIKeyboardHandler *pKeyboardHandler = (UIKeyboardHandler*)pvUser;
992 EventRef inEvent = (EventRef)pvCarbonEvent;
993 UInt32 eventClass = ::GetEventClass(inEvent);
994
995 /* Check if this is an application key combo. In that case we will not pass
996 * the event to the guest, but let the host process it. */
997 if (::darwinIsApplicationCommand(pvCocoaEvent))
998 return false;
999
1000 /* All keyboard class events needs to be handled. */
1001 if (eventClass == kEventClassKeyboard)
1002 {
1003 if (pKeyboardHandler->darwinKeyboardEvent (pvCocoaEvent, inEvent))
1004 return true;
1005 }
1006 /* Pass the event along. */
1007 return false;
1008}
1009
1010bool UIKeyboardHandler::darwinKeyboardEvent(const void *pvCocoaEvent, EventRef inEvent)
1011{
1012 bool ret = false;
1013 UInt32 EventKind = ::GetEventKind(inEvent);
1014 if (EventKind != kEventRawKeyModifiersChanged)
1015 {
1016 /* Convert keycode to set 1 scan code. */
1017 UInt32 keyCode = ~0U;
1018 ::GetEventParameter(inEvent, kEventParamKeyCode, typeUInt32, NULL, sizeof (keyCode), NULL, &keyCode);
1019 /* The usb keyboard driver translates these codes to different virtual
1020 * key codes depending of the keyboard type. There are ANSI, ISO, JIS
1021 * and unknown. For European keyboards (ISO) the key 0xa and 0x32 have
1022 * to be switched. Here we are doing this at runtime, cause the user
1023 * can have more than one keyboard (of different type), where he may
1024 * switch at will all the time. Default is the ANSI standard as defined
1025 * in g_aDarwinToSet1. Please note that the "~" on some English ISO
1026 * keyboards will be wrongly swapped. This can maybe fixed by
1027 * using a Apple keyboard layout in the guest. */
1028 if ( (keyCode == 0xa || keyCode == 0x32)
1029 && KBGetLayoutType(LMGetKbdType()) == kKeyboardISO)
1030 keyCode = 0x3c - keyCode;
1031 unsigned scanCode = ::DarwinKeycodeToSet1Scancode(keyCode);
1032 if (scanCode)
1033 {
1034 /* Calc flags. */
1035 int flags = 0;
1036 if (EventKind != kEventRawKeyUp)
1037 flags |= KeyPressed;
1038 if (scanCode & VBOXKEY_EXTENDED)
1039 flags |= KeyExtended;
1040 /** @todo KeyPause, KeyPrint. */
1041 scanCode &= VBOXKEY_SCANCODE_MASK;
1042
1043 /* Get the unicode string (if present). */
1044 AssertCompileSize(wchar_t, 2);
1045 AssertCompileSize(UniChar, 2);
1046 ByteCount cbWritten = 0;
1047 wchar_t ucs[8];
1048 if (::GetEventParameter(inEvent, kEventParamKeyUnicodes, typeUnicodeText, NULL,
1049 sizeof(ucs), &cbWritten, &ucs[0]) != 0)
1050 cbWritten = 0;
1051 ucs[cbWritten / sizeof(wchar_t)] = 0; /* The api doesn't terminate it. */
1052
1053 ret = keyEvent(keyCode, scanCode, flags, m_iKeyboardGrabViewIndex, ucs[0] ? ucs : NULL);
1054 }
1055 }
1056 else
1057 {
1058 /* May contain multiple modifier changes, kind of annoying. */
1059 UInt32 newMask = 0;
1060 ::GetEventParameter(inEvent, kEventParamKeyModifiers, typeUInt32, NULL,
1061 sizeof(newMask), NULL, &newMask);
1062 newMask = ::DarwinAdjustModifierMask(newMask, pvCocoaEvent);
1063 UInt32 changed = newMask ^ m_darwinKeyModifiers;
1064 if (changed)
1065 {
1066 for (UInt32 bit = 0; bit < 32; bit++)
1067 {
1068 if (!(changed & (1 << bit)))
1069 continue;
1070 unsigned scanCode = ::DarwinModifierMaskToSet1Scancode(1 << bit);
1071 if (!scanCode)
1072 continue;
1073 unsigned keyCode = ::DarwinModifierMaskToDarwinKeycode(1 << bit);
1074 Assert(keyCode);
1075
1076 if (!(scanCode & VBOXKEY_LOCK))
1077 {
1078 unsigned flags = (newMask & (1 << bit)) ? KeyPressed : 0;
1079 if (scanCode & VBOXKEY_EXTENDED)
1080 flags |= KeyExtended;
1081 scanCode &= VBOXKEY_SCANCODE_MASK;
1082 ret |= keyEvent(keyCode, scanCode & 0xff, flags, m_iKeyboardGrabViewIndex);
1083 }
1084 else
1085 {
1086 unsigned flags = 0;
1087 if (scanCode & VBOXKEY_EXTENDED)
1088 flags |= KeyExtended;
1089 scanCode &= VBOXKEY_SCANCODE_MASK;
1090 keyEvent(keyCode, scanCode, flags | KeyPressed, m_iKeyboardGrabViewIndex);
1091 keyEvent(keyCode, scanCode, flags, m_iKeyboardGrabViewIndex);
1092 }
1093 }
1094 }
1095
1096 m_darwinKeyModifiers = newMask;
1097
1098 /* Always return true here because we'll otherwise getting a Qt event
1099 we don't want and that will only cause the Pause warning to pop up. */
1100 ret = true;
1101 }
1102
1103 return ret;
1104}
1105
1106#endif
1107
1108bool UIKeyboardHandler::keyEvent(int iKey, uint8_t uScan, int fFlags, ulong uScreenId, wchar_t *pUniKey /* = 0 */)
1109{
1110 /* Get host-combo key list: */
1111 QSet<int> allHostComboKeys = UIHotKeyCombination::toKeyCodeList(m_globalSettings.hostCombo()).toSet();
1112
1113 /* Update the map of pressed host-combo keys: */
1114 if (fFlags & KeyPressed)
1115 {
1116 if (allHostComboKeys.contains(iKey))
1117 {
1118 if (!m_pressedHostComboKeys.contains(iKey))
1119 m_pressedHostComboKeys.insert(iKey, uScan);
1120 else if (m_bIsHostComboPressed)
1121 return true;
1122 }
1123 }
1124 else
1125 {
1126 if (allHostComboKeys.contains(iKey) && m_pressedHostComboKeys.contains(iKey))
1127 m_pressedHostComboKeys.remove(iKey);
1128 }
1129 /* Check if we are currently holding FULL host-combo: */
1130 bool fIsFullHostComboPresent = allHostComboKeys == m_pressedHostComboKeys.keys().toSet();
1131 /* Check if currently pressed/released key had changed host-combo state: */
1132 const bool isHostComboStateChanged = (!m_bIsHostComboPressed && fIsFullHostComboPresent) ||
1133 (m_bIsHostComboPressed && !fIsFullHostComboPresent);
1134
1135#ifdef Q_WS_WIN
1136 if (m_bIsHostComboPressed || isHostComboStateChanged)
1137 {
1138 /* Currently this is used in winLowKeyboardEvent() only: */
1139 m_bIsHostkeyInCapture = m_fIsKeyboardCaptured;
1140 }
1141#endif /* Q_WS_WIN */
1142
1143 /* Check if it's C-A-D and GUI/PassCAD is not true: */
1144 if (!m_fPassCAD &&
1145 uScan == 0x53 /* Del */ &&
1146 ((m_pressedKeys[0x38] & IsKeyPressed) /* Alt */ ||
1147 (m_pressedKeys[0x38] & IsExtKeyPressed)) &&
1148 ((m_pressedKeys[0x1d] & IsKeyPressed) /* Ctrl */ ||
1149 (m_pressedKeys[0x1d] & IsExtKeyPressed)))
1150 {
1151 /* Use the C-A-D combination as a last resort to get the keyboard and mouse back
1152 * to the host when the user forgets the Host Key. Note that it's always possible
1153 * to send C-A-D to the guest using the Host+Del combination: */
1154 if (uisession()->isRunning() && m_fIsKeyboardCaptured)
1155 {
1156 releaseKeyboard();
1157 if (!uisession()->isMouseSupportsAbsolute() || !uisession()->isMouseIntegrated())
1158 machineLogic()->mouseHandler()->releaseMouse();
1159 }
1160 return true;
1161 }
1162
1163 /* Prepare empty code-buffer: */
1164 LONG aCodesBuffer[16];
1165 LONG *pCodes = aCodesBuffer;
1166 uint uCodesCount = 0;
1167 /* Processing usual key-presses/releases without host-key being held: */
1168 if (!m_bIsHostComboPressed || isHostComboStateChanged)
1169 {
1170 /* Special flags handling (KeyPrint): */
1171 if (fFlags & KeyPrint)
1172 {
1173 if (fFlags & KeyPressed)
1174 {
1175 static LONG PrintMake[] = { 0xE0, 0x2A, 0xE0, 0x37 };
1176 pCodes = PrintMake;
1177 uCodesCount = SIZEOF_ARRAY(PrintMake);
1178 }
1179 else
1180 {
1181 static LONG PrintBreak[] = { 0xE0, 0xB7, 0xE0, 0xAA };
1182 pCodes = PrintBreak;
1183 uCodesCount = SIZEOF_ARRAY(PrintBreak);
1184 }
1185 }
1186 /* Special flags handling (KeyPause): */
1187 else if (fFlags & KeyPause)
1188 {
1189 if (fFlags & KeyPressed)
1190 {
1191 static LONG Pause[] = { 0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5 };
1192 pCodes = Pause;
1193 uCodesCount = SIZEOF_ARRAY(Pause);
1194 }
1195 else
1196 {
1197 /* Pause shall not produce a break code: */
1198 return true;
1199 }
1200 }
1201 /* Common flags handling: */
1202 else
1203 {
1204 /* Get the type of key - simple or extended: */
1205 uint8_t uWhatPressed = fFlags & KeyExtended ? IsExtKeyPressed : IsKeyPressed;
1206
1207 /* If some key was pressed or some previously pressed key was released =>
1208 * we are updating the list of pressed keys and preparing scancodes: */
1209 if ((fFlags & KeyPressed) || (m_pressedKeys[uScan] & uWhatPressed))
1210 {
1211 /* Check if the guest has the same view on the modifier keys
1212 * (NumLock, CapsLock, ScrollLock) as the X server.
1213 * If not, send KeyPress events to synchronize the state: */
1214 if (fFlags & KeyPressed)
1215 fixModifierState(pCodes, &uCodesCount);
1216
1217 /* Prepend 'extended' scancode if needed: */
1218 if (fFlags & KeyExtended)
1219 pCodes[uCodesCount++] = 0xE0;
1220
1221 /* Process key-press: */
1222 if (fFlags & KeyPressed)
1223 {
1224 /* Append scancode: */
1225 pCodes[uCodesCount++] = uScan;
1226 m_pressedKeys[uScan] |= uWhatPressed;
1227 }
1228 /* Process key-release if that key was pressed before: */
1229 else if (m_pressedKeys[uScan] & uWhatPressed)
1230 {
1231 /* Append scancode: */
1232 pCodes[uCodesCount++] = uScan | 0x80;
1233 m_pressedKeys[uScan] &= ~uWhatPressed;
1234 }
1235
1236 /* Update keyboard-captured flag: */
1237 if (m_fIsKeyboardCaptured)
1238 m_pressedKeys[uScan] |= IsKbdCaptured;
1239 else
1240 m_pressedKeys[uScan] &= ~IsKbdCaptured;
1241 }
1242 /* Ignore key-release if that key was NOT pressed before,
1243 * but only if thats not one of the host-combination keys: */
1244 else if (!allHostComboKeys.contains(iKey))
1245 return true;
1246 }
1247 }
1248
1249 /* Process the host-combo funtionality: */
1250 if (fFlags & KeyPressed)
1251 {
1252 if (isHostComboStateChanged)
1253 {
1254 if (!m_bIsHostComboPressed)
1255 {
1256 m_bIsHostComboPressed = true;
1257 m_bIsHostComboAlone = true;
1258 if (uisession()->isRunning())
1259 saveKeyStates();
1260 }
1261 }
1262 else
1263 {
1264 if (m_bIsHostComboPressed)
1265 {
1266 if (m_bIsHostComboAlone)
1267 {
1268 m_bIsHostComboAlone = false;
1269 m_pressedHostComboKeys.clear();
1270 /* Process Host+<key> shortcuts.
1271 * Currently, <key> is limited to alphanumeric chars.
1272 * Other Host+<key> combinations are handled in Qt event(): */
1273 return processHotKey(iKey, pUniKey);
1274 }
1275 }
1276 }
1277 }
1278 else
1279 {
1280 if (isHostComboStateChanged)
1281 {
1282 if (m_bIsHostComboPressed)
1283 {
1284 m_bIsHostComboPressed = false;
1285 if (m_bIsHostComboAlone)
1286 {
1287 /* Capturing/releasing keyboard/mouse: */
1288 if (uisession()->isRunning())
1289 {
1290 bool ok = true;
1291 if (!m_fIsKeyboardCaptured)
1292 {
1293 /* Temporarily disable auto-capture that will take place after
1294 * this dialog is dismissed because the capture state is to be
1295 * defined by the dialog result itself: */
1296 uisession()->setAutoCaptureDisabled(true);
1297 bool fIsAutoConfirmed = false;
1298 ok = vboxProblem().confirmInputCapture(&fIsAutoConfirmed);
1299 if (fIsAutoConfirmed)
1300 uisession()->setAutoCaptureDisabled(false);
1301 else
1302 m_pressedHostComboKeys.clear();
1303 /* Otherwise, the disable flag will be reset in the next
1304 * machine-view's focus-in event (since may happen asynchronously
1305 * on some platforms, after we return from this code): */
1306 }
1307 if (ok)
1308 {
1309 if (m_fIsKeyboardCaptured)
1310 releaseKeyboard();
1311 else
1312 captureKeyboard(uScreenId);
1313 if (!uisession()->isMouseSupportsAbsolute() || !uisession()->isMouseIntegrated())
1314 {
1315#ifdef Q_WS_X11
1316 /* Make sure that pending FocusOut events from the
1317 * previous message box are handled, otherwise the
1318 * mouse is immediately ungrabbed: */
1319 qApp->processEvents();
1320#endif /* Q_WS_X11 */
1321 if (m_fIsKeyboardCaptured)
1322 machineLogic()->mouseHandler()->captureMouse(uScreenId);
1323 else
1324 machineLogic()->mouseHandler()->releaseMouse();
1325 }
1326 }
1327 }
1328 }
1329 if (uisession()->isRunning())
1330 sendChangedKeyStates();
1331 }
1332 }
1333 else
1334 {
1335 if (m_bIsHostComboPressed)
1336 m_bIsHostComboAlone = false;
1337 }
1338 }
1339
1340 /* Notify all listeners: */
1341 emit keyboardStateChanged(keyboardState());
1342
1343 /* If the VM is NOT paused and there are scancodes to send: */
1344 if (!uisession()->isPaused() && uCodesCount)
1345 {
1346 /* Get the VM keyboard to pass key in there: */
1347 CKeyboard keyboard = session().GetConsole().GetKeyboard();
1348 Assert(!keyboard.isNull());
1349
1350 /* Pass this key to the guest: */
1351 std::vector<LONG> scancodes(pCodes, &pCodes[uCodesCount]);
1352 keyboard.PutScancodes(QVector<LONG>::fromStdVector(scancodes));
1353
1354 /* If full host-key sequence was just finalized
1355 * and the last key of host-combination was just sent to the guest =>
1356 * we have to notify guest to make it release keys from the host-combination: */
1357 if (isHostComboStateChanged && m_bIsHostComboPressed)
1358 {
1359 QList<uint8_t> hostComboScans = m_pressedHostComboKeys.values();
1360 for (int i = 0 ; i < hostComboScans.size(); ++i)
1361 {
1362 uint8_t uScan = hostComboScans[i];
1363 if (m_pressedKeys[uScan] & IsKeyPressed)
1364 {
1365 keyboard.PutScancode(uScan | 0x80);
1366 }
1367 else if (m_pressedKeys[uScan] & IsExtKeyPressed)
1368 {
1369 QVector<LONG> scancodes(2);
1370 scancodes[0] = 0xE0;
1371 scancodes[1] = uScan | 0x80;
1372 keyboard.PutScancodes(scancodes);
1373 }
1374 m_pressedKeys[uScan] = 0;
1375 }
1376 }
1377 }
1378
1379 /* Prevent the key from going to Qt: */
1380 return true;
1381}
1382
1383bool UIKeyboardHandler::processHotKey(int iHotKey, wchar_t *pHotKey)
1384{
1385 /* Prepare processing result: */
1386 bool fWasProcessed = false;
1387
1388#ifdef Q_WS_WIN
1389 Q_UNUSED(pHotKey);
1390 int iKeyboardLayout = GetKeyboardLayoutList(0, NULL);
1391 Assert(iKeyboardLayout);
1392 HKL *pList = new HKL[iKeyboardLayout];
1393 GetKeyboardLayoutList(iKeyboardLayout, pList);
1394 for (int i = 0; i < iKeyboardLayout && !fWasProcessed; ++i)
1395 {
1396 wchar_t symbol;
1397 static BYTE keys[256] = {0};
1398 if (!ToUnicodeEx(iHotKey, 0, keys, &symbol, 1, 0, pList[i]) == 1)
1399 symbol = 0;
1400 if (symbol)
1401 fWasProcessed = machineLogic()->actionsPool()->processHotKey(QKeySequence((Qt::UNICODE_ACCEL + QChar(symbol).toUpper().unicode())));
1402 }
1403 delete[] pList;
1404#endif /* Q_WS_WIN */
1405
1406#ifdef Q_WS_X11
1407 Q_UNUSED(pHotKey);
1408 Display *pDisplay = QX11Info::display();
1409 int iKeysymsPerKeycode = getKeysymsPerKeycode();
1410 KeyCode keyCode = XKeysymToKeycode(pDisplay, iHotKey);
1411 for (int i = 0; i < iKeysymsPerKeycode && !fWasProcessed; i += 2)
1412 {
1413 KeySym ks = XKeycodeToKeysym(pDisplay, keyCode, i);
1414 char symbol = 0;
1415 if (!XkbTranslateKeySym(pDisplay, &ks, 0, &symbol, 1, NULL) == 1)
1416 symbol = 0;
1417 if (symbol)
1418 {
1419 QChar qtSymbol = QString::fromLocal8Bit(&symbol, 1)[0];
1420 fWasProcessed = machineLogic()->actionsPool()->processHotKey(QKeySequence((Qt::UNICODE_ACCEL + qtSymbol.toUpper().unicode())));
1421 }
1422 }
1423#endif /* Q_WS_X11 */
1424
1425#ifdef Q_WS_MAC
1426 Q_UNUSED(iHotKey);
1427 if (pHotKey && pHotKey[0] && !pHotKey[1])
1428 fWasProcessed = machineLogic()->actionsPool()->processHotKey(QKeySequence(Qt::UNICODE_ACCEL + QChar(pHotKey[0]).toUpper().unicode()));
1429#endif /* Q_WS_MAC */
1430
1431 /* Grab the key from the Qt if it was processed, or pass it to the Qt otherwise
1432 * in order to process non-alphanumeric keys in event(), after they are converted to Qt virtual keys: */
1433 return fWasProcessed;
1434}
1435
1436void UIKeyboardHandler::fixModifierState(LONG *piCodes, uint *puCount)
1437{
1438 /* Synchronize the views of the host and the guest to the modifier keys.
1439 * This function will add up to 6 additional keycodes to codes. */
1440#if defined(Q_WS_X11)
1441 Window wDummy1, wDummy2;
1442 int iDummy3, iDummy4, iDummy5, iDummy6;
1443 unsigned uMask;
1444 unsigned uKeyMaskNum = 0, uKeyMaskCaps = 0, uKeyMaskScroll = 0;
1445
1446 uKeyMaskCaps = LockMask;
1447 XModifierKeymap* map = XGetModifierMapping(QX11Info::display());
1448 KeyCode keyCodeNum = XKeysymToKeycode(QX11Info::display(), XK_Num_Lock);
1449 KeyCode keyCodeScroll = XKeysymToKeycode(QX11Info::display(), XK_Scroll_Lock);
1450
1451 for (int i = 0; i < 8; ++ i)
1452 {
1453 if (keyCodeNum != NoSymbol && map->modifiermap[map->max_keypermod * i] == keyCodeNum)
1454 uKeyMaskNum = 1 << i;
1455 else if (keyCodeScroll != NoSymbol && map->modifiermap[map->max_keypermod * i] == keyCodeScroll)
1456 uKeyMaskScroll = 1 << i;
1457 }
1458 XQueryPointer(QX11Info::display(), DefaultRootWindow(QX11Info::display()), &wDummy1, &wDummy2,
1459 &iDummy3, &iDummy4, &iDummy5, &iDummy6, &uMask);
1460 XFreeModifiermap(map);
1461
1462 if (uisession()->numLockAdaptionCnt() && (uisession()->isNumLock() ^ !!(uMask & uKeyMaskNum)))
1463 {
1464 uisession()->setNumLockAdaptionCnt(uisession()->numLockAdaptionCnt() - 1);
1465 piCodes[(*puCount)++] = 0x45;
1466 piCodes[(*puCount)++] = 0x45 | 0x80;
1467 }
1468 if (uisession()->capsLockAdaptionCnt() && (uisession()->isCapsLock() ^ !!(uMask & uKeyMaskCaps)))
1469 {
1470 uisession()->setCapsLockAdaptionCnt(uisession()->capsLockAdaptionCnt() - 1);
1471 piCodes[(*puCount)++] = 0x3a;
1472 piCodes[(*puCount)++] = 0x3a | 0x80;
1473 /* Some keyboard layouts require shift to be pressed to break
1474 * capslock. For simplicity, only do this if shift is not
1475 * already held down. */
1476 if (uisession()->isCapsLock() && !(m_pressedKeys[0x2a] & IsKeyPressed))
1477 {
1478 piCodes[(*puCount)++] = 0x2a;
1479 piCodes[(*puCount)++] = 0x2a | 0x80;
1480 }
1481 }
1482#elif defined(Q_WS_WIN)
1483 if (uisession()->numLockAdaptionCnt() && (uisession()->isNumLock() ^ !!(GetKeyState(VK_NUMLOCK))))
1484 {
1485 uisession()->setNumLockAdaptionCnt(uisession()->numLockAdaptionCnt() - 1);
1486 piCodes[(*puCount)++] = 0x45;
1487 piCodes[(*puCount)++] = 0x45 | 0x80;
1488 }
1489 if (uisession()->capsLockAdaptionCnt() && (uisession()->isCapsLock() ^ !!(GetKeyState(VK_CAPITAL))))
1490 {
1491 uisession()->setCapsLockAdaptionCnt(uisession()->capsLockAdaptionCnt() - 1);
1492 piCodes[(*puCount)++] = 0x3a;
1493 piCodes[(*puCount)++] = 0x3a | 0x80;
1494 /* Some keyboard layouts require shift to be pressed to break
1495 * capslock. For simplicity, only do this if shift is not
1496 * already held down. */
1497 if (uisession()->isCapsLock() && !(m_pressedKeys[0x2a] & IsKeyPressed))
1498 {
1499 piCodes[(*puCount)++] = 0x2a;
1500 piCodes[(*puCount)++] = 0x2a | 0x80;
1501 }
1502 }
1503#elif defined(Q_WS_MAC)
1504 /* if (uisession()->numLockAdaptionCnt()) ... - NumLock isn't implemented by Mac OS X so ignore it. */
1505 if (uisession()->capsLockAdaptionCnt() && (uisession()->isCapsLock() ^ !!(::GetCurrentEventKeyModifiers() & alphaLock)))
1506 {
1507 uisession()->setCapsLockAdaptionCnt(uisession()->capsLockAdaptionCnt() - 1);
1508 piCodes[(*puCount)++] = 0x3a;
1509 piCodes[(*puCount)++] = 0x3a | 0x80;
1510 /* Some keyboard layouts require shift to be pressed to break
1511 * capslock. For simplicity, only do this if shift is not
1512 * already held down. */
1513 if (uisession()->isCapsLock() && !(m_pressedKeys[0x2a] & IsKeyPressed))
1514 {
1515 piCodes[(*puCount)++] = 0x2a;
1516 piCodes[(*puCount)++] = 0x2a | 0x80;
1517 }
1518 }
1519#else
1520//#warning Adapt UIKeyboardHandler::fixModifierState
1521#endif
1522}
1523
1524void UIKeyboardHandler::saveKeyStates()
1525{
1526 ::memcpy(m_pressedKeysCopy, m_pressedKeys, sizeof(m_pressedKeys));
1527}
1528
1529void UIKeyboardHandler::sendChangedKeyStates()
1530{
1531 QVector <LONG> codes(2);
1532 CKeyboard keyboard = session().GetConsole().GetKeyboard();
1533 for (uint i = 0; i < SIZEOF_ARRAY(m_pressedKeys); ++ i)
1534 {
1535 uint8_t os = m_pressedKeysCopy[i];
1536 uint8_t ns = m_pressedKeys[i];
1537 if ((os & IsKeyPressed) != (ns & IsKeyPressed))
1538 {
1539 codes[0] = i;
1540 if (!(ns & IsKeyPressed))
1541 codes[0] |= 0x80;
1542 keyboard.PutScancode(codes[0]);
1543 }
1544 else if ((os & IsExtKeyPressed) != (ns & IsExtKeyPressed))
1545 {
1546 codes[0] = 0xE0;
1547 codes[1] = i;
1548 if (!(ns & IsExtKeyPressed))
1549 codes[1] |= 0x80;
1550 keyboard.PutScancodes(codes);
1551 }
1552 }
1553}
1554
1555UIMachineWindow* UIKeyboardHandler::isItListenedWindow(QObject *pWatchedObject) const
1556{
1557 UIMachineWindow *pResultWindow = 0;
1558 QMap<ulong, UIMachineWindow*>::const_iterator i = m_windows.constBegin();
1559 while (!pResultWindow && i != m_windows.constEnd())
1560 {
1561 UIMachineWindow *pIteratedWindow = i.value();
1562 if (pIteratedWindow->machineWindow() == pWatchedObject)
1563 {
1564 pResultWindow = pIteratedWindow;
1565 continue;
1566 }
1567 ++i;
1568 }
1569 return pResultWindow;
1570}
1571
1572UIMachineView* UIKeyboardHandler::isItListenedView(QObject *pWatchedObject) const
1573{
1574 UIMachineView *pResultView = 0;
1575 QMap<ulong, UIMachineView*>::const_iterator i = m_views.constBegin();
1576 while (!pResultView && i != m_views.constEnd())
1577 {
1578 UIMachineView *pIteratedView = i.value();
1579 if (pIteratedView == pWatchedObject)
1580 {
1581 pResultView = pIteratedView;
1582 continue;
1583 }
1584 ++i;
1585 }
1586 return pResultView;
1587}
1588
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use