/**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of QtUiTest. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qinputgenerator_p.h" #include #include #include #include #include "qtuitestnamespace.h" #include "private/qcore_mac_p.h" #define QINPUTGENERATOR_DEBUG() if (1); else qDebug() << "QInputGenerator:" QMap > qt_key_to_mac_keycode_make() { QMap > m; static UInt32 deadKeyState = 0L; const UCKeyboardLayout *keyLayout = 0; #if defined(Q_OS_MAC32) KeyboardLayoutRef keyLayoutRef = 0; KLGetCurrentKeyboardLayout(&keyLayoutRef); OSStatus err; if (keyLayoutRef != 0) { err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLuchrData, (reinterpret_cast(&keyLayout))); if (err != noErr) { qWarning("Qt::internal::unable to get keyboardlayout %ld %s:%d", long(err), __FILE__, __LINE__); } } #else QCFType inputSource = TISCopyCurrentKeyboardInputSource(); Q_ASSERT(inputSource != 0); CFDataRef data = static_cast(TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData)); keyLayout = data ? reinterpret_cast(CFDataGetBytePtr(data)) : 0; #endif if (keyLayout) { UniChar string[4]; UniCharCount actualLength; for (UInt32 keyCode = 0; keyCode < 128; ++keyCode) { if (noErr == UCKeyTranslate(keyLayout, keyCode, kUCKeyActionDisplay, 0, LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask, &deadKeyState, 4, &actualLength, string)) m.insert(int(string[0]), qMakePair(int(keyCode), Qt::KeyboardModifiers(Qt::NoModifier))); if (noErr == UCKeyTranslate(keyLayout, keyCode, kUCKeyActionDisplay, ((shiftKey >> 8) & 0xff), LMGetKbdType(), kUCKeyTranslateNoDeadKeysMask, &deadKeyState, 4, &actualLength, string)) m.insert(int(string[0]), qMakePair(int(keyCode), Qt::KeyboardModifiers(Qt::ShiftModifier))); } } #ifdef Q_OS_MAC32 else { const void *keyboard_layout; KeyboardLayoutRef keyLayoutRef = 0; KLGetCurrentKeyboardLayout(&keyLayoutRef); err = KLGetKeyboardLayoutProperty(keyLayoutRef, kKLKCHRData, reinterpret_cast(&keyboard_layout)); for (UInt32 keyCode = 0; keyCode < 128; ++keyCode) { int translatedChar = KeyTranslate(keyboard_layout, keyCode, &deadKeyState); if (translatedChar >= Qt::Key_Space && translatedChar <= Qt::Key_AsciiTilde) { m.insert(translatedChar, qMakePair(int(keyCode), Qt::KeyboardModifiers(Qt::NoModifier))); } translatedChar = KeyTranslate(keyboard_layout, keyCode | shiftKey, &deadKeyState); if (translatedChar >= Qt::Key_Space && translatedChar <= Qt::Key_AsciiTilde) { m.insert(translatedChar, qMakePair(int(keyCode), Qt::KeyboardModifiers(Qt::ShiftModifier))); } } } #endif m.insert( Qt::Key_Escape, qMakePair(53, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Tab, qMakePair(48, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Backspace, qMakePair(51, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Return, qMakePair(36, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Enter, qMakePair(76, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Insert, qMakePair(114, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Delete, qMakePair(117, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Print, qMakePair(105, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Space, qMakePair(49, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_PageUp, qMakePair(116, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_PageDown, qMakePair(121, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_End, qMakePair(119, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Home, qMakePair(115, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Left, qMakePair(123, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Up, qMakePair(126, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Right, qMakePair(124, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Down, qMakePair(125, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_CapsLock, qMakePair(57, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_NumLock, qMakePair(71, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F1, qMakePair(122, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F2, qMakePair(120, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F3, qMakePair(99, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F4, qMakePair(118, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F5, qMakePair(96, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F6, qMakePair(97, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F7, qMakePair(98, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F8, qMakePair(100, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F9, qMakePair(101, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F10, qMakePair(109, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F11, qMakePair(103, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F12, qMakePair(111, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F13, qMakePair(105, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F14, qMakePair(107, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_F15, qMakePair(113, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Menu, qMakePair(110, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Help, qMakePair(114, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_division, qMakePair(75, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_multiply, qMakePair(67, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Minus, qMakePair(78, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Plus, qMakePair(69, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Period, qMakePair(65, Qt::KeyboardModifiers(Qt::NoModifier)) ); // Modifiers m.insert( Qt::ShiftModifier, qMakePair(56, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Shift, qMakePair(56, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::ControlModifier, qMakePair(55, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Control, qMakePair(55, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::AltModifier, qMakePair(58, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Alt, qMakePair(58, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::MetaModifier, qMakePair(59, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_Meta, qMakePair(55, Qt::KeyboardModifiers(Qt::NoModifier)) ); m.insert( Qt::Key_CapsLock, qMakePair(57, Qt::KeyboardModifiers(Qt::NoModifier)) ); return m; } QMap qt_button_to_mac_button_down_make() { QMap m; m.insert( Qt::LeftButton, kCGEventLeftMouseDown ); m.insert( Qt::MidButton, kCGEventOtherMouseDown ); m.insert( Qt::RightButton, kCGEventRightMouseDown ); return m; } QMap qt_button_to_mac_button_up_make() { QMap m; m.insert( Qt::LeftButton, kCGEventLeftMouseUp ); m.insert( Qt::MidButton, kCGEventOtherMouseUp ); m.insert( Qt::RightButton, kCGEventRightMouseUp ); return m; } QMap qt_button_to_mac_button_drag_make() { QMap m; m.insert( Qt::LeftButton, kCGEventLeftMouseDragged ); m.insert( Qt::MidButton, kCGEventOtherMouseDragged ); m.insert( Qt::RightButton, kCGEventRightMouseDragged ); return m; } QMap qt_button_to_mac_button_make() { QMap m; m.insert( Qt::LeftButton, kCGMouseButtonLeft ); m.insert( Qt::MidButton, kCGMouseButtonCenter ); m.insert( Qt::RightButton, kCGMouseButtonRight ); return m; } class QInputGeneratorPrivate { public: QInputGeneratorPrivate(); QInputGenerator* q; void keyEvent(Qt::Key, bool); void mouseEvent(QPoint const&, Qt::MouseButtons); void ensureModifiers(Qt::KeyboardModifiers); Qt::KeyboardModifiers currentModifiers() const; QMap > const keyMap; QMap const buttonMap; QMap const buttonDownMap; QMap const buttonUpMap; QMap const buttonDragMap; QPoint currentPos; Qt::MouseButtons currentButtons; }; QInputGeneratorPrivate::QInputGeneratorPrivate() : keyMap(qt_key_to_mac_keycode_make()), buttonMap(qt_button_to_mac_button_make()), buttonDownMap(qt_button_to_mac_button_down_make()), buttonUpMap(qt_button_to_mac_button_up_make()), buttonDragMap(qt_button_to_mac_button_drag_make()), currentPos(), currentButtons(0) { //Following line is workaround for a bug in Tiger... CFRelease(CGEventCreate(NULL)); } QInputGenerator::QInputGenerator(QObject* parent) : QObject(parent), d(new QInputGeneratorPrivate) { d->q = this; QINPUTGENERATOR_DEBUG() << "constructor"; } QInputGenerator::~QInputGenerator() { QINPUTGENERATOR_DEBUG() << "destructor"; /* Restore all keyboard modifiers to off. If we don't do this, the current modifiers stay activated even when this application is closed. Note that there is no guarantee this code actually gets run. */ d->ensureModifiers(0); if (d->currentButtons) { d->mouseEvent(d->currentPos, 0); } d->q = 0; delete d; d = 0; } /* Returns the Qt keyboard modifiers which are currently pressed. */ Qt::KeyboardModifiers QInputGeneratorPrivate::currentModifiers() const { quint32 modifiers = GetCurrentKeyModifiers(); int state = 0; state |= (modifiers & shiftKey ? Qt::ShiftModifier : Qt::NoModifier); state |= (modifiers & controlKey ? Qt::ControlModifier : Qt::NoModifier); state |= (modifiers & optionKey ? Qt::AltModifier : Qt::NoModifier); state |= (modifiers & cmdKey ? Qt::MetaModifier : Qt::NoModifier); return Qt::KeyboardModifier(state); } void QInputGeneratorPrivate::ensureModifiers(Qt::KeyboardModifiers desiredMod) { Qt::KeyboardModifiers currentMod = currentModifiers(); // For each modifier.. for (unsigned i = 0; i < sizeof(q->AllModifiers)/sizeof(Qt::KeyboardModifier); ++i) { Qt::KeyboardModifier thisMod = q->AllModifiers[i]; // If the modifier is currently disabled but we want it enabled, or vice-versa... if ((desiredMod & thisMod) && !(currentMod & thisMod)) { QINPUTGENERATOR_DEBUG() << "Enabling modifier" << (void*)thisMod << "by press"; // press to enable keyEvent(q->modifierToKey(thisMod), true); } else if (!(desiredMod & thisMod) && (currentMod & thisMod)) { QINPUTGENERATOR_DEBUG() << "Disabling modifier" << (void*)thisMod << "by release"; // release to disable keyEvent(q->modifierToKey(thisMod), false); } } if (currentMod != desiredMod) { currentMod = desiredMod; for (int i = 0; i < 1000 && QApplication::keyboardModifiers() & Qt::KeypadModifier != desiredMod & Qt::KeypadModifier; i += 50, QtUiTest::wait(50)) {} if (QApplication::keyboardModifiers() & Qt::KeypadModifier != desiredMod & Qt::KeypadModifier) qWarning() << "QtUitest: couldn't set all modifiers to desired state! " "Current state:" << (void*)(int)QApplication::keyboardModifiers() << "Desired:" << (void*)(int)desiredMod; } } void QInputGeneratorPrivate::keyEvent(Qt::Key key, bool is_press) { int sym = 0; Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(Qt::NoModifier); do { QMap >::const_iterator found = keyMap.find(key); if (found != keyMap.end()) { sym = (*found).first; if (key < Qt::Key_A || key > Qt::Key_Z) modifiers = (*found).second; break; } qWarning() << "QInputGenerator: don't know how to translate Qt key" << (void*)key << "into Mac keycode"; return; } while(0); if (modifiers) { ensureModifiers(modifiers); } //TODO: CGPostKeyboardEvent is deprecated in Snow Leopard, however the alternative (using CGEventPost) // does not work as reliably (at least on Tiger/PPC). Further investigation required. CGPostKeyboardEvent(0, (CGKeyCode)sym, is_press); #if 0 CGEventRef event = CGEventCreateKeyboardEvent(NULL, (CGKeyCode)sym, is_press); CGEventPost(kCGHIDEventTap, event); CFRelease(event); #endif QINPUTGENERATOR_DEBUG() << (is_press ? "press" : "release") << sym; } void QInputGenerator::keyPress(Qt::Key key, Qt::KeyboardModifiers mod, bool autoRepeat) { Q_UNUSED(autoRepeat); d->ensureModifiers(mod); d->keyEvent(key, true); QtUiTest::wait(1); } void QInputGenerator::keyRelease(Qt::Key key, Qt::KeyboardModifiers mod) { d->ensureModifiers(mod); d->keyEvent(key, false); QtUiTest::wait(1); } void QInputGenerator::keyClick(Qt::Key key, Qt::KeyboardModifiers mod) { keyPress(key,mod); QtUiTest::wait(20); keyRelease(key,mod); } void QInputGeneratorPrivate::mouseEvent(QPoint const& pos, Qt::MouseButtons state) { CGPoint p; p.x = (float)(pos.x()); p.y = (float)(pos.y()); ProcessSerialNumber psn; GetCurrentProcess(&psn); //TODO: CGPostMouseEvent is deprecated in Snow Leopard, however the alternative (using CGEventPost) // does not work as reliably (at least on Tiger/PPC). Further investigation required. CGPostMouseEvent(p, true, 3, Qt::LeftButton & state, Qt::RightButton & state, Qt::MidButton & state); #if 0 if (currentPos != pos) { currentPos = pos; QINPUTGENERATOR_DEBUG() << "Mouse is at: " << QCursor::pos(); CGEventType eventType = kCGEventMouseMoved; CGEventRef eventRef = CGEventCreateMouseEvent(NULL, eventType, p, kCGMouseButtonLeft); //This shouldn't be necessary, but it is... CGEventSetType(eventRef, eventType); CGEventPostToPSN(&psn, eventRef); CFRelease(eventRef); for (int i = 0; i < 1000 && QCursor::pos() != pos; i += 50, QtUiTest::wait(50)) {} QINPUTGENERATOR_DEBUG() << "Mouse is now at: " << QCursor::pos(); if (QCursor::pos() != pos) qWarning() << "QtUitest: couldn't move cursor to desired point! " "Current position:" << QCursor::pos() << "Desired:" << pos; } typedef QPair ButtonEvent; QList buttonEvents; foreach (int button, buttonMap.keys()) { bool desired = button & state; bool current = button & currentButtons; // Do we need to release? if (!desired && current) { buttonEvents << qMakePair(buttonUpMap[button], buttonMap[button]); QINPUTGENERATOR_DEBUG() << "releasing " << buttonMap[button]; } // Do we need to press? if (desired && !current) { buttonEvents << qMakePair(buttonDownMap[button], buttonMap[button]); QINPUTGENERATOR_DEBUG() << "pressing " << buttonMap[button]; } // Do we need to drag? Is this necessary? if (desired && current) { buttonEvents << qMakePair(buttonDragMap[button], buttonMap[button]); QINPUTGENERATOR_DEBUG() << "dragging " << buttonMap[button]; } } foreach (ButtonEvent const& event, buttonEvents) { CGEventType eventType = event.first; QINPUTGENERATOR_DEBUG() << "posting Quartz event " << eventType << ", " << event.second; CGEventRef eventRef = CGEventCreateMouseEvent(NULL, eventType, p, event.second); CGEventSetType(eventRef, eventType); CGEventPostToPSN(&psn, eventRef); CFRelease(eventRef); } #endif currentButtons = state; } void QInputGenerator::mousePress(QPoint const& pos, Qt::MouseButtons state) { QINPUTGENERATOR_DEBUG() << "Mouse press" << pos << (void*)(int)state; d->mouseEvent(pos, d->currentButtons | state); } void QInputGenerator::mouseRelease(QPoint const& pos, Qt::MouseButtons state) { QINPUTGENERATOR_DEBUG() << "Mouse release" << pos << (void*)(int)(d->currentButtons & ~state); d->mouseEvent(pos, d->currentButtons & ~state); } void QInputGenerator::mouseClick(QPoint const& pos, Qt::MouseButtons state) { mousePress (pos,state); QtUiTest::wait(1); mouseRelease(pos,state); QtUiTest::wait(1); }